由于多线程共享数据或资源,导致误读,脏读,造成错误,影响安全,故称多线程不安全。
实例:
模拟三个窗口售票,票源为100张票,三个窗口同时出售。
定义一个Runnable实现类
在测试main中创建三个线程,共享RunableImp中数据。
打印结果如下:
其中出现了相同票号和票号为0和-1的错误数据。说明多线程共享数据并不安全。
成因分析:
多线程共享CPU执行权,且并行运行,相互切换,造成脏读、误读。
显然,这种线程不安全在实际中是不能被接收的。
解决方案:
一个线程访问共享数据的时候,无论是否失去了CPU的执行权,让其他线程只能等待当前线程释放数据后,其他线程才能使用。
解决思想: 让需要同步的代码只能让一个线程执行。
方法一:同步代码块。 synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源进行互斥访问。
格式:
synchronized(同步锁){
// 需要同步操作的代码。
}
实例: 以之前案例说明,修改RunnableImp如下:
运行结果:
资源数值正常递减,未出现错误数据。
方法二: 同步方法 使用synchronized关键字修饰方法
格式:
public synchronized(同步锁) void method(){
// 需要同步操作的代码。
}
同步方法的锁对象就是实现类对象,也就是this.
实例如下图:
此时:锁对象是this,即类实例对象。
除此之外,还可以使用静态同步方法,此时锁对象是类的class属性 -->class文件对象。
方法三:锁机制
主要为使用Lock接口锁,其实现类主要使用Reentrantlock。
使用步骤:
1. 在成员位置创建一个Reentrantlock对象。
2. 在可能出现安全问题的代码前调用lock接口中的lock获取锁。
3. 在可能出现安全问题的代码后调用lock接口中的unlock释放锁。
实例:
打印结果如下:
注意使用lock时的代码格式,将lock.unlock 放入finally中,无论代码是否报错,都释放锁,与释放IO流同理