2020-09-15 Java线程安全
一、线程安全简介
单线程程序不会产生线程安全问题。
多线程程序没有访问共享数据,也不会产生问题。
多线程程序访问共享数据,会产生线程安全问题。
三个窗口卖票Demo:
线程安全1.png
线程安全2.png
线程安全问题(出现了重复的票和不存在的票)产生原因:
假设t0、t1、t2都执行进if后立即失去话语权,这时再随机醒来,就会打出
“tx...正在卖第1张票”(此时ticket-- 即-0)循环结束
“ty...正在卖第0张票”(此时ticket-- 即-1)循环结束
“tz...正在卖第-1张票”(此时ticket-- 即-2)循环结束
假设t0、t1、t2都执行到输出语句时立即失去话语权,这时再随机醒来,就会打出卖出了相同的票。
二、线程同步
为了解决每个子线程都能正常进行原子操作的安全问题,Java提供了同步机制(synchronized)来解决。
有三种方式:
1.同步代码块;
2.同步方法;
3.锁机制。
2.1同步代码块(对某个区域中的资源实行互斥访问)
格式:
synchronized(同步锁){
//需要同步的代码
}
同步锁:在对象上标记了一个锁,可以使任意类型;多个线程对象要使用同一把锁。(注意:在任何时候,最多允许一个线程拥有同步锁,谁拿锁谁就进入代码块,其他线程只能等待BLOCKED)
如下图,只要把imp类如此修改即可解决上面安全问题:
原理:
三个线程一起抢夺CPU执行权,谁抢到了谁卖票。
1.t0抢到了执行run方法,遇到synchronized代码块,这时会检查synchronized同步代码块是否有锁对象;
发现有(因为是第一个执行),t0就会获得锁对象,进入执行。
2.t1抢到了执行run方法,遇到synchronized代码块,这时会检查synchronized同步代码块是否有锁对象;
发现没有,进入到阻塞状态,一直等待t0线程归还所对象。
一直到t0执行完同步中的代码,会归还锁对象给同步代码块,t1才能获得锁对象进入其中执行。
同步中的线程没有执行完毕不会释放锁,同步外的线程没有锁进不去同步。保证了安全,程序频繁的判断锁、获取锁、释放锁、程序效率会降低
2.2同步方法
格式:
puvlic synchronized void method(){
//需要同步的代码
}
此时的同步锁:对于非static方法,同步锁就是this;对于static方法,同步锁为当前方法所在类的字节码对象(类名.class)
2.3同步锁
JDK1.5之后提供了更强大的Lock锁(同步锁),可获得更广发的操作。
java.util.concurrent.locks.Lock接口:
public void lock():加锁
public void unlock():释放锁
使用:java.util.concurrent.locks.Lock.ReentrantLock
1.创建ReentrantLock对象
2.在可能出现安全问题的代码前调用 lock();
3.在可能出现安全问题的代码后调用unlock();(建议写在finally中)
下一章介绍线程的状态和等待唤醒机制