(二十一)线程同步机制——线程锁synchronized
0 线程安全
例子:电影院买票,多人同时购买。一张票被同时购买,(多个线程访问共享数据(同一张电影票))产生了线程安全问题。
public class SaleTickets implements Runnable {
private static int trickets=3;
@Override
public void run() {
while (true){
if (trickets>0){
System.out.println("在卖第"+trickets+" 张票");
trickets--;
}
}
}
public static void main(String[] args){
//模拟卖票案例
//只创建一个实现类,让多个线程都访问这个实现类
SaleTickets s1 = new SaleTickets();
Thread t1 = new Thread(s1);
Thread t2 = new Thread(s1);
t1.start();
t2.start();
}
运行结果:
1 为什么同一张票被买了2次,或者出现了0/-1张票?
因为当第一个线程执行完打印操作,就失去了控制权,不能够进行其他操作(对票的加或者减),然后第二个线程得到了控制权,也真好打印了买票语句,因为第一个线程还没有来得急对票的数量进行操作,所以第二个线程也会打印一次这句话。同理,出现了0/-1票,就是当还有一张票的时候,线程都恰好运行到完了if控制语句,所以才会出现0/-1票数。
2 线程同步
①同步代码块
Synchronized(锁对象){ 可能出现线程安全的代码块 }
锁对象,就是一个对象,可以是任意的,但每个线程使用的锁对象必须要是同一个
作用:把代码块封锁,只让一个线程在代码快里面运行。
②同步方法
将共享的代码块抽取出来,封装成一个方法,在该方法的声明中添加synchronized关键词即可。然后在调用该方法。原理和同步代码块差不多,把方法里面的代码锁住,只不过同步锁对象是实现runnable接口的实现类本身。
③ Lock接口锁
lock接口. jdk1.5之后才出现.方便用户了解什么时候得到或者释放锁
lock()获取锁 ;unlock()释放锁;
用法: 1 创建一个lock的实现类reentrantion对象。2 在可能会出现线程安全的代码前调用lock()。3 当完成了,释放锁。
3 synchronized锁对象同步机制的原理
锁对象即同步锁,也叫对象监视器。当一个线程执行的时候,遇到了synochronized同步代码块,线程会检查是否有锁对象,若有,则会获取锁对象,然后进入同步代码块中执行。当另一个线程执行到synochronized同步代码快的时候,也会检查是否有锁对象,因为锁对象被第一个线程获取了,他获取不到,所以该线程会进入阻塞状态,直到第一个线程执行完毕后,释放了锁对象。它才会继续执行,就像第一个线程一样。获取锁—执行同步代码—释放锁。
也就是说,一个线程没有执行完毕,是不会让另一个线程去执行的。另一个线程想要执行,必须等到别的线程执行完毕释放锁才可以执行。
优缺点:同步保证了线程的安全,却降低了线程的效率,因为需要不停的判断和释放。