0.学习方法
源码+官方文档:
其实官方文档就是源码编译出来的,其本质还是看源码,不过文档会比较方便学习
1.什么是JUC
JUC其实就是Java.Util.concurrent包的缩写
java.util.concurrent
java.util.concurrent.atomi
java.util.concurrentlocks
是 java.util 工具包、包、分类
Thread
Runnable
Callable
2.线程与进程
线程、进程,如果不能使用一句话说出来的技术,不扎实!
打开(Win10)任务管理器可以清楚看到执行的线程与进程:
参考博客:什么是线程?什么是进程
进程:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础
进行(运行)中的程序,如打开任务管理器后中各种.exe程序
线程:
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程是真正执行资源调度(使程序跑起来)的主体,一个进程往往可以包含多个线程,但至少包含一个线程。
如:开一个idea进程,其中至少有—> 线程1:输入代码,线程2:自动保存
??老是强调多线程,那么 Java真的可以开启线程吗?
答案是 : 不能。查看Thread.start()
源码可得知:
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } } // 本地方法,底层的C++ ,Java 无法直接操作硬件 private native void start0();
并发编程:并发、并行
public class Test1 { public static void main(String[] args) { // 获取cpu的核数 // CPU 密集型,IO密集型 System.out.println(Runtime.getRuntime().availableProcessors()); //输出为8 //说明笔者电脑为八核处理器 } }
从源码回答,有理有据
public enum State { // 新生 NEW, // 运行 RUNNABLE, // 阻塞 BLOCKED, // 等待,死死地等 WAITING, // 超时等待 TIMED_WAITING, // 终止 TERMINATED; }
看源码说话嗷??
//Object.wait() public final void wait() throws InterruptedException { wait(0); } //Thread.sleep() public static native void sleep(long millis) throws InterruptedException;
来自不同的类
关于锁的释放
是否需要捕获异常?
需要。源码都在上面写的死死的,throws InterruptedException ??都不知网上随手一搜的博客说wait() 不用捕获异常怎么搞得。
使用范围:
wait() 需要在同步代码块中使用
// wait、notify/notifyAll必须在同步控制块、同步方法里面使用。而sleep的使用在任意地方。 synchronized(x){ x.notify() //或者wait() }
sleep()可以在任何地方睡
作用对象:
方法属性:
static
并发类中进程睡眠使用的函数TimeUnit.Days.sleep(1)
同时只要线程都有中断异常,所以wait也有中断异常。
3.Lock锁(重点)
真正的多线程开发,公司中的开发,降低耦合性 线程就是一个单独的资源类,没有任何附属的操作! 对于资源类只有: 属性、方法
开启线程错误方式:
class Ticket implements Runnable{}
耦合度高,违背了oop(面向对象)思想
public class SaleTicket_WithSynchronized { public static void main(String[] args) { // 并发:多线程操作同一个资源类, 把资源类丢入线程 Ticket ticket = new Ticket(); // @FunctionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{ 代码 } new Thread(() -> { for (int i = 1; i < 40; i++) { ticket.saleTicket(); } }, "A").start(); new Thread(() -> { for (int i = 1; i < 40; i++) { ticket.saleTicket(); } }, "B").start(); new Thread(() -> { for (int i = 1; i < 40; i++) { ticket.saleTicket(); } }, "C").start(); } } //资源类 OOP: class Ticket { //属性、方法 private int number = 30; //卖票方法 //用synchronized 上锁 public synchronized void saleTicket() { if (number > 0) { System.out.println(Thread.currentThread().getName() + "卖出了第" + (number--) + "张票,剩余" + number + "张"); } } }
lambda表达式简化了runnable函数式接口的使用,使得程序更加简洁,保证了资源类的独立性和程序的解耦合
package com.bupt; public class SaleTicket { public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(()->{ for (int i = 0; i < 4; i++) { ticket.saleTicket(); } },"A").start(); //上下两个线程写法是等价的,Runable匿名内部类使用lambda表达式进行了简化 new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 4; i++) { ticket.saleTicket(); } } }, "B").start(); } } class Ticket{ //只是一个资源类,保证只遵循oop原则 private int number = 30; public synchronized void saleTicket(){ if(number > 0){ System.out.println(Thread.currentThread().getName() + "卖出了第" + number-- + "张票,剩余"+ number); } } }
用Lock接口
Lock源码定义:
常用上锁语句:
Lock l = ...; l.lock(); //加锁 try { // access the resource protected by this lock } finally { l.unlock(); //解锁}
ReentrantLock 可重入锁
公平锁与非公平锁(简单认识,后面详解)
上案例
// Lock 三部曲 // 1、 new ReentrantLock(); // 2、 lock.lock(); // 加锁 // 3、 finally=> lock.unlock(); // 解锁 public class SaleTicket_WithLock { public static void main(String[] args) { // 并发:多线程操作同一个资源类, 把资源类丢入线程 Ticket ticket = new Ticket(); // @FunctionalInterface 函数式接口,jdk1.8 lambda表达式 (参数)->{ 代码 } new Thread(() -> { for (int i = 1; i < 40; i++) { ticket.saleTicket(); } }, "A").start(); new Thread(() -> { for (int i = 1; i < 40; i++) { ticket.saleTicket(); } }, "B").start(); new Thread(() -> { for (int i = 1; i < 40; i++) { ticket.saleTicket(); } }, "C").start(); } } class Ticket2 { // 属性、方法 private int number = 30; 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(); // 解锁 } } }
Synchronized 和 Lock 区别
Java中的锁——Lock和synchronized
4、相比于synchronized,Lock接口所具备的其他特性
①尝试非阻塞的获取锁tryLock():当前线程尝试获取锁,如果该时刻锁没有被其他线程获取到,就能成功获取并持有锁
②能被中断的获取锁lockInterruptibly():获取到锁的线程能够响应中断,当获取到锁的线程被中断的时候,会抛出中断异常同时释放持有的锁
③超时的获取锁tryLock(long time, TimeUnit unit):在指定的截止时间获取锁,如果没有获取到锁返回false
ReentrantLock中lock(),tryLock(),lockInterruptibly()的区别
join方法:原来主线程和子线程是并行的关系,但是一旦使用了join()方法,就会变成串行的关系;当主线程调用子线程的join()方法时,意味着必须等子线程执行完毕之后,主线程才会开始执行。
yield是线程失去CPU资源,从运行状态变成就绪状态
wait是释放当前锁,让其他线程获取锁,当被唤醒后就参与获取锁的竞争
非公平锁按照一定的策略比如耗时等选择线程执行,但是公平锁是按照先来后到,一般选择非公平锁
从pc(producer<–>consumer)问题看锁的本质
生产者消费者synchronized版本
案例:对一个数字不停进行 +1 -1操作,加完了减,减完了加
public class PC_WithSynchronized { 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(); } } },"ADD").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"MINUS").start(); } } //判断等待-->业务代码-->通知 //数字:资源类 class Data{ //属性 private int number = 0; //+1方法 public synchronized void increment() throws InterruptedException { if (number != 0){ //等待 this.wait(); } number++; System.out.println(Thread.currentThread().getName()+"-->"+number); //加完了通知其他线程 this.notifyAll(); } //-1 public synchronized void decrement() throws InterruptedException { if (number == 0){ //等待 this.wait(); } number--; System.out.println(Thread.currentThread().getName()+"-->"+number); //减完了通知其他线程 this.notifyAll(); } }
此时输出:
加减交替执行,一片祥和~
.. ADD-->1 MINUS-->0 ADD-->1 MINUS-->0 ADD-->1 MINUS-->0 ADD-->1 MINUS-->0 ADD-->1 MINUS-->0 ...
问题来了,现在只有"ADD"和"MINUS"两个线程执行操作,如果增加多两个线程会怎样呢?
于是在main方法中增加了"ADD2" 和 "MINUS2"两条线程:
new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"ADD2").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"MINUS2").start();
出大问题了:出现了数据错误甚至死锁问题
原因如下:
此时解决方法:将 if 改为 while
package com.kuangstudy.JUC; /** * @BelongsProject: itstack-demo-design-1-00 * @BelongsPackage: com.kuangstudy.JUC * @Author: lgwayne * @CreateTime: 2021-02-20 17:32 * @Description: */ public class PC_WithSynchronized { 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(); } } },"ADD").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"MINUS").start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } },"ADD2").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"MINUS2").start(); } } //判断等待-->业务代码-->通知 //数字:资源类 class Data{ //data类相同,不再赘述 }
虚假唤醒的一个形象的例子
当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功
1.比如说买货,如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁
相关博文:https://blog.csdn.net/qq_39455116/article/details/87101633
结果一片祥和~
... ADD2-->1 MINUS2-->0 ADD2-->1 MINUS2-->0 ADD2-->1 MINUS2-->0 ADD2-->1 MINUS2-->0 ADD2-->1 MINUS2-->0 ...
通过Lock找到condition来配合控制对线程的唤醒
public class PC_WithLock { public static void main(String[] args) { //主线程测试方法与上述一致,不再赘述 } } //判断等待-->业务代码-->通知 //数字:资源类 class Data_withLock { //属性 private int number = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); //+1方法 public void increment() throws InterruptedException { lock.lock(); try { while (number != 0) { //等待 condition.await(); } number++; System.out.println(Thread.currentThread().getName() + "-->" + number); //加完了通知其他线程 condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } //-1 public void decrement() throws InterruptedException { lock.lock(); try { while (number == 0) { //等待 condition.await(); } number--; System.out.println(Thread.currentThread().getName() + "-->" + number); //减完了通知其他线程 condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } }
用condition搭配状态位置试试
public class PC_WithAwakeByCondition { public static void main(String[] args) { Data_AwakeInOrder data = new Data_AwakeInOrder(); new Thread(()->{ for (int i = 0; i < 10; i++) { data.printA(); } },"线程A").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { data.printB(); } },"线程B").start(); new Thread(()->{ for (int i = 0; i < 10; i++) { data.printC(); } },"线程C").start(); } } //资源类 class Data_AwakeInOrder{ private 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() + "=>AAAAAA"+"-----number为->"+number); // 唤醒,唤醒指定的人,B number = 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() + "=>BBBBBB"+"-----number为->"+number); // 唤醒,唤醒指定的人C 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() + "=>CCCCCC"+"-----number为->"+number); // 唤醒,唤醒指定的人A number = 1; condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
运行结果:??一片祥和~依序执行
... 线程A=>AAAAAA-----number为->1 线程B=>BBBBBB-----number为->2 线程C=>CCCCCC-----number为->3 线程A=>AAAAAA-----number为->1 线程B=>BBBBBB-----number为->2 线程C=>CCCCCC-----number为->3 ...
看视频的时候看到有人用wait和notify 也实现了精准唤醒,接下来稍作尝试
public class PC_AwakeWithWait { public static void main(String[] args) { Data_AwakeInOrderByWait data = new Data_AwakeInOrderByWait(); new Thread(()->{ for (int i = 0; i < 100; i++) { data.printA(); } },"T_A").start(); new Thread(()->{ for (int i = 0; i < 100; i++) { data.printB(); } },"T_B").start(); new Thread(()->{ for (int i = 0; i < 100; i++) { data.printC(); } },"T_C").start(); } } //资源类 class Data_AwakeInOrderByWait{ private int number = 1; public synchronized void printA() { // 业务,判断-> 执行-> 通知 while (number != 1) { // 等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "=>AAAAAA->"+number); // 唤醒,唤醒指定的人,B number = 2; this.notifyAll(); } public synchronized void printB(){ // 业务,判断-> 执行-> 通知 while (number != 2) { // 等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "=>BBBBBB->"+number); // 唤醒,唤醒指定的人,B number = 3; this.notifyAll(); } public synchronized void printC(){ // 业务,判断-> 执行-> 通知 while (number != 3) { // 等待 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "=>CCCCCC->"+number); // 唤醒,唤醒指定的人,B number = 1; this.notifyAll(); } }
结果也确实可以实现精准唤醒:
... T_A=>AAAAAA->1 T_B=>BBBBBB->2 T_C=>CCCCCC->3 T_A=>AAAAAA->1 T_B=>BBBBBB->2 T_C=>CCCCCC->3 ...
后来的技术是迭代出的更优秀的版本。等学成归来对比下这两种唤醒的效率
原文:https://www.cnblogs.com/henuliulei/p/14862335.html