JUC编程
源码+官方文档

java.util 工具包
current
业务
2、线程和进程
线程、进程
- 进程:一个程序,程序的集合一个
- 进程:包含多个线程,至少包含一个
并发和并行
并发:同一时间段,多个任务都在执行。(多线程操作同一个资源)
cpu 单核 模拟多条线程,快速交替并行:单位时间内,多个任务同时执行
多核,多个线程可以同时执行
java默认有几个线程?
main线程、GC
线程:某个单个小功能就是由线程控制的。java真的可以开启线程吗?
只能通过native方法(底层的C++),不能自己开启
并发编程:充分利用CPU的资源
回顾多线程
线程状态
- new 新生
- Runnable 运行
- Blocked阻塞
- wait 等待
- time_waitting 超时等待
- terminated 终止
创建线程的方式
- 继承:Thread
- 接口: Runnable
- 接口: Callable
- 通过线程池创建对象
wait 和sleep的区别
1、来自不同的类
wait object类
sleep Thread类
2、关于锁的释放
wait会释放锁,sleep睡觉了,抱着锁睡觉,不会释放
3、使用的范围是不同的
wait 必须在同步代码块中
sleep可以在任何地方睡
4、是否需要捕获异常
wait不需要捕获异常
sleep必须要捕获异常
3、Lock锁(重点)
3.1、Synchronized(关键字,JVM)
传统synchronized
package com.zmy.demo;//基本的卖票/*多线程线程就是一个单独的资源类,没有任何的附属的操作1、属性2、方法*/public class SaleTicket {public static void main(String[] args) {//并发:多线程操作同一个资源类,把资源丢入线程final Ticket ticket = new Ticket();//@FunctionalInterface 函数式接口 jdk1.8 lamada表达式 (参数)-> { 代码 }new Thread(()->{for (int i = 1; i < 500; i++){ticket.sale();}},"a").start();new Thread(()->{for (int i = 1; i < 500; i++){ticket.sale();}},"b").start();new Thread(()->{for (int i = 1; i < 500; i++){ticket.sale();}},"c").start();}}class MyThread implements Runnable{public void run() {}}//资源类OOPclass Ticket{private int number = 500;//卖票的方式//不加synchronized,结果是乱的//synchronized本质是:队列,锁//public synchronized void sale(){if (number>0){System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余"+number);}}}
3.2、Lock(类)
Lock
Lock三部曲
- new ReentrantLock()
- lock.lock(); 加锁
- finally => lock.unlock(); 解锁


- 锁的分类:
package com.zmy.demo;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;//基本的卖票/*多线程线程就是一个单独的资源类,没有任何的附属的操作1、属性2、方法*/public class SaleTicket01 {public static void main(String[] args) {//并发:多线程操作同一个资源类,把资源丢入线程Ticket1 ticket = new Ticket1();//@FunctionalInterface 函数式接口 jdk1.8 lamada表达式 (参数)-> { 代码 }new Thread(()->{for (int i = 1; i < 500; i++){ticket.sale();}},"a").start();new Thread(()->{for (int i = 1; i < 500; i++){ticket.sale();}},"b").start();new Thread(()->{for (int i = 1; i < 500; i++){ticket.sale();}},"c").start();}}class Ticket1{//卖票的方式//Lock三部曲//1、new ReentrantLock();//2、lock.lock(); 加锁//3、finally => lock.unlock(); 解锁private int number = 500;Lock lock= new ReentrantLock();public void sale(){lock.lock();try {if (number>0){System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余"+number);};}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}}
3.3、Synchronized和lock的区别
1、Synchronized 内置的Java关键字,Lock是一个Java类
2、Synchronized 无法判断获取锁的状态,Lock可以判断
3、Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁
4、Synchronized 线程1(获得锁->阻塞)、线程2(等待);lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。
5、Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;
6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;
4. 生产者和消费者的关系
4.1、Synchronized 版本
package com.zmy.ProAndConsumer;/*** 线程之间的通信问题 生产者和消费者问题 等待唤醒,通知唤醒* 线程交替执行 A B 操作同一变量资源 num = 0* A = num + 1* B = num - 1*/public class ConsumeAndProduct {public static void main(String[] args) {Data data = new Data();new Thread(() -> {for (int i = 0; i < 10; i++) {try {data.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {data.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "B").start();}}//判断等待,业务,通知class Data { //数字 资源类private int num = 0;// +1public synchronized void increment() throws InterruptedException {// 判断等待if (num != 0) {this.wait();}num++;System.out.println(Thread.currentThread().getName() + "=>" + num);// 通知其他线程 +1 执行完毕this.notifyAll();}// -1public synchronized void decrement() throws InterruptedException {// 判断等待if (num == 0) {this.wait();}num--;System.out.println(Thread.currentThread().getName() + "=>" + num);// 通知其他线程 -1 执行完毕this.notifyAll();}}
4.2、存在问题
如果存在四个线程,会出现虚假唤醒
A=>1B=>0C=>1A=>2C=>3B=>2B=>1

解决方式 ,
if 改为while即可,防止虚假唤醒
package com.zmy.ProAndConsumer;/*** 线程之间的通信问题 生产者和消费者问题 等待唤醒,通知唤醒* 线程交替执行 A B 操作同一变量资源 num = 0* A = num + 1* B = num - 1*/public class ConsumeAndProduct {public static void main(String[] args) {Data data = new Data();new Thread(() -> {for (int i = 0; i < 10; i++) {try {data.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {data.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "C").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {data.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "B").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {data.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "D").start();}}//判断等待,业务,通知class Data { //数字 资源类private int num = 0;// +1public synchronized void increment() throws InterruptedException {// 判断等待while (num != 0) {this.wait();}num++;System.out.println(Thread.currentThread().getName() + "=>" + num);// 通知其他线程 +1 执行完毕this.notifyAll();}// -1public synchronized void decrement() throws InterruptedException {// 判断等待while (num == 0) {this.wait();}num--;System.out.println(Thread.currentThread().getName() + "=>" + num);// 通知其他线程 -1 执行完毕this.notifyAll();}}
A=>1B=>0C=>1B=>0A=>1B=>0C=>1
4.3、Lock版

package com.zmy.ProAndConsumer;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/*** 线程之间的通信问题 生产者和消费者问题 等待唤醒,通知唤醒* 线程交替执行 A B 操作同一变量资源 num = 0* A = num + 1* B = num - 1*/public class LockPAC {public static void main(String[] args) {Data1 data = new Data1();new Thread(() -> {for (int i = 0; i < 10; i++) {try {data.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "A").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {data.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "B").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {data.increment();} catch (InterruptedException e) {e.printStackTrace();}}}, "C").start();new Thread(() -> {for (int i = 0; i < 10; i++) {try {data.decrement();} catch (InterruptedException e) {e.printStackTrace();}}}, "D").start();}}//判断等待,业务,通知class Data1 { //数字 资源类private int num = 0;Lock lock = new ReentrantLock();Condition condition = lock.newCondition(); //替代同步监视器//condition.await(); //等待//condition.signalAll(); //唤醒全部// +1public void increment() throws InterruptedException {try {lock.lock();//业务代码// 判断等待while (num != 0) {condition.await();}num++;System.out.println(Thread.currentThread().getName() + "=>" + num);// 通知其他线程 +1 执行完毕condition.signalAll();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}// -1public void decrement() throws InterruptedException {try {lock.lock();//业务代码// 判断等待while (num == 0) {condition.await();}num--;System.out.println(Thread.currentThread().getName() + "=>" + num);// 通知其他线程 -1 执行完毕condition.signalAll();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}}
4.4、Condition的优势
精准的通知和唤醒线程!
指定通知的下一个进行顺序怎么办? 可以用Condition来指定通知进程
package com.zmy.ProAndConsumer;/*** A执行完调用B* B执行完调用C* C执行完调用A**/import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class Condition {public static void main(String[] args) {Data2 data2 = new Data2();new Thread(()->{for (int i = 0; i < 10;i++){try {data2.printA();} catch (Exception e) {e.printStackTrace();}}},"A").start();new Thread(()->{for (int i = 0; i < 10;i++){try {data2.printB();} catch (Exception e) {e.printStackTrace();}}},"B").start();new Thread(()->{for (int i = 0; i < 10;i++){try {data2.printC();} catch (Exception e) {e.printStackTrace();}}},"C").start();}}class Data2{ //资源类 Lockprivate Lock lock = new ReentrantLock();private java.util.concurrent.locks.Condition condition1 = lock.newCondition();private java.util.concurrent.locks.Condition condition2 = lock.newCondition();private java.util.concurrent.locks.Condition condition3 = lock.newCondition();private int number = 1; // 1A 2B 3Cpublic void printA(){try {lock.lock();//业务代码while (number != 1){//等待condition1.await();}System.out.println(Thread.currentThread().getName()+"====AAAAAA");//唤醒指定的人 Bnumber = 2;condition2.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}};public void printB(){try {lock.lock();//业务代码while (number != 2){//等待condition2.await();}System.out.println(Thread.currentThread().getName()+"====BBBB");//唤醒指定的人 Bnumber = 3;condition3.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}};public void printC(){try {lock.lock();//业务代码while (number != 3){//等待condition3.await();}System.out.println(Thread.currentThread().getName()+"====CCCCC");//唤醒指定的人 Bnumber = 1;condition1.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}};}
5、八锁现象
8锁,就是关于锁的八个问题(有延迟才和普通方法显示差别!!!)
1、标准情况下,两个线程先打印 发短信还是打电话? 1.发短信 2.打电话
2、发短信延迟四秒,两个线程先打印 发短信还是打电话? 1.发短信 2.打电话
synchronized 锁的对象是方法的调用!对于两个方法用的是同一个锁,谁先拿到(代码的执行顺序)谁先执行!另外一个则等待!
3、增加了一个普通方法后! 执行的顺序又是什么 普通方法
4、两个对象,两个同步方法 和延迟时间有关系(延迟时间短的先执行)
5、一个对象,两个静态同步方法 执行顺序是什么? 锁的是类,与类的执行代码顺序有关系
6、两个对象,两个静态同步方法 两个对象用的是同一个Class
7、一个对象,一个静态同步方法 一个普通同步方法 看方法的延迟时间
8、两个对象,一个静态同步方法 一个普通同步方法 看方法的延迟时间和这个执行顺序
package com.zmy.lock;/*** 8锁,就是关于锁的八个问题(有延迟才和普通方法显示差别!!!)* 1、标准情况下,两个线程先打印 发短信还是打电话? 1.发短信 2.打电话* 2、发短信延迟四秒,两个线程先打印 发短信还是打电话? 1.发短信 2.打电话* 3、增加了一个普通方法后! 执行的顺序又是什么 普通方法* 4、两个对象,两个同步方法 和延迟时间有关系(延迟时间短的先执行)* 5、一个对象,两个静态同步方法 执行顺序是什么? 锁的是类,与类的执行代码顺序有关系* 6、两个对象,两个静态同步方法 两个对象用的是同一个Class* 7、一个对象,一个静态同步方法 一个普通同步方法 看方法的延迟时间* 8、两个对象,一个静态同步方法 一个普通同步方法 看方法的延迟时间和这个执行顺序*/import java.util.concurrent.TimeUnit;public class Test1 {public static void main(String[] args) {Phone phone = new Phone();Phone phone2 = new Phone();//锁的存在new Thread(()->{phone2.sendSms();}).start();try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{phone2.call();}).start();new Thread(()->{phone.call();}).start();new Thread(()->{phone.hello();}).start();}}class Phone{//synchronized 锁的对象是方法的调用者 也就是phone//两个方法用的是同一把锁,谁先拿到谁先执行//static 静态方法//类一加载就有了! Class模板public static synchronized void sendSms(){System.out.println("发短信");};public synchronized void call(){try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("打电话");}//这里没有锁,不是同步方法,不受锁的影响public void hello(){System.out.println("hello");}}
6、集合类不安全
6.1、List不安全
//java.util.ConcurrentModificationException 并发修改异常!public class ListTest {public static void main(String[] args) {List<Object> arrayList = new ArrayList<>();for(int i=1;i<=10;i++){new Thread(()->{arrayList.add(UUID.randomUUID().toString().substring(0,5));System.out.println(arrayList);},String.valueOf(i)).start();}}}
会导致 java.util.ConcurrentModificationException 并发修改异常!
ArrayList 在并发情况下是不安全的
解决方案:
public class ListTest {public static void main(String[] args) {/*** 解决方案* 1. List<String> list = new Vector<>();* 2. List<String> list = Collections.synchronizedList(new ArrayList<>());* 3. List<String> list = new CopyOnWriteArrayList<>();*/List<String> list = new CopyOnWriteArrayList<>();for (int i = 1; i <=10; i++) {new Thread(() -> {list.add(UUID.randomUUID().toString().substring(0,5));System.out.println(list);},String.valueOf(i)).start();}}}
CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略
核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。
读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为写的时候不会锁住旧的CopyOnWriteArrayList。
多个线程调用的时候,list,读取的时候,固定的,写入(存在覆盖操作);在写入的时候避免覆盖,造成数据错乱的问题;
CopyOnWriteArrayList比Vector厉害在哪里
- Vector底层是使用synchronized关键字来实现的:效率特别低下。
- CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!
6.2、set不安全
Set和List同理可得: 多线程情况下,普通的Set集合是线程不安全的;
解决方案还是两种:
- 使用Collections工具类的synchronized包装的Set类
- 使用CopyOnWriteArraySet 写入复制的JUC解决方案
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 HashSet<>();Set<String> set = new CopyOnWriteArraySet<>();for (int i = 1; i <= 30; i++) {new Thread(() -> {set.add(UUID.randomUUID().toString().substring(0,5));System.out.println(set);},String.valueOf(i)).start();}}}
HashSet底层是什么?
hashSet底层就是一个HashMap;

6.3、Map不安全
//map 是这样用的吗? 不是,工作中不使用这个//默认等价什么? new HashMap<>(16,0.75);Map<String, String> map = new HashMap<>();//加载因子、初始化容量
- currentHashMap
7、Callable
1、可以有返回值;
2、可以抛出异常;
3、方法不同,run()/call()
public class CallableTest {public static void main(String[] args) throws ExecutionException, InterruptedException {for (int i = 1; i < 10; i++) {MyThread1 myThread1 = new MyThread1();FutureTask<Integer> futureTask = new FutureTask<>(myThread1);// 放入Thread中使用,结果会被缓存new Thread(futureTask,String.valueOf(i)).start();// 这个get方法可能会被阻塞,如果在call方法中是一个耗时的方法,所以一般情况我们会把这个放在最后,或者使用异步通信int a = futureTask.get();System.out.println("返回值:" + s);}}}class MyThread1 implements Callable<Integer> {@Overridepublic Integer call() throws Exception {System.out.println("call()");return 1024;}}
8. 常用的辅助类
8.1、CountDownLatch
public class CountDownLatchDemo {public static void main(String[] args) throws InterruptedException {// 总数是6CountDownLatch countDownLatch = new CountDownLatch(6);for (int i = 1; i <= 6; i++) {new Thread(() -> {System.out.println(Thread.currentThread().getName() + "==> Go Out");countDownLatch.countDown(); // 每个线程都数量 -1},String.valueOf(i)).start();}countDownLatch.await(); // 等待计数器归零 然后向下执行System.out.println("close door");}}
主要方法:
- countDown 减一操作;
- await 等待计数器归零
await 等待计数器归零,就唤醒,再继续向下运行
8.2、CyclickBarrier

public class CyclicBarrierDemo {public static void main(String[] args) {// 主线程CyclicBarrier cyclicBarrier = 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 + "颗龙珠");try {cyclicBarrier.await(); // 加法计数 等待} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}).start();}}}
8.3、Semaphore
public class SemaphoreDemo {public static void main(String[] args) {// 线程数量,停车位,限流Semaphore semaphore = new Semaphore(3);for (int i = 0; i <= 6; i++) {new Thread(() -> {// acquire() 得到try {semaphore.acquire();System.out.println(Thread.currentThread().getName() + "抢到车位");TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName() + "离开车位");}catch (Exception e) {e.printStackTrace();}finally {semaphore.release(); // release() 释放}}).start();}}}Thread-1抢到车位Thread-0抢到车位Thread-2抢到车位Thread-0离开车位Thread-2离开车位Thread-1离开车位Thread-5抢到车位Thread-3抢到车位Thread-4抢到车位Thread-5离开车位Thread-3离开车位Thread-6抢到车位Thread-4离开车位Thread-6离开车位Process finished with exit code 0
原理:
semaphore.acquire()获得资源,如果资源已经使用完了,就等待资源释放后再进行使用!
semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!
作用: 多个共享资源互斥的使用! 并发限流,控制最大的线程数!
9. 读写锁
public class ReadWriteLockDemo {public static void main(String[] args) {MyCache myCache = new MyCache();int num = 6;for (int i = 1; i <= num; i++) {int finalI = i;new Thread(() -> {myCache.write(String.valueOf(finalI), String.valueOf(finalI));},String.valueOf(i)).start();}for (int i = 1; i <= num; i++) {int finalI = i;new Thread(() -> {myCache.read(String.valueOf(finalI));},String.valueOf(i)).start();}}}/*** 方法未加锁,导致写的时候被插队*/class MyCache {private volatile Map<String, String> map = new HashMap<>();public void write(String key, String value) {System.out.println(Thread.currentThread().getName() + "线程开始写入");map.put(key, value);System.out.println(Thread.currentThread().getName() + "线程写入ok");}public void read(String key) {System.out.println(Thread.currentThread().getName() + "线程开始读取");map.get(key);System.out.println(Thread.currentThread().getName() + "线程写读取ok");}}
2线程开始写入2线程写入ok3线程开始写入3线程写入ok1线程开始写入 # 插入了其他线程的写入,导致数据不一致4线程开始写入4线程写入ok1线程写入ok6线程开始写入6线程写入ok5线程开始写入5线程写入ok1线程开始读取1线程写读取ok2线程开始读取2线程写读取ok3线程开始读取3线程写读取ok4线程开始读取4线程写读取ok5线程开始读取6线程开始读取6线程写读取ok5线程写读取okProcess finished with exit code 0
所以如果我们不加锁的情况,多线程的读写会造成数据不可靠的问题。
我们也可以采用synchronized这种重量锁和轻量锁 lock去保证数据的可靠。
但是这次我们采用更细粒度的锁:ReadWriteLock 读写锁来保证

public class ReadWriteLockDemo {public static void main(String[] args) {MyCache2 myCache = new MyCache2();int num = 6;for (int i = 1; i <= num; i++) {int finalI = i;new Thread(() -> {myCache.write(String.valueOf(finalI), String.valueOf(finalI));},String.valueOf(i)).start();}for (int i = 1; i <= num; i++) {int finalI = i;new Thread(() -> {myCache.read(String.valueOf(finalI));},String.valueOf(i)).start();}}}class MyCache2 {private volatile Map<String, String> map = new HashMap<>();private ReadWriteLock lock = new ReentrantReadWriteLock();public void write(String key, String value) {lock.writeLock().lock(); // 写锁try {System.out.println(Thread.currentThread().getName() + "线程开始写入");map.put(key, value);System.out.println(Thread.currentThread().getName() + "线程写入ok");}finally {lock.writeLock().unlock(); // 释放写锁}}public void read(String key) {lock.readLock().lock(); // 读锁try {System.out.println(Thread.currentThread().getName() + "线程开始读取");map.get(key);System.out.println(Thread.currentThread().getName() + "线程写读取ok");}finally {lock.readLock().unlock(); // 释放读锁}}}
1线程开始写入1线程写入ok6线程开始写入6线程写入ok3线程开始写入3线程写入ok2线程开始写入2线程写入ok5线程开始写入5线程写入ok4线程开始写入4线程写入ok1线程开始读取5线程开始读取2线程开始读取1线程写读取ok3线程开始读取2线程写读取ok6线程开始读取6线程写读取ok5线程写读取ok4线程开始读取4线程写读取ok3线程写读取okProcess finished with exit code 0
10. 阻塞队列


10.1、BlockQueue
是Collection的一个子类
什么情况下我们会使用阻塞队列
多线程并发处理、线程池

BlockingQueue 有四组api
| 方式 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞,等待 | 超时等待 |
|---|---|---|---|---|
| 添加 | add | offer | put | offer(timenum.timeUnit) |
| 移出 | remove | poll | take | poll(timenum,timeUnit) |
| 判断队首元素 | element | peek | - | - |
/*** 抛出异常*/public static void test1(){//需要初始化队列的大小ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);System.out.println(blockingQueue.add("a"));System.out.println(blockingQueue.add("b"));System.out.println(blockingQueue.add("c"));//抛出异常:java.lang.IllegalStateException: Queue full// System.out.println(blockingQueue.add("d"));System.out.println(blockingQueue.remove());System.out.println(blockingQueue.remove());System.out.println(blockingQueue.remove());//如果多移除一个//这也会造成 java.util.NoSuchElementException 抛出异常System.out.println(blockingQueue.remove());}=======================================================================================/*** 不抛出异常,有返回值*/public static void test2(){ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);System.out.println(blockingQueue.offer("a"));System.out.println(blockingQueue.offer("b"));System.out.println(blockingQueue.offer("c"));//添加 一个不能添加的元素 使用offer只会返回false 不会抛出异常System.out.println(blockingQueue.offer("d"));System.out.println(blockingQueue.poll());System.out.println(blockingQueue.poll());System.out.println(blockingQueue.poll());//弹出 如果没有元素 只会返回null 不会抛出异常System.out.println(blockingQueue.poll());}=======================================================================================/*** 等待 一直阻塞*/public static void test3() throws InterruptedException {ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);//一直阻塞 不会返回blockingQueue.put("a");blockingQueue.put("b");blockingQueue.put("c");//如果队列已经满了, 再进去一个元素 这种情况会一直等待这个队列 什么时候有了位置再进去,程序不会停止// blockingQueue.put("d");System.out.println(blockingQueue.take());System.out.println(blockingQueue.take());System.out.println(blockingQueue.take());//如果我们再来一个 这种情况也会等待,程序会一直运行 阻塞System.out.println(blockingQueue.take());}=======================================================================================/*** 等待 超时阻塞* 这种情况也会等待队列有位置 或者有产品 但是会超时结束*/public static void test4() throws InterruptedException {ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);blockingQueue.offer("a");blockingQueue.offer("b");blockingQueue.offer("c");System.out.println("开始等待");blockingQueue.offer("d",2, TimeUnit.SECONDS); //超时时间2s 等待如果超过2s就结束等待System.out.println("结束等待");System.out.println("===========取值==================");System.out.println(blockingQueue.poll());System.out.println(blockingQueue.poll());System.out.println(blockingQueue.poll());System.out.println("开始等待");blockingQueue.poll(2,TimeUnit.SECONDS); //超过两秒 我们就不要等待了System.out.println("结束等待");}
10.2、同步队列
同步队列 没有容量,也可以视为容量为1的队列;
进去一个元素,必须等待取出来之后,才能再往里面放入一个元素;
put方法 和 take方法;
Synchronized 和 其他的BlockingQueue 不一样 它不存储元素;
put了一个元素,就必须从里面先take出来,否则不能再put进去值!
并且SynchronousQueue 的take是使用了lock锁保证线程安全的。
package com.marchsoft.queue;import java.util.concurrent.BlockingDeque;import java.util.concurrent.BlockingQueue;/*** Description:** @author jiaoqianjin* Date: 2020/8/12 10:02**/public class SynchronousQueue {public static void main(String[] args) {BlockingQueue<String> synchronousQueue = new java.util.concurrent.SynchronousQueue<>();// 网queue中添加元素new Thread(() -> {try {System.out.println(Thread.currentThread().getName() + "put 01");synchronousQueue.put("1");System.out.println(Thread.currentThread().getName() + "put 02");synchronousQueue.put("2");System.out.println(Thread.currentThread().getName() + "put 03");synchronousQueue.put("3");} catch (InterruptedException e) {e.printStackTrace();}}).start();// 取出元素new Thread(()-> {try {System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());System.out.println(Thread.currentThread().getName() + "take" + synchronousQueue.take());}catch (InterruptedException e) {e.printStackTrace();}}).start();}}
Thread-0put 01Thread-1take1Thread-0put 02Thread-1take2Thread-0put 03Thread-1take3Process finished with exit code 0
11. 线程池
线程池:三大方式、七大参数、四种拒绝策略
池化技术
程序的运行,本质:占用系统的资源!我们需要去优化资源的使用 ===> 池化技术
线程池、JDBC的连接池、内存池、对象池 等等。。。。
资源的创建、销毁十分消耗资源
池化技术:事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率。
11.1、线程池的好处:
1、降低资源的消耗;
2、提高响应的速度;
3、方便管理;
线程复用、可以控制最大并发数、管理线程;
11.2、线程池:三大方法
- ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
- ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
- ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的
//工具类 Executors 三大方法;public class Demo01 {public static void main(String[] args) {ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的//线程池用完必须要关闭线程池try {for (int i = 1; i <=100 ; i++) {//通过线程池创建线程threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+ " ok");});}} catch (Exception e) {e.printStackTrace();} finally {threadPool.shutdown();}}}
11.3、七大参数

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.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}
阿里巴巴规范

阿里巴巴的Java操作手册中明确说明:对于Integer.MAX_VALUE初始值较大,所以一般情况我们要使用底层的ThreadPoolExecutor来创建线程池。
public class PollDemo {public static void main(String[] args) {// 获取cpu 的核数int max = Runtime.getRuntime().availableProcessors();ExecutorService service =new ThreadPoolExecutor(2,max,3,TimeUnit.SECONDS,new LinkedBlockingDeque<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());try {for (int i = 1; i <= 10; i++) {service.execute(() -> {System.out.println(Thread.currentThread().getName() + "ok");});}}catch (Exception e) {e.printStackTrace();}finally {service.shutdown();}}}
11.4、四种拒绝策略
- new ThreadPoolExecutor.AbortPolicy(): //该拒绝策略为:银行满了,还有人进来,不处理这个人的,并抛出异常
超出最大承载,就会抛出异常:队列容量大小+maxPoolSize
new ThreadPoolExecutor.CallerRunsPolicy(): //该拒绝策略为:哪来的去哪里 main线程进行处理
new ThreadPoolExecutor.DiscardPolicy(): //该拒绝策略为:队列满了,丢掉异常,不会抛出异常。
new ThreadPoolExecutor.DiscardOldestPolicy(): //该拒绝策略为:队列满了,尝试去和最早的进程竞争,不会抛出异常
11.5、如何设置线程池的大小
1、CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小
// 获取cpu 的核数int max = Runtime.getRuntime().availableProcessors();ExecutorService service =new ThreadPoolExecutor(2,max,3,TimeUnit.SECONDS,new LinkedBlockingDeque<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
2、I/O密集型:
在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。
小结和扩展
了解io密集型、CPU密集型 调优
池的最大线程如何设置
12、四大函数式接口
新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算
函数式接口:只有一个方法的接口
@FunctionalInterfacepublic interface Runnable {public abstract void run();}//超级多的@FunctionalInterface//简化编程模型,在新版本的框架底层大量应用//foreach()的参数也是一个函数式接口,消费者类的函数式接口
16、JMM
请你谈谈你对Volatile 的理解
Volatile 是 Java 虚拟机提供 轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
什么是JMM?
JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!
关于JMM的一些同步的约定:
- 线程解锁前,必须把共享变量立刻刷回主存;
- 线程加锁前,必须读取主存中的最新值到工作内存中;
- 加锁和解锁是同一把锁;
8种操作:
- Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
- load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
- Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
- Assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
- store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
- write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;
- lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;


JMM对这8种操作给了相应的规定:\
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
17、volatile
17.1、保证可见性
public class JMMDemo01 {// 如果不加volatile 程序会死循环// 加了volatile是可以保证可见性的private volatile static Integer number = 0;public static void main(String[] args) {//main线程//子线程1new Thread(()->{while (number==0){}}).start();try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}//子线程2new Thread(()->{while (number==0){}}).start();try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}number=1;System.out.println(number);}}
17.2、不保证原子性
原子性:不可分割;
线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功shi,要么同时失败。
/*** 不保证原子性* number <=2w**/public class VDemo02 {private static volatile int number = 0;public static void add(){number++;//++ 不是一个原子性操作,是两个~3个操作//}public static void main(String[] args) {//理论上number === 20000for (int i = 1; i <= 20; i++) {new Thread(()->{for (int j = 1; j <= 1000 ; j++) {add();}}).start();}while (Thread.activeCount()>2){//main gcThread.yield();}System.out.println(Thread.currentThread().getName()+",num="+number);}}
如果不加lock和synchronized ,怎么样保证原子性?

解决方法:使用JUC下的原子包下的class;

public class VDemo02 {private static volatile AtomicInteger number = new AtomicInteger();public static void add(){// number++;number.incrementAndGet(); //底层是CAS保证的原子性}public static void main(String[] args) {//理论上number === 20000for (int i = 1; i <= 20; i++) {new Thread(()->{for (int j = 1; j <= 1000 ; j++) {add();}}).start();}while (Thread.activeCount()>2){//main gcThread.yield();}System.out.println(Thread.currentThread().getName()+",num="+number);}}
这些类的底层都直接和操作系统挂钩!是在内存中修改值。
Unsafe类是一个很特殊的存在;
原子类为什么这么高级?
17.3、禁止指令重排
什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的
源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性!
int x=1; //1int y=2; //2x=x+5; //3y=x*x; //4//我们期望的执行顺序是 1_2_3_4 可能执行的顺序会变成2134 1324//可不可能是 4123? 不可能的
可能造成的影响结果:前提:a b x y这四个值 默认都是0
| 线程A | 线程B |
|---|---|
| x=a | y=b |
| b=1 | a=2 |
正常的结果: x = 0; y =0;
| 线程A | 线程B |
|---|---|
| x=a | y=b |
| b=1 | a=2 |
可能在线程A中会出现,先执行b=1,然后再执行x=a;
在B线程中可能会出现,先执行a=2,然后执行y=b;
那么就有可能结果如下:x=2; y=1.
volatile可以避免指令重排:
volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。
内存屏障:CPU指令。作用:
1、保证特定的操作的执行顺序;
2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)
18、单例模式
18.1、饿汉式
/*** 饿汉式单例*/public class Hungry {/*** 可能会浪费空间*/private byte[] data1=new byte[1024*1024];private byte[] data2=new byte[1024*1024];private byte[] data3=new byte[1024*1024];private byte[] data4=new byte[1024*1024];private Hungry(){}private final static Hungry hungry = new Hungry();public static Hungry getInstance(){return hungry;}}
18.2、DCL懒汉式
//懒汉式单例模式public class LazyMan {private static boolean key = false;private LazyMan(){synchronized (LazyMan.class){if (key==false){key=true;}else{throw new RuntimeException("不要试图使用反射破坏异常");}}System.out.println(Thread.currentThread().getName()+" ok");}private volatile static LazyMan lazyMan;//双重检测锁模式 简称DCL懒汉式public static LazyMan getInstance(){//需要加锁if(lazyMan==null){synchronized (LazyMan.class){if(lazyMan==null){lazyMan=new LazyMan();/*** 1、分配内存空间* 2、执行构造方法,初始化对象* 3、把这个对象指向这个空间** 就有可能出现指令重排问题* 比如执行的顺序是1 3 2 等* 我们就可以添加volatile保证指令重排问题*/}}}return lazyMan;}//单线程下 是ok的//但是如果是并发的public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {//Java中有反射// LazyMan instance = LazyMan.getInstance();Field key = LazyMan.class.getDeclaredField("key");key.setAccessible(true);Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);declaredConstructor.setAccessible(true); //无视了私有的构造器LazyMan lazyMan1 = declaredConstructor.newInstance();key.set(lazyMan1,false);LazyMan instance = declaredConstructor.newInstance();System.out.println(instance);System.out.println(lazyMan1);System.out.println(instance == lazyMan1);}}
19、深入理解CAS
19.1、什么是CAS?
public class casDemo {//CAS : compareAndSet 比较并交换public static void main(String[] args) {AtomicInteger atomicInteger = new AtomicInteger(2020);//boolean compareAndSet(int expect, int update)//期望值、更新值//如果实际值 和 我的期望值相同,那么就更新//如果实际值 和 我的期望值不同,那么就不更新System.out.println(atomicInteger.compareAndSet(2020, 2021));System.out.println(atomicInteger.get());//因为期望值是2020 实际值却变成了2021 所以会修改失败//CAS 是CPU的并发原语atomicInteger.getAndIncrement(); //++操作System.out.println(atomicInteger.compareAndSet(2020, 2021));System.out.println(atomicInteger.get());}}

19.2、unsafe类(java操作内存的后门)


总结:
CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。
缺点:
- 循环会耗时;
- 一次性只能保证一个共享变量的原子性;
- 它会存在ABA问题
CAS:ABA问题?(狸猫换太子)

线程1:期望值是1,要变成2;
线程2:两个操作:
- 1、期望值是1,变成3
- 2、期望是3,变成1
所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;
package com.zmy.CAS;import java.util.concurrent.atomic.AtomicInteger;public class CASDemo {//CAS compareAndSet 比较并交换public static void main(String[] args) {AtomicInteger atomicInteger=new AtomicInteger(2020);//boolean compareAndSet(int expect, int update)//期望值、更新值//如果实际值 和 我的期望值相同,那么就更新//如果实际值 和 我的期望值不同,那么就不更新//==========================捣乱的线程==================System.out.println(atomicInteger.compareAndSet(2020,2021));System.out.println(atomicInteger.get());System.out.println(atomicInteger.compareAndSet(2021,2020));System.out.println(atomicInteger.get());//==================期望的线程============================//因为期望值是2020 实际值却变成了2021 所以会修改失败//CAS 是CPU的并发原语//atomicInteger.getAndIncrement(); //++操作System.out.println(atomicInteger.compareAndSet(2020,2021));System.out.println(atomicInteger.get());}}
20、原子引用
解决ABA问题,对应的思想:就是使用了乐观锁~
带版本号的 原子操作!
坑:如果泛型是包装类,要注意对象得引用问题-
Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。

所以如果遇到,使用大于128的时候,使用原子引用的时候,如果超过了这个值,那么就不会进行版本上升!

那么如果我们使用小于128的时候:

21、各种锁的理解
21.1、公平锁、非公平锁
公平锁:非常公平;不能插队的,必须先来后到;
/*** Creates an instance of {@code ReentrantLock}.* This is equivalent to using {@code ReentrantLock(false)}.*/public ReentrantLock() {sync = new NonfairSync();}
非公平锁:非常不公平,允许插队的,可以改变顺序。(默认都是非公平锁)
/*** Creates an instance of {@code ReentrantLock} with the* given fairness policy.** @param fair {@code true} if this lock should use a fair ordering policy*/public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}
21.2、可重入锁
可重入锁(递归锁)

Synchronized锁
public class Demo01 {public static void main(String[] args) {Phone phone = new Phone();new Thread(()->{phone.sms();},"A").start();new Thread(()->{phone.sms();},"B").start();}}class Phone{public synchronized void sms(){System.out.println(Thread.currentThread().getName()+"=> sms");call();//这里也有一把锁}public synchronized void call(){System.out.println(Thread.currentThread().getName()+"=> call");}}
lock锁
//lockpublic class Demo02 {public static void main(String[] args) {Phone2 phone = new Phone2();new Thread(()->{phone.sms();},"A").start();new Thread(()->{phone.sms();},"B").start();}}class Phone2{Lock lock=new ReentrantLock();public void sms(){lock.lock(); //细节:这个是两把锁,两个钥匙//lock锁必须配对,否则就会死锁在里面try {System.out.println(Thread.currentThread().getName()+"=> sms");call();//这里也有一把锁} catch (Exception e) {e.printStackTrace();}finally {lock.unlock();}}public void call(){lock.lock();try {System.out.println(Thread.currentThread().getName() + "=> call");}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}}
- lock锁必须配对,相当于lock和 unlock 必须数量相同;
- 在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;
21.3、自旋锁
spinlock
public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;}
自我设计自旋锁:
public class SpinlockDemo {//int 0//thread nullAtomicReference<Thread> atomicReference=new AtomicReference<>();//加锁public void myLock(){Thread thread = Thread.currentThread();System.out.println(thread.getName()+"===> mylock");//自旋锁while (!atomicReference.compareAndSet(null,thread)){System.out.println(Thread.currentThread().getName()+" ==> 自旋中~");}}//解锁public void myunlock(){Thread thread=Thread.currentThread();System.out.println(thread.getName()+"===> myUnlock");atomicReference.compareAndSet(thread,null);}}
public class TestSpinLock {public static void main(String[] args) throws InterruptedException {ReentrantLock reentrantLock = new ReentrantLock();reentrantLock.lock();reentrantLock.unlock();//使用CAS实现自旋锁SpinlockDemo spinlockDemo=new SpinlockDemo();new Thread(()->{spinlockDemo.myLock();try {TimeUnit.SECONDS.sleep(3);} catch (Exception e) {e.printStackTrace();} finally {spinlockDemo.myunlock();}},"t1").start();TimeUnit.SECONDS.sleep(1);new Thread(()->{spinlockDemo.myLock();try {TimeUnit.SECONDS.sleep(3);} catch (Exception e) {e.printStackTrace();} finally {spinlockDemo.myunlock();}},"t2").start();}}
21.4、死锁

死锁测试,怎么排除死锁:
package com.ogj.lock;import java.util.concurrent.TimeUnit;public class DeadLock {public static void main(String[] args) {String lockA= "lockA";String lockB= "lockB";new Thread(new MyThread(lockA,lockB),"t1").start();new Thread(new MyThread(lockB,lockA),"t2").start();}}class MyThread implements Runnable{private String lockA;private String lockB;public MyThread(String lockA, String lockB) {this.lockA = lockA;this.lockB = lockB;}@Overridepublic void run() {synchronized (lockA){System.out.println(Thread.currentThread().getName()+" lock"+lockA+"===>get"+lockB);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lockB){System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA);}}}}
解决问题
1、使用jps定位进程号,jdk的bin目录下: 有一个jps
命令:jps -l

2、使用jstack 进程进程号 找到死锁信息

一般情况信息在最后:

