JUC并发编程笔记1__JUC概述、虚假唤醒问题、JUC的生产者和消费者关系、8锁问题(对象锁、类锁区别)
目录
一、回顾
概念
线程的几个状态
wait和sleep的区别
Synchronized 和 Lock的区别
二、虚假唤醒问题
举例synchronized下的生产者、消费者模式场景
三、JUC下的生产者、消费者关系
四、8锁问题(对象锁、类锁)
一、回顾
概念
JUC即并发编程,也是多线程的进阶版
主要涉及的三个类java.util.concurrent、 java.util.concurrent.automic、 java.util.concurrent.locks
注意java实际上是开启不了线程的,而是调用本地方法,底层是c++开启线程,java是运行在虚拟机上的,无法直接操作硬件
线程的几个状态
new 新生
runnable 运行
blocked 阻塞
waiting 等待
timed_witing 超时等待
terminated 终止
wait和sleep的区别
- 来自不同的类,wait来自Object,sleep来自Thread
- 锁是否是否释放不同,wait会释放锁,sleep不会释放锁
- 使用范围不同,wait必须放在同步代码块中(与显示锁Lock对应)
- 是否需要异常捕获,wait不需要异常捕获,sleep需要异常捕获
Synchronized 和 Lock的区别
- Synchronized 是内置java 关键字,Lock是接口
- Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
- Synchronized 会自动释放锁,Lock必须手动释放
- Synchronized 线程1获得锁,则其它线程会一直等待,Lock 锁下的线程就不一定会一直等
- Synchronized 是可重入锁,不可中断的,非公平,Lock 可重入锁,可以判断锁,非公平锁可以设置公平性
- Synchronized 适合对少量代码加锁,Lock 适合大量同步代码
二、虚假唤醒问题
举例synchronized下的生产者、消费者模式场景
- 资源类的属性num 为 0,方法是 对num 加1 和 减1
- 初始是两个线程使用wait()和notifyAll()进行通信,保证num为0时候+1,为1时-1
- 线程A是对num进行10次的+1,线程B是对num进行10次的-1
- 线程A执行+1的条件是if(num == 0) ,否则wait等待,执行完 +1 就 notifyAll 唤醒其他线程(此时只有B线程);B线程同理
- 结果可以发现两个线程间的通信 正常
package synchronized_productor_consumer;public class Demo {public static void main(String[] args) {Data data = new Data();// 线程A,进行加1new Thread(()->{try {// 10 次操作for (int i = 0; i < 10; i++) {data.increment();}} catch (InterruptedException e) {e.printStackTrace();}},"线程A").start();// 线程B,进行减1new Thread(()->{try {for (int i = 0; i < 10; i++) {data.decrement();}} catch (InterruptedException e) {e.printStackTrace();}},"线程B").start();}
}/*** 待操作的Data资源类,需要解耦*/class Data{private int num = 0;public synchronized void increment() throws InterruptedException {if(num != 0){// 等待this.wait();}System.out.println(Thread.currentThread().getName() + "=> " + num);num ++;this.notifyAll();}public synchronized void decrement() throws InterruptedException {if(num == 0){// 等待this.wait();}System.out.println(Thread.currentThread().getName() + "=> " + num);num --;this.notifyAll();}}输出:
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
现在是A一个线程做 + 1,B一个线程做 - 1,如果是多个线程呢?
package synchronized_productor_consumer;public class Demo {public static void main(String[] args) {Data data = new Data();// 线程A,进行加1new Thread(()->{try {// 10 次操作for (int i = 0; i < 10; i++) {data.increment();}} catch (InterruptedException e) {e.printStackTrace();}},"线程A").start();// 线程B,进行减1new Thread(()->{try {for (int i = 0; i < 10; i++) {data.decrement();}} catch (InterruptedException e) {e.printStackTrace();}},"线程B").start();// 线程A,进行加1new Thread(()->{try {// 10 次操作for (int i = 0; i < 10; i++) {data.increment();}} catch (InterruptedException e) {e.printStackTrace();}},"线程C").start();// 线程B,进行减1new Thread(()->{try {for (int i = 0; i < 10; i++) {data.decrement();}} catch (InterruptedException e) {e.printStackTrace();}},"线程D").start();}
}/*** 待操作的Data资源类,需要解耦*/class Data{private int num = 0;public synchronized void increment() throws InterruptedException {if(num != 0){// 等待this.wait();}System.out.println(Thread.currentThread().getName() + "=> " + num);num ++;this.notifyAll();}public synchronized void decrement() throws InterruptedException {if(num == 0){// 等待this.wait();}System.out.println(Thread.currentThread().getName() + "=> " + num);num --;this.notifyAll();}}输出:
线程A=> 0
线程D=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程C=> 0
线程B=> 1
线程D=> 0
线程D=> -1
线程D=> -2
线程D=> -3
线程D=> -4
线程D=> -5
线程D=> -6
线程D=> -7
线程D=> -8
线程A=> -9
虚假唤醒问题分析
结果出现问题的原因:在于资源类内部的if判断,因为if判断只进行一次,造成多个线程之间的虚假唤醒问题,就是指有可能当A线程进入在if代码块中调用wait()方法后,A线程会处于阻塞状态,wait()方法还会释放当前A线程获取到的锁,此时有可能C线程获取到Cpu时间片,进入if代码块,调用wait()方法处于阻塞状态并释放锁,当其他线程重新唤醒A、C线程以后,A、C线程都会从if代码块中跳出来,执行num + 1的操作
解决办法:就是将资源类内部 if 判断换成 while 即可,这样每次线程都会先进行一次条件判断,避免虚假唤醒问题
三、JUC下的生产者、消费者关系
Lock下的线程通信
- Condition接口取代了Object监视器方法(wait、notify、notifyAll)使用自己的方法完成线程间通信
- 使用lock对象的newCondition()方法可以获得condition对象,该对象有一个await()等待方法和singalAll()唤醒全部线程方法
- 同时Condition支持精准的通知、唤醒线程(使线程按照顺序执行)
场景举栗
package lock_productor_consumer;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Demo {public static void main(String[] args) {Data data = new Data();// 线程A,进行加1new Thread(()->{try {// 10 次操作for (int i = 0; i < 10; i++) {data.increment();}} catch (Exception e) {e.printStackTrace();}},"线程A").start();// 线程B,进行减1new Thread(()->{try {for (int i = 0; i < 10; i++) {data.decrement();}} catch (Exception e) {e.printStackTrace();}},"线程B").start();// 线程A,进行加1new Thread(()->{try {// 10 次操作for (int i = 0; i < 10; i++) {data.increment();}} catch (Exception e) {e.printStackTrace();}},"线程C").start();// 线程B,进行减1new Thread(()->{try {for (int i = 0; i < 10; i++) {data.decrement();}} catch (Exception e) {e.printStackTrace();}},"线程D").start();}
}/*** 待操作的Data资源类,需要解耦*/class Data{private int num = 0;Lock lock = new ReentrantLock();Condition condition = lock.newCondition();public void increment() {// 加锁lock.lock();try {while(num != 0){// 等待condition.await();}System.out.println(Thread.currentThread().getName() + "=> " + num);num ++;// 唤醒condition.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {// 解锁lock.unlock();}}public void decrement() {// 加锁lock.lock();try {while(num == 0){// 等待condition.await();}System.out.println(Thread.currentThread().getName() + "=> " + num);num --;// 唤醒condition.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {// 解锁lock.unlock();}}}
输出:
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程A=> 0
线程B=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
线程C=> 0
线程D=> 1
Condition控制线程间精准通信
场景如三个线程顺序执行:
A线程先执行,使达到B线程的执行条件,唤醒B线程,A线程处于等待状态;
B线程接着执行,使达到C线程的执行条件,唤醒C线程,B线程处于等待状态;
C线程接着执行,使达到A线程的执行条件,唤醒A线程,C线程处于等待状态;
…
实现原理是condition对象可以设置多个,每个对象可以和线程绑定一块。一个condition对象服务一个线程,因为加锁了,所以其他线程正在等待,而有执行条件的线程会接到通知从 判断--->等待--->执行--->通知
package study_condition;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Demo {public static void main(String[] args) {Data data = new Data();new Thread(()->{for (int i = 0; i < 10; i++) {data.A();}},"1线程").start();new Thread(()->{for (int i = 0; i < 10; i++) {data.B();}},"2线程").start();new Thread(()->{for (int i = 0; i < 10; i++) {data.C();}},"3线程").start();}
}class Data {private Lock lock = new ReentrantLock();private Condition condition1 = lock.newCondition();private Condition condition2 = lock.newCondition();private Condition condition3 = lock.newCondition();private int num = 1;public void A(){lock.lock();try {while(num != 1){// 等待condition1.await();}System.out.println(Thread.currentThread().getName() + "=> A 方法" );num = 2;// 唤醒执行方法B的线程condition2.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void B(){lock.lock();try {while(num != 2){// 等待condition2.await();}System.out.println(Thread.currentThread().getName() + "=> B 方法" );num = 3;// 唤醒执行方法B的线程condition3.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}public void C(){lock.lock();try {while(num != 3){// 等待condition3.await();}System.out.println(Thread.currentThread().getName() + "=> C 方法" );num = 1;// 唤醒执行方法B的线程condition1.signal();} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}}输出:1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
1线程=> A 方法
2线程=> B 方法
3线程=> C 方法
四、8锁问题(对象锁、类锁)
初始场景:
- Phone资源类有 void sendMessage() 、void call()两个方法
- main方法开启 多线程场景 进行测试,8种情况可以提出8个锁问题,即8锁问题
- JUC下线程sleep方法:TimeUnit.SECONDS.sleep(1) -- 线程睡眠1s
1.创建一个Phone实例多线程调用两个方法,问哪一个先执行?
package eight_locks;import java.util.concurrent.TimeUnit;public class Demo1 {public static void main(String[] args) {Phone p = new Phone();// 发短信new Thread(()->{p.sendMessage();}).start();// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 打电话new Thread(()->{p.call();}).start();}
}class Phone {public synchronized void sendMessage(){System.out.println("发短信");}public synchronized void call(){System.out.println("打电话");}
}输出:
发短信
打电话
结果分析:
- 第一个线程与第二个线程之间因为有睡眠,所以第一个线程肯定先拿到cpu的时间片,先执行方法
- 因为synchronized关键字 是对 该Phone资源类的对象 上锁,因此哪个线程先拿到对象锁,就先执行
- 因此第一个线程先拿到锁,所以先执行
2.创建一个Phone实例多线程调用两个方法,其中第一个线程调用的方法中加延迟,问哪一个先执行?
package eight_locks;import java.util.concurrent.TimeUnit;public class Demo1 {public static void main(String[] args) {Phone p = new Phone();// 发短信new Thread(()->{p.sendMessage();}).start();// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 打电话new Thread(()->{p.call();}).start();}
}class Phone {public synchronized void sendMessage(){// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(4);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}public synchronized void call(){System.out.println("打电话");}
}输出:
发短信
打电话
结果分析
- synchronized修饰普通方法锁住的是方法的调用者,也就是Phone资源类的对象实例
- 因为是第一个线程先拿到 Phone资源类 的锁对象,所以第一个输出的还是发短信
3.创建一个Phone实例多线程调用两个方法,其中一个是普通方法,而且该线程位置靠后,问哪一个先执行?
package eight_locks;import java.util.concurrent.TimeUnit;public class Demo3 {public static void main(String[] args) {Phone3 p = new Phone3();// 发短信new Thread(()->{p.sendMessage();}).start();// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 打电话new Thread(()->{p.watchMovie();}).start();}
}class Phone3 {public synchronized void sendMessage(){// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}public synchronized void call(){System.out.println("打电话");}public void watchMovie(){System.out.println("看电影");}
}输出:
看电影
发短信
结果分析
watchMovie()
为普通方法,不受 锁 的影响,即使第一个线程获取到了Phone资源类的对象锁,到第二个线程执行的时候,照样可以去执行普通方法- 因为
sendMessage()
方法体中有延迟语句,因此会后输出
4.创建两个Phone实例多线程调用两个方法,问哪一个先执行?
import java.util.concurrent.TimeUnit;public class Demo4 {public static void main(String[] args) {Phone4 one = new Phone4();Phone4 two = new Phone4();// 发短信new Thread(()->{one.sendMessage();}).start();// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 打电话new Thread(()->{two.call();}).start();}
}class Phone4 {public synchronized void sendMessage(){// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}public synchronized void call(){System.out.println("打电话");}}输出:
打电话
发短信
结果分析
- 因为synchronized锁住的是调用方法的对象,上面Phone资源类有两个不同的象,所以一、二两个线程调用方法互不影响
- 之所以,打电话 先输出,是因为 发短信方法中进行了睡眠
5.创建一个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?
import java.util.concurrent.TimeUnit;public class Demo5 {public static void main(String[] args) {Phone5 one = new Phone5();// 发短信new Thread(()->{one.sendMessage();}).start();// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 打电话new Thread(()->{one.call();}).start();}
}class Phone5 {public synchronized static void sendMessage(){// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}public synchronized static void call(){System.out.println("打电话");}}输出:
发短信
打电话
问题分析
- 加上static关键字之后,两个方法都变为静态方法。
- 发短信 在前面的原因是 synchronized 加 静态方法 锁的是 Class ,
Phone5.Class
只有单个。因此第二个线程需要 等待第一个线程释放Class锁才能执行。
6.创建两个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?
import java.util.concurrent.TimeUnit;public class Demo6 {public static void main(String[] args) {Phone6 one = new Phone6();Phone6 two = new Phone6();// 发短信new Thread(()->{one.sendMessage();}).start();// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 打电话new Thread(()->{two.call();}).start();}
}class Phone6 {public synchronized static void sendMessage(){// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}public synchronized static void call(){System.out.println("打电话");}}输出:
发短信
打电话
结果分析
- 两个对象的Class只有一个Phone6.Class
- synchronized 加 静态方法 锁的是 Class
7.创建一个Phone实例多线程调用两个方法,其中一个有static修饰,问哪一个先执行?
import java.util.concurrent.TimeUnit;public class Demo7 {public static void main(String[] args) {Phone7 one = new Phone7();// 发短信new Thread(()->{one.sendMessage();}).start();// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 打电话new Thread(()->{one.call();}).start();}
}class Phone7 {public synchronized static void sendMessage(){// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}public synchronized void call(){System.out.println("打电话");}}输出:
打电话
发短信
结果分析
- 打电话 先输出的原因是 Phone7 实例 和 Phone7.Class 分别被锁,一个是对象锁,一个是类锁,所以第二个线程照样可以去调用打电话的方法,两个线程之间并无影响
- 打电话先执行主要是因为在发短信的方法中有线程延迟
- 再次 证明 synchronized 锁的是 类实例即对象 、synchronized 加 静态方法 锁的是 Class
8.创建两个Phone实例多线程调用两个方法,其中一个有static修饰,问哪一个先执行?
package eight_locks;import java.util.concurrent.TimeUnit;public class Demo7 {public static void main(String[] args) {Phone8 one = new Phone8();Phone8 two = new Phone8();// 发短信new Thread(()->{one.sendMessage();}).start();// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 打电话new Thread(()->{two.call();}).start();}
}class Phone7 {public synchronized static void sendMessage(){// JUC下的线程延时方法try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("发短信");}public synchronized void call(){System.out.println("打电话");}}输出:
打电话
发短信
结果分析
- 原理同7,两个线程分别锁的是 Phone8.Class 和 Phone8实例,因为线程延迟,打电话 才会优先输出。
JUC并发编程笔记1__JUC概述、虚假唤醒问题、JUC的生产者和消费者关系、8锁问题(对象锁、类锁区别)相关推荐
- 【尚硅谷】大厂必备技术之JUC并发编程——笔记总结
[JUC并发编程01]JUC概述 关键字:进程和线程.进程和线程.wait和sleep.并发与并行.管程.用户线程和守护线程 [JUC并发编程02]Lock接口 关键字:synchronized.Lo ...
- JAVA并发编程 之 LMAX Disruptor使用实例(高效解决生产者与消费者问题)
什么是Disruptor? Disruptor是一个开源的JAVA框架,它被设计用于在生产者-消费者(producer-consumer problem,简称PCP)问题上获得尽量高的吞吐量(TPS) ...
- 基于《狂神说Java》JUC并发编程--学习笔记
前言: 本笔记仅做学习与复习使用,不存在刻意抄袭. -------------------------------------------------------------------------- ...
- JUC并发编程(java util concurrent)(哔站 狂神说java juc并发编程 摘录笔记)
JUC并发编程(java util concurrent) 1.什么是JUC JUC并不是一个很神秘的东西(就是 java.util 工具包.包.分类) 业务:普通的线程代码 Thread Runna ...
- 厚积薄发打卡Day26:狂神说Java之JUC并发编程<代码+笔记>(上)
前言: 学习视频来源:[狂神说Java]JUC并发编程最新版通俗易懂 一个十分优秀且励志的技术大牛+Java讲师,十分推荐他的频道:遇见狂神说
- 多线程进阶=》JUC并发编程
多线程进阶=>JUC并发编程 1.什么是JUC JUC是java.util.concurrent的简写. 用中文概括一下,JUC的意思就是java并发编程工具包. 并发编程的本质就是 ...
- Java JUC并发编程详解
Java JUC并发编程详解 1. JUC概述 1.1 JUC简介 1.2 进程与线程 1.2 并发与并行 1.3 用户线程和守护线程 2. Lock接口 2.1 Synchronized 2.2 什 ...
- JUC并发编程小总结
JUC是Java编发编程中使用的工具类,全称为java.util.concurrent.近期在大厂面试中屡屡被问到关于JUC的相关知识点问题,其重要性不言而喻,学好用好JUC可以说是每一个Java程序 ...
- Java并发编程笔记之 CountDownLatch闭锁的源码分析
转 自: Java并发编程笔记之 CountDownLatch闭锁的源码分析 JUC 中倒数计数器 CountDownLatch 的使用与原理分析,当需要等待多个线程执行完毕后在做一件事情时候 C ...
最新文章
- adding oracle jvm 慢,java – 什么JVM优化导致这些性能结果?
- 【转载】请问Silverlight 获取客户端网卡mac码
- Nginx 反向代理时获取用户的真实 IP
- securecrt自动发送空格防止session卡死
- openshift4离线部署_OpenShift 4.2 离线安装补充记录
- 基于MATLAB的turbo码代码,一种基于Simulink的Turbo码仿真实现
- list接口中的常用方法例子
- 微服务升级_SpringCloud Alibaba工作笔记0009---阿里云部署微服务_在内网不同机器上_报错_注册中心找不到对应的机器_遇到java.net.UnknownHostExceptio
- Windows 1.0
- 虚拟钢琴音源插件-GSi Genuine Sounds Vol.I Piano Edition v1.0.2 CE-win
- unity3D实现小游戏案例--弹开小球
- C# 获取打印机状态
- 广州银行冲刺A股上市:不良贷款规模突破100亿元,不良率飙升
- 电脑C盘满了怎么办?教您3招快速释放C盘空间
- linux脚本编写图形,shell图形化界面脚本实现
- 操作系统笔记(3)——同步与互斥
- 2014暑假学习总结
- ubuntu中利用LTSP搭建无盘工作站
- 寒江独钓-Windows内核安全编程总结
- SIM 卡接口电平转换