目录
- 回顾
- synchronized下的生产者、消费者关系之虚假唤醒问题
- JUC下的生产者、消费者关系
- 8锁问题(对象锁、类锁)
- 创建一个Phone实例多线程调用两个方法,问哪一个先执行?
- 创建一个Phone实例多线程调用两个方法,其中第一个线程调用的方法中加延迟,问哪一个先执行?
- 创建一个Phone实例多线程调用两个方法,其中一个是普通方法,而且该线程位置靠后,问哪一个先执行?
- 创建两个Phone实例多线程调用两个方法,问哪一个先执行?
- 创建一个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?
- 创建两个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?
- 创建一个Phone实例多线程调用两个方法,其中一个有static修饰,而且调用线程在前面,问哪一个先执行?
- 创建两个Phone实例多线程调用两个方法,其中一个有static修饰,而且调用线程在前面,问哪一个先执行?
一、回顾
概念
- 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();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
data.increment();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程A").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
data.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程B").start();
}
}
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();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
data.increment();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程A").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
data.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程B").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
data.increment();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程C").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
data.decrement();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程D").start();
}
}
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
虚假唤醒问题分析
三、JUC下的生产者、消费者关系
Lock下的线程通信
- Condition接口取代了Object监视器方法(wait、notify、notifyAll)使用自己的方法完成线程间通信
- 使用lock对象的newCondition()方法可以获得condition对象,该对象有一个await()方法和singal()方法
- 同时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();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
data.increment();
}
} catch (Exception e) {
e.printStackTrace();
}
},"线程A").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
data.decrement();
}
} catch (Exception e) {
e.printStackTrace();
}
},"线程B").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
data.increment();
}
} catch (Exception e) {
e.printStackTrace();
}
},"线程C").start();
new Thread(()->{
try {
for (int i = 0; i < 10; i++) {
data.decrement();
}
} catch (Exception e) {
e.printStackTrace();
}
},"线程D").start();
}
}
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对象服务一个线程,因为加锁了,所以其他线程正在等待,而有执行条件的线程会接到通知从
等待--->执行--->更新num状态---->等待
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;
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;
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;
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方法:
imeUnit.SECONDS.sleep(1);
package eight_locks;
public class Demo1 {
public static void main(String[] args) {
}
}
class Phone {
public synchronized void sendMessage(){
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
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();
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("打电话");
}
}
输出:
发短信
打电话
结果分析
- 因为synchronized关键字 是对 该资源类的对象 上锁,因此哪个线程先拿到对象锁,就先执行
- 因此上面的线程先拿到锁,先执行,如果还不理解接着看问题2
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();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
p.call();
}).start();
}
}
class Phone {
public synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
输出:
发短信
打电话
结果分析
- 原理同上。还是上面的线程先拿到 资源类 锁对象
- 同一资源类下,synchronized修饰的方法 使用的是当前资源类 对象锁。谁先拿到谁执行。
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();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
p.watchMovie();
}).start();
}
}
class Phone3 {
public synchronized void sendMessage(){
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() 为普通方法,不受同步方法的影响,也就是不受 锁 的影响- 因为
sendMessage() 方法体 中有延迟语句,因此会后输出,其实 抛去延迟来说,两个输出结果为不一定
4.创建两个Phone实例多线程调用两个方法,问哪一个先执行?
package eight_locks;
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();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
two.call();
}).start();
}
}
class Phone4 {
public synchronized void sendMessage(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
输出:
打电话
发短信
结果分析
- 区别于问题1,该情况是 两个资源类对象分别开启两个线程,因此锁对象 并无互相干扰,因为线程延时的原因,打电话 先输出
- 由此可在次证明synchronized加载方法前面锁住的是 调用该方法的 实例对象,多个实例之间的锁 不干扰
5.创建一个Phone实例多线程调用两个方法,两个方法都有static修饰,问哪一个先执行?
package eight_locks;
import java.util.concurrent.TimeUnit;
public class Demo5 {
public static void main(String[] args) {
Phone5 one = new Phone5();
new Thread(()->{
one.sendMessage();
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
one.call();
}).start();
}
}
class Phone5 {
public synchronized static void sendMessage(){
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修饰,问哪一个先执行?
package eight_locks;
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();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
two.call();
}).start();
}
}
class Phone6 {
public synchronized static void sendMessage(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized static void call(){
System.out.println("打电话");
}
}
输出:
发短信
打电话
结果分析
- 两个对象的Class只有一个Phone6.Class
- 原理同5,synchronized 加 静态方法 锁的是 Class
7.创建一个Phone实例多线程调用两个方法,其中一个有static修饰,而且调用线程在前面,问哪一个先执行?
package eight_locks;
import java.util.concurrent.TimeUnit;
public class Demo7 {
public static void main(String[] args) {
Phone7 one = new Phone7();
new Thread(()->{
one.sendMessage();
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
one.call();
}).start();
}
}
class Phone7 {
public synchronized static void sendMessage(){
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();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
two.call();
}).start();
}
}
class Phone7 {
public synchronized static void sendMessage(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
输出:
打电话
发短信
结果分析
- 原理同7。两个线程分别锁的是 Phone8.Class 和 Phone8实例,因为线程延迟,打电话 才会优先输出。
|