Java之线程同步浅析

目录

一、为什么需要线程同步

二、为什么会出现竞态条件        

三、锁对象

        1、ReentrantLock-重入锁

        2、synchronized

四、volatile


一、为什么需要线程同步

          我们知道,线程的使用可以适用于大量并发的场景,比如购买车票这样的场景,当多个线程需要共享对同一数据的存储,比如车的座位数,如果多个窗口卖票,相当于多个线程对这辆车的剩余座位数做修改,卖票或退票。这样将会导致多个线程相互覆盖,最终实际剩余座位数和记录的座位数不相符。这种情况就是“竞态条件”。

二、为什么会出现竞态条件        

        出现“竞争条件”现象的原因是由于我们的操作不熟原子性的,比如对剩余座位数count的处理如下:

        count = count - 1;        

        这行代码执行需要做下面三步:

        1、将count加载到寄存器;

        2、减去1;

        3、将结果写回到count。

假如现在剩余座位数为10个,线程一操作减1,执行步骤1、2后,它的运行权被强占交给给了线程二,线程二减1,执行步骤1、2、3。这个时候剩余座位数就是9。这个时候线程一被唤醒了,线程一接着执行步骤三,最后写回到的结果还是9。那线程二减去的一张票相当于被抹除掉了。最后是不是就会出现拿着车票的人去坐车,发现车票总数大于车的总座位数。

三、锁对象

        有两种机制可以防止并发访问代码块:ReentrantLock和Synchronized。

        1、ReentrantLock-重入锁

用法如下

public class Car {
	private Condition condition;
	private ReentrantLock reentrantLock;
	
	public Car() {
		reentrantLock = new ReentrantLock();//构造重入锁
		condition = reentrantLock.newCondition();//返回一个与这个锁相关联的条件对象
	}
	
	public void calculate(int remain, int buy) {
		condition = reentrantLock.newCondition();
		reentrantLock.lock();//获得锁,如果锁当前被其他线程占用,则阻塞
		try {
			while(remain < buy) {//如果不满足当前操作条件,比如购买数大于剩余数
				condition.await();//将该线程放入这个条件的等待集中
			}
            remain -= buy;
			condition.signalAll();//解除该条件等待集中的所有线程的阻塞状态
			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally {
			reentrantLock.unlock();/释放锁,unlock必须放在finally中,也就是说最后锁必须释放,不然其他线程会永远阻塞。
		}
	}
}

        ReentrantLock能够保证只有一个线程进入临界区,一旦有一个线程锁定了锁对象,其他线程都无法通过loak语句,当其他线程调用lock时,他们会暂停,直到第一个线程释放这个锁对象。有时即使这个线程成功进入临界区,却发现线程需进行的操作依然无法执行,比如,并不满足执行的条件,这个时候我们可以通过一个条件对象来管理这个线程reentrantLock.newCondition()。在当前线程不满足执行的条件下,调用condition.await()进入这个条件的等待集中,线程暂停,并放弃锁,有其他线程进行操作,直到其他线程调用condition.signalAll()激活这个条件的等待集中的所有线程,一旦锁可用,这些线程会重新竞争这个锁,等到锁后,会从之前暂停的地方继续执行。

        调用signalAll来唤醒所有等待集中的所有线程能有效的避免死锁,死锁的原因是所有的线程都被阻塞,程序永远挂起。使用signalAll只是解锁等待线程的阻塞,给他们在当前线程释放锁后竞争访问对象的机会。

        还有一种解除等待状态的方法condition.signal(),signal只能随机解除等待集中一个线程的阻塞状态,这可定要比signalAll更更高效,但也会更容易存在被解除阻塞状态的线程仍旧无法满足条件,重新进入等待集中,如果没有其他线程再来调用signal,系统就会进入死锁。

        2、synchronized

        synchronized是Java的一个内部锁,从1.0版本开始,Java中的每个对象都有一个内部锁,如果一个方法声明时有synchronized关键字,那么这个对象的锁就会抱回整个方法。synchronized内部锁只有一个关联条件,wait方法将一个线程增加到等待集中,notify()和notifyAll()可以解除等待线程的阻塞。使用方法如下:

public synchronized void calculate(int remain, int buy) throws InterruptedException {
	while(remain < buy) {
		wait();
	}
	remain -= buy;
	notifyAll();
}

        此外,还有一种机制可以获得锁,就是同步块,用法如下:

private Object lock = new Object();
public void calculate(int remain, int buy) {
	synchronized (lock) {
		remain -= buy;
	}
}

四、volatile

        volatile关键字为实例字段的同步访问提供了一种免锁机制。用法如下:

private volatile boolean isChecked;

在单例模式中双重校验锁实现方式使用的就是volatile和synchronized,如下:

public class Singleton {
	private volatile static Singleton singleton;
	private Singleton() {}
	public static Singleton getInstance() {
		if (singleton == null) {
			synchronized (Singleton.class) {
				if (singleton == null) {
					singleton = new Singleton();
				}
			}
		}
		return singleton;
	}
	public void showMessage() {
		System.out.println("......!!!!");
	}
}

 

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值