并发
同一个对象被多个线程同时操作
线程同步
解决方案:队列 + 锁
线程同步其实是一种等待机制,多个需要同时访问该对象的线程进入 这个对象的 等待池,形成队列。等待前面线程使用完毕,下一个线程再使用。
关键词 synchronized
存在的问题:
- 导致性能下降,一个线程持有锁,会导致其他所有需要此锁的线程挂起
- 多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待优先级低的线程释放锁,会导致优先级倒挂,引起性能问题。
// 用在代码块上
synchronized() {
// 同步代码块
}
// 用在方法声明上, 一定是用this对象锁,表示整个方法体都需要同步。
// 可能会无故扩大同步范围,导致程序执行效率降低,所以不太常用。
public synchronized void withdraw() {
...
}
// 在静态方法上使用synchronized,找的是类锁
// 一个对象一把锁,100个对象100把对象锁。但是100个对象,只有一个类锁
public synchronized static void withdraw() {}
synchronized后面小括号中的数据是多线程共享的数据,才能达到多线程排队。
哪些变量
- 实例变量:堆中
- 静态变量:方法区中
- 局部变量:栈中
- 常量:不可修改
常量不可修改,局部变量不共享,所以局部变量和常量永远不会有 线程安全问题。但是实例变量和静态变量可能存在线程安全问题。
同步范围越小越好
所以在局部变量时,建议使用StringBuilder,因为局部变量不存在线程安全问题,StringBuilder也没有同步机制
等待/通知机制
需要注意的是等待/通知机制使用的是必须使用同一个对象锁,如果你两个线程使用的是不同的对象锁,那它们之间是不能用等待/通知机制通信的
synchronized版
package com.liangwei.kuang.demo02;
/**
* 线程间通信问题:生产者、消费者模型! 等待唤醒,通知唤醒
* 线程交替执行,生产者 和 消费者 操作同一个变量 num=0
* 生产者 生产 使 num++
* 消费者 消费 使 num--
*/
public class Demo02 {
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();
}
}
}, "producer").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrease();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "consumer").start();
}
}
/**
* 生产者、消费者模型:等待、业务、通知
*/
class Data {
private int num = 0;
public synchronized void increment() throws InterruptedException {
if(num != 0) { // 如果num不为0,就等待,num为0就工作
// 等待
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "->" + num);
// 通知
this.notify();
}
public synchronized void decrease() throws InterruptedException {
if(num == 0) { // 如果num为0就等待,不为0就开始工作
// 等待
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "->" + num);
// 通知
this.notify();
}
}
问题:如果有四个线程的情况下A,B, C, D,两个生产两个消费 —-》 虚假唤醒问题
CPU底层的唤醒实现机制,注定了虚假唤醒是存在的。因此,在 if 语句下,如果线程被虚假唤醒了,但是又没有获得锁,会被