JUC并发编程(java util concurrent)(哔站 狂神说java juc并发编程 摘录笔记)
JUC并发编程(java util concurrent)
1、什么是JUC
JUC并不是一个很神秘的东西(就是 java.util 工具包、包、分类)
业务:普通的线程代码 Thread
Runnable 没有返回值、效率相比于 Callable 相对较低!
2、线程和进程
进程:一个程序,如:QQ.exe Music.exe 程序的集合;
一个进程往往可以包含多个线程,至少包含一个!
java默认有几个线程?2个 一个是我们的main线程,一个是我们的GC线程
线程:开了一个进程,比如typora,我们可以在上面写字(有一个自动保存功能,就是线程负责的)
对于java而言:Thread Runnable Callable
java真的可以开启线程吗? 答案是开不了的,它要去调用一个本地方法,底层的c++开启,java是无法直接操作硬件的,是在我们的jvm(java虚拟机)上面运行的。
**并发:**多线程操作同一个资源;
- CPU 为单核,模拟出来多条线程,天下武功,为快不破,快速交替
并行:多个人一起行走;
CUP为多核,多个线程可以同时执行
package com.hejign.demo01;public class Test1 {public static void main(String[] args) {//获取CPU核数//CPU密集型 IO密集型System.out.println(Runtime.getRuntime().availableProcessors());} }
并发编程的本质:充分利用CPU 的资源
线程有几个状态:
public enum State {/*** 新生*/NEW,/** 运行*/RUNNABLE,/**阻塞*/BLOCKED,/*** 等待,死死的等*/WAITING,/*** 超时等待*/TIMED_WAITING,/*** 终止*/TERMINATED;}
wait和sleep的区别:
1、来自不同的类 wait 是Object类 sleep是来自Thread类
2、关于锁的释放 wait会释放锁,sleep睡觉了,抱着锁睡觉,不会放开的
3、使用的范围不同 wait必须在同步代码块中使用,sleep可以在任何地方睡
4、是否需要捕获异常 wait不需要捕获异常(现在好像也要捕获),sleep必须捕获异常
3、Lock锁(重点)
传统synchronized:(自动挡)
synchronized //本质:队列,锁(就好比学校食堂排队打饭,前面一个打完饭之后离开就服务下一个打饭的同学)
Lock接口:(手动挡)
/*** Lock三部曲* 1、new 一个 lock 出来* 2、加锁 lock.lock()* 3、finally=》 解锁 lock.unlock()*/
class Ticket2 {//属性private int number = 50;//new 一个Lock lock = new ReentrantLock();public void sale (){lock.lock();//加锁try {//业务代码}catch (Exception e){e.printStackTrace();}finally {lock.unlock();//解锁}if (number>0){System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,剩余"+number);}}
**synchronized ** 和 lock 的区别
1、Synchronized内置的java关键字,lock是一个java类
2、Synchronized无法捕获锁的状态,lock可以判断是否获取到了锁
3、Synchronized会自动释放锁,lock锁必须要手动释放锁,如果不释放锁,就会产生死锁的情况
4、Synchronized 线程1(获得锁,然后被阻塞了)、紧接着线程2(只有等待了,会一直傻傻的等待线程1释放);Lock锁不一定会一直等待下去
5、Synchronized 可重入锁,不可以中断的,非公平的;Lock,可重入锁,可以判断锁,非公平(可以自己设置)
6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码(lock锁的灵活度非常高)
4、生产者和消费者问题
面试的:单例模式代码手写,八大排序算法手写,线程生产者和消费者代码手写,死锁问题
生产者消费者的Synchronized版:
package com.hejign.demo01.pc;/*** 线程之间的通信问题,生产者和消费者的问题,这里就涉及到了一个叫做 等待唤醒 和 通知唤醒 的东西* 线程交替执行 A B 操作同一个变量 num=0* A num+1* B num-1*/ public class A {public static void main(String[] args) {//这里就是线程操作资源类了,并发,多个线程操作同一个资源,这里开了两个线程,然后同时操作Date类资源Data data = new Data();new Thread(()->{ for (int i=0;i<10;i++){ data.increment(); } },"A").start();new Thread(()->{ for (int i=0;i<10;i++){ data.decrement(); } },"B").start();} } //1、判断是否等待,2、处理业务 3、进行通知 class Data{//数字,资源类,线程操作资源类private int number = 0;//+1public synchronized void increment(){if (number!=0){//等待try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}number++;//这个时候就需要通知其他线程我加一完毕了System.out.println(Thread.currentThread().getName()+"=>"+number);this.notifyAll();}//-1public synchronized void decrement(){if (number==0){//这里也是需要等待的操作try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}number--;//这里就需要通知其他线程我减一完毕了System.out.println(Thread.currentThread().getName()+"=>"+number);this.notifyAll();} }
想一想:如果同时存在A、B、C、D四个线程会出现什么问题(怎样解决)
解决方案:if判断换成while判断
package com.hejign.demo01.pc;/*** 线程之间的通信问题,生产者和消费者的问题,这里就涉及到了一个叫做 等待唤醒 和 通知唤醒 的东西* 线程交替执行 A B 操作同一个变量 num=0* A num+1* B num-1*/ public class A {public static void main(String[] args) {//这里就是线程操作资源类了,并发,多个线程操作同一个资源,这里开了两个线程,然后同时操作Date类资源Data data = new Data();new Thread(()->{ for (int i=0;i<10;i++){ data.increment(); } },"A").start();new Thread(()->{ for (int i=0;i<10;i++){ data.decrement(); } },"B").start();new Thread(()->{ for (int i=0;i<10;i++){ data.increment(); } },"C").start();new Thread(()->{ for (int i=0;i<10;i++){ data.decrement(); } },"D").start();} } //1、判断是否等待,2、处理业务 3、进行通知 class Data{//数字,资源类,线程操作资源类private int number = 0;//+1public synchronized void increment(){while (number!=0){//等待try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}number++;//这个时候就需要通知其他线程我加一完毕了System.out.println(Thread.currentThread().getName()+"=>"+number);this.notifyAll();}//-1public synchronized void decrement(){while (number==0){//这里也是需要等待的操作try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}number--;//这里就需要通知其他线程我减一完毕了System.out.println(Thread.currentThread().getName()+"=>"+number);this.notifyAll();} }
生产者和消费者的JUC版:
我们通过Lock可以找到我们的一个Condition方法
代码实现:
package com.hejign.demo01.pc;import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;public class B {public static void main(String[] args) {//这里就是线程操作资源类了,并发,多个线程操作同一个资源,这里开了两个线程,然后同时操作Date类资源Data data2 = new Data();new Thread(()->{ for (int i=0;i<10;i++){ data2.increment(); } },"A").start();new Thread(()->{ for (int i=0;i<10;i++){ data2.decrement(); } },"B").start();new Thread(()->{ for (int i=0;i<10;i++){ data2.increment(); } },"C").start();new Thread(()->{ for (int i=0;i<10;i++){ data2.decrement(); } },"D").start(); } } //1、判断是否等待,2、处理业务 3、进行通知 class Data2{//数字,资源类,线程操作资源类 private int number = 0; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); //condition.await();//等待 //condition.signalAll();//唤醒全部 //+1 public void increment(){lock.lock();//加锁try {while (number!=0){try {condition.await();//等待} catch (InterruptedException e) {e.printStackTrace();}}number++;//这个时候就需要通知其他线程我加一完毕了System.out.println(Thread.currentThread().getName()+"=>"+number);condition.signalAll();//唤醒全部}catch (Exception e){e.printStackTrace();}finally {lock.unlock();//释放锁} } //-1 public void decrement(){lock.lock();try {while (number==0){//这里也是需要等待的操作try {condition.await();//等待} catch (InterruptedException e) {e.printStackTrace();}}number--;//这里就需要通知其他线程我减一完毕了System.out.println(Thread.currentThread().getName()+"=>"+number);condition.signalAll();//唤醒全部}catch (Exception e){e.printStackTrace();}finally {lock.unlock();} } }
任何一个新技术,绝对不是仅仅覆盖了原来的技术,一定会有优势和补充
那么这样使用则会出现一个问题,线程访问时随机的,不是自己想要的顺序结果进行运行的,那么如何解决这个问题呢?(如下)
Condition精准的通知和唤醒线程:
代码实现:
package com.hejign.demo01.pc;import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;/*** @hejing*/ public class C {public static void main(String[] args) {Data3 data = new Data3();//要求实现,A执行完通知B,B执行完通知C,C执行完调用Anew Thread(()->{for (int i=1;i<=10;i++)data.printA();},"A").start();new Thread(()->{for (int i=1;i<=10;i++)data.printB();},"B").start();new Thread(()->{for (int i=1;i<=10;i++)data.printC();},"C").start();} } class Data3{//资源类,Lockprivate Lock lock = new ReentrantLock();private Condition condition1 = lock.newCondition();private Condition condition2 = lock.newCondition();private Condition condition3 = lock.newCondition();private int number = 1;public void printA(){lock.lock();try {//业务代码while (number!=1){//等待condition1.await();}System.out.println(Thread.currentThread().getName()+"=>AAAAAAAAA");//这里需要唤醒指定Bnumber = 2;condition2.signal();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}public void printB(){lock.lock();try {//业务代码while (number!=2){condition2.await();}System.out.println(Thread.currentThread().getName()+"=》BBBBBBB");number=3;condition3.signal();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}public void printC(){lock.lock();try {//业务代码while (number!=3){condition3.await();}System.out.println(Thread.currentThread().getName()+"=》CCCCC");number=1;condition1.signal();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}} }
5、8锁现象
如何判断锁的是谁?永远的知道什么是锁,,锁到底锁的是谁
对象,Class 这两
8锁现象就是要我们的锁
package com.hejign.demo01.lock8; import java.util.concurrent.TimeUnit; /*** 8锁,就是关于锁的8个问题* 1、标准情况下,两个线程先打印发短信还是打电话? 结果:1、发短信 2、打电话* 为什么会一直是这种结果呢:这个不是谁先被调用的问题,面试不要这样回答,每次运行都出现先发短信然后在出现打电话,这其实和* 锁有关系,synchronized关键字,去锁的对象是方法的调用者,谁调用的这个方法,谁就被锁住了,要先调用的方法的锁被释放了,* 才能调用下一个方法;* 2、sendSms方法延迟4秒,两个线程先打印 发短信 还是打电话? 结果:1、发短信 2、打电话**/ public class Test1 {public static void main(String[] args) throws InterruptedException {Phone phone = new Phone();new Thread(()->{try {phone.sendSms();} catch (InterruptedException e) {e.printStackTrace();}},"A").start();TimeUnit.SECONDS.sleep(1);new Thread(()->{phone.call();},"B").start();} } class Phone{//synchronized关键字,去锁的对象是方法的调用者,谁调用的这个方法,谁就被锁住了//这里的两个方法其实都是用的同一个锁public synchronized void sendSms() throws InterruptedException {TimeUnit.SECONDS.sleep(1);System.out.println("发短信");}public synchronized void call(){System.out.println("打电话");} }
package com.hejign.demo01.lock8; import java.util.concurrent.TimeUnit; /*** 3、增加一个普通方法后,不给它加锁是先出现发短信还是先出现hello 普通方法先执行* 原因是普通方法没有加锁,不是同步方法,不受锁的限制* 4、两个对象,两个同步方法,是先执行发短信还是先执行打电话 答案是先打电话 再发短信* 原因是两个对象,因为锁的对象是方法的调用者,所以这里有两个调用者,产生了两把锁,* 这里就要看睡眠时间了,发短信睡了4秒,打电话没有睡,所以是先打电话*/ public class Test2 {public static void main(String[] args) throws InterruptedException {//两个对象,因为锁的对象是方法的调用者,所以这里有两个调用者,产生了两把锁Phone2 phone1 = new Phone2();Phone2 phone2 = new Phone2();new Thread(()->{try {phone1.sendSms();} catch (InterruptedException e) {e.printStackTrace();}},"A").start();TimeUnit.SECONDS.sleep(1);new Thread(()->{phone2.call();},"C").start();} } class Phone2{//synchronized关键字,去锁的对象是方法的调用者,谁调用的这个方法,谁就被锁住了//这里的两个方法其实都是用的同一个锁public synchronized void sendSms() throws InterruptedException {TimeUnit.SECONDS.sleep(4);System.out.println("发短信");}public synchronized void call(){System.out.println("打电话");}//这里没有加锁!不是同步方法,不受锁的影响public void hello(){System.out.println("hello");} }
package com.hejign.demo01.lock8; import java.util.concurrent.TimeUnit; /*** 5、增加两个静态的同步方法,只有一个对象,先打印 发短信 还是打电话 答案是发短信,再打电话* 如果只回答是因为添加了synchronized是不行的,注意这里加了一个static* 静态方法,类一加载就有了,class模板,锁的是一个class* 6、两个对象,增加两个静态的同步方法,先打印 发短信 还是打电话 答案是先发短信,再打电话*/ public class Test3 {public static void main(String[] args) throws InterruptedException {//两个对象的class类模板只有一个,static,锁的是classPhone3 phone1 = new Phone3();Phone3 phone2 = new Phone3();new Thread(()->{try {phone1.sendSms();} catch (InterruptedException e) {e.printStackTrace();}},"A").start();TimeUnit.SECONDS.sleep(1);new Thread(()->{phone2.call();},"B").start();} } class Phone3{//如果只回答是因为添加了synchronized是不行的,注意这里加了一个static//静态方法,类一加载就有了,class模板,锁的是一个classpublic static synchronized void sendSms() throws InterruptedException {TimeUnit.SECONDS.sleep(4);System.out.println("发短信");}public static synchronized void call(){System.out.println("打电话");} }
package com.hejign.demo01.lock8; import java.util.concurrent.TimeUnit; /***7、一个静态的同步方法,一个普通的同步方法,一个对象,先打印发短信还是打电话 答案:先打电话,再发短信* 8、一个静态同步方法,一个普通的同步方法,两个对象,先打印发短信还是打电话 答案:先打电话,再发短信*/ public class Test4 {public static void main(String[] args) throws InterruptedException {//两个对象的class类模板只有一个,static,锁的是classPhone4 phone1 = new Phone4();Phone4 phone2 = new Phone4();new Thread(()->{try {phone1.sendSms();} catch (InterruptedException e) {e.printStackTrace();}},"A").start();TimeUnit.SECONDS.sleep(1);new Thread(()->{phone2.call();},"B").start();} } class Phone4{//如果只回答是因为添加了synchronized是不行的,注意这里加了一个static//静态方法,类一加载就有了,class模板,锁的是一个classpublic static synchronized void sendSms() throws InterruptedException {TimeUnit.SECONDS.sleep(4);System.out.println("发短信");}public synchronized void call(){System.out.println("打电话");} }
6、集合类不安全
List不安全
package com.hejign.demo01.unsafe;import java.util.*; import java.util.concurrent.CopyOnWriteArrayList;/*** ConcurrentModificationException 并发修改异常*/ public class ListTest {public static void main(String[] args) {//并发下ArrayList 是不安全的/*** 解决方案:* 1、List<String> list = Vector<>();* 2、List<String> list = Collections.synchronizedList(new ArrayList<>());* List<String> list = new CopyOnWriteArrayList<>();* 写入时复制,COW 计算机程序设计领域的一种优化策略* 多个线程调用的时候,list,读取的时候,固定的,写入(覆盖)* 在写入的时候避免覆盖造成数据问题* 读写分离* CopyOnWriteArrayList 比 Vector 牛逼在哪里*/List<String> list = new CopyOnWriteArrayList<>();for (int i =1;i<=40;i++){new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,5));System.out.println(list);},String.valueOf(i)).start();}} }
Set不安全
package com.hejign.demo01.unsafe;import java.util.*; import java.util.concurrent.CopyOnWriteArraySet;/*** 同理,在多线程环境下出现了ConcurrentModificationException 线程修改异常*/ public class SetTest {public static void main(String[] args) {/*** 解决方案:* 1、Set<String> set = Collections.synchronizedSet(new HashSet<>());* 2、Set<String> set = new CopyOnWriteArraySet<>();*/Set<String> set = new CopyOnWriteArraySet<>();for (int i =0;i<=20;i++){new Thread(()->{set.add(UUID.randomUUID().toString().substring(0,5));System.out.println(set);},String.valueOf(i)).start();}} }
这里插一脚:
HashSet的底层是什么?没错,就是HashMap()
Map不安全
package com.hejign.demo01.unsafe;import java.util.HashMap; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap;public class MapTest {public static void main(String[] args) {//map是这样用的吗?不是 工作中不用HashMap//默认等价于什么//Map<String, Object> map = new HashMap<>();Map<String, Object> map = new ConcurrentHashMap<>();for (int i=1;i<=50;i++){new Thread(()->{map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));System.out.println(map);},String.valueOf(i)).start();}} }
7、Callable
a、可以有返回值
b、可以抛出异常
package com.hejign.demo01.callabel;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** @hejing*/
public class CallableTest {public static void main(String[] args) throws ExecutionException, InterruptedException {//new Thread(new Runnable()).start()//new Thread(new FutureTask<V>()).start();//new Thread(new FutureTask<V>(Callable)).start();new Thread().start();//怎么启动Callable呢?MyThread myThread = new MyThread();FutureTask futureTask = new FutureTask(myThread);new Thread(futureTask,"A").start();new Thread(futureTask,"B").start();//结果会被缓存效率高Object o = futureTask.get();//获取Callable的返回结果System.out.println(o.toString());}
}
class MyThread implements Callable<String> {@Overridepublic String call() throws Exception {return "123456";}
}
注意细节:
1、有缓存
2、可能会需要等待,会阻塞
8、常用的辅助类
8.1、CountDownLatch(减法计数器)
看代码:
package com.hejign.demo01.add;import java.util.concurrent.CountDownLatch;//计数器
public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {//总数是6,在某些必须要执行某个任务的时候去使用CountDownLatch countDownLatch = new CountDownLatch(6);for (int i=1;i<=6;i++){new Thread(()->{System.out.println(Thread.currentThread().getName()+" Go out");countDownLatch.countDown();//数量减一},String.valueOf(i)).start();}countDownLatch.await();//等待计数器归零,然后再向下执行System.out.println("Close Door");}
}
知原理:
主要两个方法: countDownLatch.countDown();//数量减一 countDownLatch.await();//等待计数器归零,然后再向下执行
每次有线程调用 countDown数量减一,假设计数器变为0,await方法就会被唤醒
8.2、CyclicBarrier(加法计数器)
看代码:
package com.hejign.demo01.add;import java.util.concurrent.CyclicBarrier;/*** 加法计数器*/
public class CyclicBarrierDemo {public static void main(String[] args) {//集齐7颗龙珠召唤神龙//召唤龙珠的线程CyclicBarrier barrier = new CyclicBarrier(7, () -> {System.out.println("召唤神龙成功");});for (int i =1;i<=7;i++){int finalI = i;new Thread(()->{System.out.println(Thread.currentThread().getName()+"收集了"+ finalI +"颗龙珠");},String.valueOf(i)).start();}}
}
8.3、Semaphore(信号量)
看代码:
package com.hejign.demo01.add;import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;/*** 信号量*/
public class SemaphoreDemo {public static void main(String[] args) {//线程数量:停车位,具体使用场景,限流的时候可以使用Semaphore semaphore = new Semaphore(3);for (int i = 1;i<=6;i++){new Thread(()->{//得到acquire()//释放release()try {semaphore.acquire();System.out.println(Thread.currentThread().getName()+"抢到车位");TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName()+"离开车位");} catch (InterruptedException e) {e.printStackTrace();}finally {semaphore.release();}},String.valueOf(i)).start();}}
}
知原理:
semaphore.acquire(); 获得,假设结果已经满了,等待,等待被释放为止
semaphore.release(); 释放会将当前的信号量释放+1 ,然后唤醒等待的线程
作用:多个共享资源互斥的使用,并发限流,控制最大的线程数
9、读写锁
ReadWriteLock
package com.hejign.demo01.rw;import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;/*** 读写锁* 写锁(独占锁) 一次只能被一个线程占有* 读锁(共享锁) 多个线程可以同时占有* ReadWriteLock* 1、读和读 可以共存* 2、读和写 不能共存,读可以多次,写只能一个一个的,* 3、写和写 不能共存*/
public class ReadWriteLockDemo {public static void main(String[] args) {MyCacheLock myCache = new MyCacheLock();//写入for (int i=1;i<=5;i++){final int temp = i;new Thread(()->{myCache.put(temp+"",temp+"");},String.valueOf(i)).start();}//读取for (int i = 1;i<=5;i++){final int temp = i;new Thread(()->{myCache.get(temp+"");},String.valueOf(i)).start();}}
}
/*** 这是加锁的*/
class MyCacheLock{private volatile Map<String,Object> map = new HashMap<>();//加入读写锁,更加细粒度的控制private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();//存,写,写入的时候只希望同时只有一个线程往这里面写public void put(String key,Object value){readWriteLock.writeLock().lock();try {System.out.println(Thread.currentThread().getName()+"写入"+key);map.put(key,value);System.out.println(Thread.currentThread().getName()+"写入完毕");}catch (Exception e){e.printStackTrace();}finally {readWriteLock.writeLock().unlock();}}//取,读,所有的线程都可以读public void get(String key){readWriteLock.readLock().lock();try {System.out.println(Thread.currentThread().getName()+"读取"+key);Object o = map.get(key);System.out.println(Thread.currentThread().getName()+"读取完毕");} catch (Exception e) {e.printStackTrace();} finally {readWriteLock.readLock().unlock();}}
}
/*** 自定义缓存(这是不加锁的)*/
class MyCache{private volatile Map<String,Object> map = new HashMap<>();//存,写public void put(String key,Object value){System.out.println(Thread.currentThread().getName()+"写入"+key);map.put(key,value);System.out.println(Thread.currentThread().getName()+"写入完毕");}//取,读public void get(String key){System.out.println(Thread.currentThread().getName()+"读取"+key);Object o = map.get(key);System.out.println(Thread.currentThread().getName()+"读取完毕");}
}
10、阻塞队列
队列 ----------- 阻塞
队列特性:先进先出;
把队列想象成一个加工厂流水线
写入(生产):如果队列满了(工厂流水线满了),就必须阻塞等待消耗产品;
读取(消耗):如果队列是空的(工厂流水线空的,没有产品生产出来),就必须阻塞等待生产(囤货)
阻塞队列:(BlockingQueue)
BlockingQueue不是一个新的东西,它和ArrayList,Set同级,父类都是Collections
那么什么情况下我们要使用阻塞队列(BlockingQueue)呢
多线程并发处理,线程池
学会使用队列:
无非就是两个操作:添加,移除
四组API
方式 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer(参数,参数,参数) |
移除 | remove() | poll() | take() | poll(参数,参数) |
取出队列首元素 | element() | peek() |
1、抛出异常
package com.hejign.demo01.bq;import java.util.concurrent.ArrayBlockingQueue;public class Test {public static void main(String[] args) {test1();}/*** 抛出异常*/public static void test1(){//队列的大小ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);System.out.println(arrayBlockingQueue.add("a"));System.out.println(arrayBlockingQueue.add("b"));System.out.println(arrayBlockingQueue.add("c"));System.out.println(arrayBlockingQueue.element());//查看队首元素是谁 //在这里如果规定了3个大小,如果再去添加就会出现异常IllegalStateException//System.out.println(arrayBlockingQueue.add("c"));System.out.println(arrayBlockingQueue.remove());System.out.println(arrayBlockingQueue.remove());System.out.println(arrayBlockingQueue.remove());//这里如果队列中已经移除完了,但是又要去执行移除操作,也会抛出异常System.out.println(arrayBlockingQueue.remove());}
}
2、不会抛出异常
package com.hejign.demo01.bq;import java.util.concurrent.ArrayBlockingQueue;public class Test {public static void main(String[] args) {test2();}/*** 不抛出异常*/public static void test2(){//队列的大小ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);System.out.println(arrayBlockingQueue.offer("a"));System.out.println(arrayBlockingQueue.offer("b"));System.out.println(arrayBlockingQueue.offer("c"));System.out.println(arrayBlockingQueue.offer("d"));//有返回值,false 这种方法就不抛出异常System.out.println(arrayBlockingQueue.peek());//取出队首元素//像这种可以根据自己的业务需求来确定是否使用功能抛出异常的方法还是使用有返回值不抛出异常的方法System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll());System.out.println(arrayBlockingQueue.poll());//null 不抛出异常}
}
3、阻塞等待
package com.hejign.demo01.bq;import java.util.concurrent.ArrayBlockingQueue;public class Test {public static void main(String[] args) throws InterruptedException {test3();}/*** 等待,阻塞(一直阻塞)*/public static void test3() throws InterruptedException {//队列的大小ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);//一直阻塞blockingQueue.put("a");blockingQueue.put("b");blockingQueue.put("c");//blockingQueue.put("c");//队列里面没有位置了,这个时候会一直阻塞System.out.println(blockingQueue.take());System.out.println(blockingQueue.take());System.out.println(blockingQueue.take());}
}
4、超时等待
package com.hejign.demo01.bq;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;public class Test {public static void main(String[] args) throws InterruptedException {test3();}/*** 等待,阻塞(等待超时)*/public static void test4() throws InterruptedException {//队列的大小ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);arrayBlockingQueue.offer("a");arrayBlockingQueue.offer("b");arrayBlockingQueue.offer("c");arrayBlockingQueue.offer("d",2, TimeUnit.SECONDS);//假设还要往里面加入d元素,等待超过2秒就退出System.out.println("============");arrayBlockingQueue.poll();arrayBlockingQueue.poll();arrayBlockingQueue.poll();arrayBlockingQueue.poll(2,TimeUnit.SECONDS);//等待超时2秒就退出}
}
同步队列(SynchronousQueue )
没有容量,进去一个元素,必须等待取出来之后, 才能再往里面放一个元素!
package com.hejign.demo01.bq;import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;/*** 同步队列* 和其他的BlocingQueue 不一样,SynchronousQueue 不存储元素* put了一个元素,必须从里面先take取出来,否则不能再put进去值!*/
public class SynchronousQueueDemo {public static void main(String[] args) {SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();new Thread(()->{try {System.out.println(Thread.currentThread().getName()+"put 1");synchronousQueue.put("1");System.out.println(Thread.currentThread().getName()+"put 2");synchronousQueue.put("2");System.out.println(Thread.currentThread().getName()+"put 3");synchronousQueue.put("3");} catch (InterruptedException e) {e.printStackTrace();}},"T1").start();new Thread(()->{try {TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName()+synchronousQueue.take());TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName()+synchronousQueue.take());TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName()+synchronousQueue.take());} catch (InterruptedException e) {e.printStackTrace();}},"T2").start();}
}
11、线程池(重点)
线程池:三大方法,7大参数,4种拒绝策略
池化技术:程序运行的本质,占用系统的资源,那么就要去优化资源的使用 于是就出现了一种技术 ==》 池化技术
比如:线程池,我们的jdbc数据库连接池,内存池,对象池 等等。。。。。 创建和销毁是十分浪费资源的
概念:事先准备好一些资源,有人要用,就来我这里拿,用完之后再还给我
线程池的好处;
1、降低资源的消耗
2、提高响应的速度
3、方便管理
线程复用,可以控制最大并发数,管理线程
线程池三大方法:
package com.hejign.demo01.pool;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/*** Executors 看成一个工具类,里面有三大方法* 使用了线程池之后,要学会使用线程池来创建线程*/
public class Demo01 {public static void main(String[] args) {ExecutorService threadExecutor = Executors.newSingleThreadExecutor();//单个线程/* Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小Executors.newCachedThreadPool();//可伸缩的,遇强则强,遇弱则弱*/try {for (int i=1;i<=10;i++){threadExecutor.execute(()->{System.out.println(Thread.currentThread().getName()+"ok");});}} catch (Exception e) {e.printStackTrace();} finally {//注意,线程池使用结束之后,是需要关闭线程池的threadExecutor.shutdown();}}
}
线程池的7大参数:
先分析一下源码:
/**
* 单个线程方法
*/
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}
/**
* 创建一个固定的线程池的大小方法
*/
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}
/**
* 可伸缩的,遇强则强,遇弱则弱方法
*/
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}//本质:都去new了一个 ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小int maximumPoolSize, //最大核心线程池大小long keepAliveTime,//超时了,没有人调用就会释放TimeUnit unit, //超时时间单位BlockingQueue<Runnable> workQueue, //阻塞队列ThreadFactory threadFactory, //创建线程工厂,一般不用动RejectedExecutionHandler handler) { //拒绝策略if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.acc = System.getSecurityManager() == null ?null :AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}
手动创建一个线程池:
package com.hejign.demo01.pool;import java.util.concurrent.*;/***手动创建线程池*/
public class Demo01 {public static void main(String[] args) {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, //核心线程池大小5, //最大承载线程池大小3, //超时等待时间TimeUnit.SECONDS, //时间单位new LinkedBlockingDeque<>(3),//队列等待区Executors.defaultThreadFactory(), //默认new ThreadPoolExecutor.AbortPolicy()//默认的拒绝策略,等待区满了,还有人进来,就不处理这个人的了,抛出异常);try {//最大承载:Deque + max//超出最大承载 就会抛出异常RejectedExecutionExceptionfor (int i=1;i<=10;i++){threadPoolExecutor.execute(()->{System.out.println(Thread.currentThread().getName()+"ok");});}} catch (Exception e) {e.printStackTrace();} finally {//注意,线程池使用结束之后,是需要关闭线程池的threadPoolExecutor.shutdown();}}
}
线程池四大拒绝策略:
1、new ThreadPoolExecutor.AbortPolicy()//默认的拒绝策略
2、new ThreadPoolExecutor.CallerRunsPolicy() //哪里来的去哪里
3、new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,就会丢掉任务,但是不会抛出异常
4、new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试和最早的竞争,也不会抛出异常
小结和拓展:
最大承载线程池大小这个参数到底该怎样去设置?(调优)
解决方案:
1、cpu密集型 ,看我们的服务器是几核的,4核的就设置为4,可以保持cpu的效率最高
根据代码去获取cpu核数:(不能写死哦,服务器上的核心数和本地电脑的核心数是不一样的,所以要根据代码去获取)
public class Demo01 {public static void main(String[] args) {//获取CPU的核心数Runtime.getRunTime().availableProcessors()} }
2、io密集型
比如说:一个程序中有15个大型任务,io十分占用资源
判断程序中十分占用资源的线程,然后设置比它大的承载数
12、四大函数式接口(必须掌握)
新时代的程序员:lambda表达式,链式编程,函数式接口,Stream流式计算
函数式接口是个什么东西:
概念:只用一个方法的接口,即为函数式接口(有且只有一个方法)
@FunctionalInterface public interface Runnable {public abstract void run(); }
java中超级多的FunctionalInterface
它能简化编程模型,在新版本的框架底层中大量应用
foreach(消费者类型的函数式接口)
四大函数式接口:
Function 函数式接口
package com.hejign.demo01.function; import java.util.function.Function; /*** Function 函数型接口* 只要是函数式接口,我们就可以使用lambda 表达式简化*/ public class demo01 {public static void main(String[] args) {/* Function function = new Function<String,String>(){@Overridepublic String apply(String s) {return s;}};*/Function function1 = (s)->{return s;};Object afgadsfds = function1.apply("afgadsfds");} }
Consumer 消费型接口
package com.hejign.demo01.function;import java.util.function.Consumer;/*** Consumer 消费型接口,只有输入,没有返回,只做消费*/ public class Demo03 {public static void main(String[] args) {/* Consumer<String> consumer = new Consumer<String>() {@Overridepublic void accept(String str) {System.out.printf(str);}};*/Consumer<String> consumer = (str)->{System.out.println(str);};consumer.accept("hejing");} }
Predicate 断定型接口
package com.hejign.demo01.function;import java.util.function.Predicate;/*** 断定型接口,有一个输入参数,返回值只能是布尔值*/ public class Demo02 {public static void main(String[] args) {//判断字符串是否为空/* Predicate<String> predicate = new Predicate<String>() {@Overridepublic boolean test(String s) {return s.isEmpty();}};*/Predicate<String> predicate = (str)->{return str.isEmpty();};System.out.println(predicate.test("123"));} }
Supplier 供给型接口
package com.hejign.demo01.function;import java.util.function.Supplier;/***Supplier 供给型函数,没有参数,只有返回值*/ public class Demo04 {public static void main(String[] args) {/* Supplier<Integer> supplier = new Supplier<Integer>() {@Overridepublic Integer get() {return 1024;}};*/Supplier<Integer> supplier = ()->{return 1024;};System.out.println(supplier.get());} }
13、Stream流式计算
什么是Stream流式计算?
大数据:存储+计算
mysql ,集合 的本质就是用来 存储 的
计算都应该交给流来计算
package com.hejign.demo01.stream;import java.util.Arrays;
import java.util.List;
import java.util.Locale;/*** 题目要求:一分钟内完成此题,只能用一行代码实现* 现在有5个用户,筛选* 1、ID必须是偶数* 2、年龄必须大于23岁* 3、用户名转为大写字母* 4、用户名字母倒着排序* 5、只输出一个用户*/
public class Test {public static void main(String[] args) {User u1 = new User(1, "a", 21);User u2 = new User(2, "b", 22);User u3 = new User(3, "c", 23);User u4 = new User(4, "d", 24);User u5= new User(5, "e", 25);//集合就是存储数据的List<User> list = Arrays.asList(u1, u2, u3, u4, u5);//而计算数据则是要交给流来进行,Stream流,流式计算//lambda表达式,链式编程、函数式接口、stream流式计算list.stream().filter(u->{return u.getId()%2==0;}).filter(u->{return u.getAge()>23;}).map(u->{return u.getName().toUpperCase();}).sorted((uu1,uu2)->{return uu1.compareTo(uu2);}).limit(1).forEach(System.out::println);}
}
class User {int id;String name;int age;public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public User(int id, String name, int age) {this.id = id;this.name = name;this.age = age;}
}
14、ForkJoin
什么 ForkJoin?(分支合并)
ForkJoin在jdk 1.7,并行执行任务!,提高效率,大数据量!
大数据量:Map Reduce(把大任务拆分为小任务)
ForkJion有一个特点:工作窃取
这个里面维护的都是双端队列
看代码:
package com.hejign.demo01.forkJoin;import java.util.concurrent.RecursiveAction;
import java.util.concurrent.RecursiveTask;/*** 求和计算的任务* ForkJoin使用或者Stream并行流使用* 如何使用ForkJoin* 1、ForkJoinPool 通过它来执行* 2、计算任务ForkJoinPool.execute(ForkJoinTask<?> task)**/
public class ForkJoinDemo extends RecursiveTask<Long> {private Long start;private Long end;//临界值private Long temp = 10000L;public ForkJoinDemo(Long start, Long end) {this.start = start;this.end = end;}//计算方法@Overrideprotected Long compute() {if((end-start)<temp){long sum = 0L;for (Long i = start; i<=end; i++){sum+=i;}return sum;}else {//分支合并计算 forkjoinlong middle = (start + end) / 2; //中间值ForkJoinDemo task1 = new ForkJoinDemo(start, middle);task1.fork();//拆分任务,把任务压入线程队列ForkJoinDemo task2 = new ForkJoinDemo(middle+1,end);task2.fork();//拆分任务,把任务压入线程队列return task1.join() + task2.join();}}
}
package com.hejign.demo01.forkJoin;import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;public class Test {public static void main(String[] args) throws ExecutionException, InterruptedException {test1();}//普通程序员public static void test1(){long sum = 0L;long start = System.currentTimeMillis();for (long i = 1L; i<=10_0000_0000L; i++){sum+=i;}long end = System.currentTimeMillis();System.out.println("sum="+sum+"时间"+(end-start));}//进阶程序员public static void test2() throws ExecutionException, InterruptedException {long start = System.currentTimeMillis();ForkJoinPool forkJoinPool = new ForkJoinPool();ForkJoinDemo forkJoinDemo = new ForkJoinDemo(0L, 10_0000_0000L);//forkJoinPool.execute(forkJoinDemo);//执行任务,没有结果ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinDemo);//提交任务,有结果Long sum = submit.get();long end = System.currentTimeMillis();System.out.println("sum="+sum+"时间"+(end-start));}//高级程序员public static void test3(){long start = System.currentTimeMillis();//Stream并行流long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);long end = System.currentTimeMillis();System.out.println("sum="+sum+"时间"+(end-start));}
}
15、异步回调
Future 设计的初衷,对将来进行的某个时间进行建模
package com.hejign.demo01.future;import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;/*** 异步调用 最开始用的时候是ajax* 异步执行* 成功回调* 失败回调*/
public class demo01 {public static void main(String[] args) throws ExecutionException, InterruptedException {//发起一个请求//没有返回值的runAsync 异步回调/*CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"runAsync=>Void");});System.out.println("1111");completableFuture.get();//获取阻塞执行结果*///有返回值的异步回调 supplyAsync//ajax 通常有个SUCCESS and FailCompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {System.out.println(Thread.currentThread().getName()+"supplyAsync=>Integer");return 1024;});completableFuture.whenComplete((t,u)->{System.out.println("t"+t+" u"+u);}).exceptionally((e)->{System.out.println(e.getMessage());return 233;}).get();}
}
16、JMM
面试的时候可能会问:请你谈谈对Volatile的理解?
Volatile是java虚拟机提供轻量级的同步机制
1、保证可见性
2、不保证原子性
3、禁止指令重排
问题:那这个时候面试官就会问,怎样保证可见性,这个是后就需要去了解什么是JMM?
JMM java内存模型,是一种不存在的东西,是一个概念,约定
关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存
2、线程加锁钱,必须读取主存中的最新值到工作内存中!
3、加锁和解锁必须是同一把锁。
线程:工作内存、主内存
八种操作:
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
- lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
- unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
- assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
- write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
代码:
package com.hejign.demo01.tvolatile;import java.util.concurrent.TimeUnit;public class JmmDemo {private static int num =0;public static void main(String[] args) { //main线程new Thread(()->{while (num==0){}}).start();try{TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e){e.printStackTrace();}num = 1;System.out.println(num);}
}
问题:
程序不知道主内存中的值已经被修改过了
17、Volatile
1、保证可见性
package com.hejign.demo01.tvolatile;import java.util.concurrent.TimeUnit;public class JmmDemo {//不加volatile程序就会死循环!//加上volatile可以保证可见性private volatile static int num =0;public static void main(String[] args) { //main线程new Thread(()->{while (num==0){}}).start();try{TimeUnit.SECONDS.sleep(1);}catch (InterruptedException e){e.printStackTrace();}num = 1;System.out.println(num);}
}
2、保证原子性
原子性:不可分割
比如:线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败
package com.hejign.demo01.tvolatile;/*** volatile是不保证原子性的*/
public class VDemo02 {private volatile static int num = 0;public static void add(){num++;}public static void main(String[] args) {for (int i =1; i <= 20;i++){new Thread(()->{for (int j =1;j<=1000;j++){add();}}).start();}while (Thread.activeCount()>2){Thread.yield();}System.out.println(Thread.currentThread().getName()+" "+num);}
}
如果不加lock 和 synchronized,怎么样保证原子性
package com.hejign.demo01.tvolatile;import java.util.concurrent.atomic.AtomicInteger;/*** volatile是不保证原子性的*/
public class VDemo02 {//使用原子类的Integerprivate volatile static AtomicInteger num = new AtomicInteger();public static void add(){//num++;//不是一个原子性操作//AtomicInteger有专门的方法进行++操作num.getAndIncrement();}public static void main(String[] args) {for (int i =1; i <= 20;i++){new Thread(()->{for (int j =1;j<=1000;j++){add();}}).start();}while (Thread.activeCount()>2){Thread.yield();}System.out.println(Thread.currentThread().getName()+" "+num);}
}
这些类的底层都直接和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在
3、指令重排
什么是指令重排:你写的程序,计算机并不是按照你写的那种顺序去执行的
源代码–>编译器优化的重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,考虑,数据之间的依赖性!
int x = 1; //1
int y = 2; //2
x = x + 5; //3
y = x * x; //4我们所期望的:1234 但是可能在执行的时候会变成 2134 1324
可不可能是 4123
可能造成影响的结果:abxy这四个默认的是0;
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常的结果:x=0,y=0
线程A | 线程B |
---|---|
b=1 | a=2 |
x=a | y=b |
指令重排操作的诡异结果:x=2,y=0
volatile可以避免指令重排
JUC并发编程(java util concurrent)(哔站 狂神说java juc并发编程 摘录笔记)相关推荐
- Java高并发编程学习(三)java.util.concurrent包
简介 我们已经学习了形成Java并发程序设计基础的底层构建块,但对于实际编程来说,应该尽可能远离底层结构.使用由并发处理的专业人士实现的较高层次的结构要方便得多.要安全得多.例如,对于许多线程问题,可 ...
- 高并发第八弹:J.U.C起航(java.util.concurrent)
java.util.concurrent是JDK自带的一个并发的包主要分为以下5部分: 并发工具类(tools) 显示锁(locks) 原子变量类(aotmic) 并发集合(collections) ...
- [转载] 多线程详解java.util.concurrent
参考链接: java.lang.Object的灵活性 一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内 ...
- java util下的并发包_jdk并发包下:使用java.util.concurrent.Executor线程池
多线程,线程池Executor的接口类图: 其他都不重要,就ExecutorService是主要的: 基本上分为单纯线程池和定时任务线程池: 说白了除了ForkJoinPool所有都继承于Thread ...
- Java并发包-java.util.concurrent详解
转载自https://blog.csdn.net/axi295309066/article/details/65665090 一.阻塞队列BlockingQueue BlockingQueue通常用于 ...
- 【ruoyi】java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.ScheduledThreadPoo
前言 ruoyi 4.6.0 jdk1.8 错误 11:48:16.879 [http-nio-9031-exec-25] INFO c.r.f.s.r.UserRealm - [doGetAuthe ...
- java.util.concurrent.TimeUnit
JavaAPI:https://docs.oracle.com/javase/8/docs/api/index.html 1 TimeUnit api:https://docs.oracle.com/ ...
- java.util.concurrent.ExecutorService 接口 源码
2019独角兽企业重金招聘Python工程师标准>>> 线程池相关 源码: package java.util.concurrent;import java.util.List; i ...
- rejected from java.util.concurrent.ThreadPoolExe错误
2019独角兽企业重金招聘Python工程师标准>>> java.util.concurrent.RejectedExecutionException: Task com.dangd ...
最新文章
- 给Resnet加人工经验提升30%的准确率
- 如何在BIOS里设置定时关机?
- iis7安装mysql_windows server 2008/2012安装php+iis7+mysql环境搭建
- Ubuntu 搜狗输入法不能输入中文解决
- 前端一HTML:八:css中与文本相关的属性
- 第四范式@2020 WAIC世界人工智能大会
- 低代码开发平台是什么
- MySQL取小数点后两位及百分比
- pyspider all 只启动了_Python 爬虫:Pyspider 安装与测试
- 用python爬虫抓站的一些技巧
- 在ubuntu用audacity把音频转换成256kbps,单声道。(亲测有效)
- 计算机组策略怎么显示音量图标,win7系统电源、网络、音量图标不见的解决方法...
- php实训心得体会doc,php实训报告心得体会php实训报告心得体会
- 上传IPA包到App Store
- 免Root卸载系统预装应用
- Java解决杨辉三角问题(这里提供了两种方法)
- mongodb数据库自动备份
- 【C语言】冒泡排序学习笔记
- vim visual block模式
- Transformer中引用iqd作为数据源的时提示TR1008无法连接问题