什么是线程安全问题?
当A和B两个线程,执行的方法都是向内存的一块数组中插入数据。当A找到数组下标为0的位置的时候,正准备插入a。。这时,时间片到了,到点了。B来了,它也找到数组下标为0的位置,它的时间比较充裕,B可以插入数据b。然后,A有时间片了,就会将a直接插入到数组小标为0的位置。所以此时数组小标为0的位置是a,b插入的数据就丢失了
多线程安全问题:
当多线程并发访问临界资源时,如果破坏原子操作(找位置,插入数据的操作),可能会造成数据不一致
什么是临界资源
临界资源:共享资源,就相当于上面讲的数组,一次仅允许一个线程使用,才可以保证其正确性
原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省
举一个例子讲一下 线程安全的问题:
代码就是 创建AB两个线程,向数组中的同一个位置0进行插入数据的操作。打印的结果的可能性有3种:
第一种情况:先插入了hello数据,然后world由于时间片被抢,所以把world重新赋值给了hello位置
代码使用了Join方法,确保在主线程之前执行线程a和线程b
如何使线程变得安全?
我们使用synchronized对临界资源上锁,在代码块里执行操作
synchronized(临界资源对象){ // 对临界资源对象加锁
// 代码(原子操作)
}
注:每个对象都有一个互斥锁标记,用来分配给线程的
只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步代码块中
线程退出同步代码块时,会释放相应的互斥锁标记
注意同步代码块 可以修改为 同步方法
这里我们把在run方法里面的代码抽取出来形成一个新的sell()方法,并在方法名前加了synchronized同步
注意:这个同步方法锁的对象就是this,当前的类的对象ticket
改一下:在方法前面加一个static,请问现在的锁是谁??? 答案:是Ticket.class
这个和上面静态的 同步方法等价
小结:如果是非静态方法,锁就是当前this对象。如果是静态方法,锁就是当前的类
同步规则:
能不加锁就不加,加锁会导致程序的效率降低
已知JDK中线程安全的类,均为synchronized修饰的同步方法:
- stringBuffer
- Vector
- Hashtable