线程安全
如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的。
线程不安全
线程不安全的原因
-
从代码层面看:多个线程对同一变量的操作(读、写),有一个写操作,就有线程安全问题
-
从原理看:
(1)原子性:多行执行代码,执行时是一组不可分割的最小执行单位,多个线程同时并发执行的代码指令,可能在一个线程操作一个共享变量时,是有前后依赖关系的,指令之间有其他的线程操作,就会导致想线程不安全(2)可见性
主存:线程都使用的共享区域,对其中变量/对象的操作
工作内存:线程之间是互相不可见的。CPU执行线程中代码指令,是从主存复制到CPU高速缓存。
(3)有序性
代码重排序:Java代码顺序是固定的。但是JVM执行字节码或者CPU执行机器码,都可能重排序指令顺序,目的是为了提高运行效率,重排序会考虑指令前后的依赖关系。
如何解决线程安全问题
一组代码,如果存在多线程对共享变量的操作,都需要考虑线程安全问题。把多线程操作的共享变量,称为临界资源,一组代码称为临界区。
synchronized关键字
synchronized的底层是使用操作系统的mutex lock实现的
- 当线程释放锁时,JMM会把该线程对应的工作内存中的共享变量刷新到主存中
- 当线程获取锁时,JMM会把线程对应的本地内存置为无效。从而使得被监视器保护的临界区代码必须从主存中读取共享变量
- synchronized用的锁是存在Java对象头里的
使用:
- 在静态方法上:加锁整个方法,锁对象为当前的类对象(Class<当前类型>,可能加锁多个多想)
public class SynchronizedDemo {
public synchronized static void methond() {
}
public static void main(String[] args) {
method(); // 进入方法会锁 SynchronizedDemo.class 指向对象中的锁;出方法会释放
SynchronizedDemo.class 指向的对象中的锁
}
}
//锁对象为SynchronizedDemo.class
2.在实例方法上:加锁整个方法,锁对象为this指向对象(只加锁一个对象)
public class SynchronizedDemo {
public void methond() {
// 进入代码块会锁 this 指向对象中的锁;出代码块会释放 this 指向的对象中的锁
synchronized (this) {
}
}
public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();
demo.method();
}
}
- 同步代码块:synchronized(锁对象){
}
作用:对同一对象加锁的线程,造成同步互斥的作用(多个线程依次执行临界区代码)
public class SynchronizedDemo {
public void methond() {
// 进入代码块会锁 SynchronizedDemo.class 指向对象中的锁;出代码块会释放
SynchronizedDemo.class 指向的对象中的锁
synchronized (SynchronizedDemo.class) {
}
}
synchronized加锁的原理
- 本质上是对对象头进行加锁,对同一个对象加锁的线程,是同步互斥
- synchronized底层原理是基于操作系统的锁实现的
volatile关键字
作用:
- 修饰共享变量,可以保证可见性,保证顺序性
- 建立一个内存屏障,
class ThraedDemo {
private volatile int n;
}
应用场景:
- 多线程对共享变量的操作,如果代码本身保证了原子性,就可以不加锁,只使用volatile保证可见性
- 多线程代码的设计目标:线程安全下尽可能提高效率
临界区代码越多,多线程对临界区加同一个锁,进行同步互斥,执行临界区代码的效率越低
提高效率优化方案:
- 锁的细粒度化(临界区代码行越少)、(共享变量写操作的代码行)
- 哪些代码不加锁也能保证线程安全
以上,总体可以称为读写分离(读读并发,读写并发,写写互斥)
通信-对象的等待集
-
wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁
-
notify()和notifyAll()的作用,唤醒当前对象上的等待进程;notify()是唤醒单个线程,notifyAll()是唤醒全部线程
-
wait(long timeout)让当前线程处于“等待(阻塞)状态”,直到其他线程调用此对象的notify()方法或notifyAll(),或超过指定的时间量,当前线程被唤醒(进入就绪态)
wait和sleep的对比
- wait()用于线程通信,sleep()用于线程等待
- wait之前需要请求锁,而执行wait时会先释放锁,等线程被唤醒时再重新请求锁。这个锁是wait对象上的monitor lock
- wait 是object的方法
- sleep是Thread的静态方法