锁与同步

前几种保证线程安全的方式:

思想缺陷
避免共享不能用全局rep共享数据
只共享只读类型只能“读”共享数据,不能写
使用线程安全类型可以共享 “读写”,但只有单一方法是安全的,多个方法调用就不安全了

可以看出,这些方式都有很大的局限性,很多时候,无法满足上述三个条件。
常见的情况是要读写共享数据,且线程中的 读写操作复杂。

这时候,我们就要使用 同步和锁的方式,通过“同步” 策略,负责多线程之间对mutable数据的共享操作,同时避免多线程同时访问数据。
使用锁机制,获得对数据的独家mutation权,其他线程被阻塞,不得访问
等待锁的使用者释放这个锁,然后再去抢这个锁的使用权

线程锁🔒

Lock是Java语言提供的内嵌机制 ,每个 object都有相关联的lock ,可以把每个对象都当作一把锁使用,包括object类本身,并不直接提供acquire 和release 关键字,而是synchronized 作为锁的关键字
同步代码块:

synchronized (lock) {
	// thread blocks here until lock is free
	// now this thread has the lock
	balance = balance + 1;
	// exiting the block releases the lock
}

拥有lock的线程可独占式的执行该部分代码
利用synchronized将程序变成顺序执行的程序

对一个对象的操作,如果是互斥的,必须使用同一个lock进行保护

任何共享的mutable变量/对象必须被lock所保护

synchronized(this){
...
}

包括类中的每一个方法
可以保证ADT中的所有的方法操作都是原子的

同步方法:

public synchronized void operation

保证方法是同步的,是一种简写,不需要指定锁,使用的锁都是this
构造方法不需要也不可是使用synchronized标记,因为JVM保证构造方法都是单线程的

死锁

死锁:多个线程竞争lock,相互等待对方释放lock
例如:

T1: synchronized(a){ synchronized(b){} } 
T2: synchronized(b){ synchronized(a){} }

由图可见,是一种循环依赖
在这里插入图片描述
举一个例子:

public synchronized void friend(Wizard that) {
	if (friends. add(that)) {
		that.friend(this);
	}
}

线程A:harry.friend(snape);

线程B:snape.friend(harry);

首先,harry和snape类中的friend()方法都依赖this作为锁,即harry的锁为harry,而snape的锁为snape,显然之前没有任何锁占用,两个方法很好的拿到了锁并对本类执行.add()方法,但是,执行完这个方法之后,还需要对that执行.friend(this)方法,因此两个类互相等待对方的类将锁释放,而对方的类又等待对方的类释放锁,导致了死锁。

解决死锁问题:
1.对锁进行排序
大家都按照锁的顺序进行调用锁,就不会出现死锁了

public void friend(Wizard that) {
	Wizard first, second;
	if (this.name . compareTo(that.name) <0) {
		first = this;
		second = that;
	} else {
		first = that;
		second = this;
	}
	synchronized (first) {
		synchronized (second) {
			if (friends . add(that)) {
				that . friend(this);
			}
		}
	}

在代码中,对人物按照首字母字典序进行排序,每个friend方法中所依赖锁的顺序都是相同的,自然不会出现死锁的状态

但是需要知道要有多少的锁,需要知道子系统中有多少锁,使得无法模块化
2.用单个锁锁定多个对象实例/子系统,扩大锁的范围

public class Wizard {
	private final Castle castle;
	private final String name;
	private final Set<Wizard> friends ;
	public void friend(Wizard that) {
		synchronized (castle) {
			if (this. friends. add(that)) {
				that . friend(this);
				}
		}
	}			

会导致很大的性能损失,丧失并发性

实例:

Thread 1:
synchronized (alpha) {
	// using alpha 
	// ... }
	synchronized (gamma) {
		synchronized (beta) {
			// using beta & gamma // ... 
		} 
	}
} // finished
Thread 2:
synchronized (gamma) {
	synchronized (alpha) {
		synchronized (beta) {
			// using alpha, beta, & gamma
			 // ... 
			 }
	  } 
} // finished
Thread 3:
	synchronized (gamma) {
		synchronized (alpha) {
			// using alpha & gamma 
			// ... 
		} 
	}
			
		synchronized (beta) {
			synchronized (gamma) {
				// using beta & gamma 
				// ... 
			} 
	
// finished
}

情况一:

Thread 1 inside using alpha
 Thread 2 blocked on synchronized (alpha) 
 Thread 3 finished

此时不会产生死锁,线程1运行结束释放了alpha,线程2便可以允许alpha和beta,完成后释放,A可以继续运行

情况二:

Thread 1 finished 
Thread 2 blocked on synchronized (beta) 
Thread 3 blocked on 2nd synchronized (gamma)

此时变化产生死锁,线程2拿到了gamma和aplha而等待beta ,而线程3拿到了beta等待gamma的释放,发生了死锁

情况三:

Thread 1 running synchronized (beta)
 Thread 2 blocked on synchronized (gamma)
 Thread 3 blocked on 1st synchronized (gamma)

此时可能会产生死锁: 当线程1运行结束后释放 beta和gamma后,三把锁都处于空闲状态,此时线程2和3都需要gamma锁,
如果先分配给线程2的话,线程2可以继续拿到alpha和beta,进而运行后释放三把锁,gamma锁被释放后线程3继续运行,不会产生死锁;
如果gamma先分配给线程3的话,其使用过gamma和alpha并释放后 ,线程2会使用gamma和alpha,进而导致线程3在拿到beta后无法拿到gamma,而线程2无法拿到beta,就会导致死锁。

情况四:

Thread 1 blocked on synchronized (beta)
 Thread 2 finished 
 Thread 3 blocked on 2nd synchronized (gamma)

这是个典型的死锁现象

对锁的手动控制

  • Object.wait() :该操作使object所处的当前线程进入阻塞/等待状态,直到其他线程调用该对象的notify()操作
  • Object.notify() :随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态
  • Object.notifyAll():唤醒所有在对象上调用wait方法的线程,解除其阻塞状态
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值