1,互锁现象
上一部分介绍了通过同步的方法来解决多线程操作共享数据时的安全问题,然而同步也有相应的缺点:
- 效率降低
- 出现互锁现象
效率问题就不在多说了,同步固有的问题;那么什么是互锁现象了;介绍一个场景:
周末到了,玉皇大帝一男一女两个神仙道地球上去寻找最后两个幸存者:why,male; jr,female;来让他们创造人类,并且说了,谁先找齐两个人类,将会升官涨工资;他们彼此的法力是不相上下的,也就是找到任何一个人的概率是一样;他们都有自己的小心思,就是找到之后爱护有加的保管起来,决不能让对方找到。还有就是为了在寻找的时候更加开心快乐,男神仙决定先找妹子,这样就可以带着妹子再去找男的,岂不是爽歪歪;女神仙当然想先找帅哥了,理由同上啦,于是他们出发了...
场景介绍完了,代码模拟一下:
两个人类:
/**
* @author why
* @date 2018年3月10日
* @description:
*/
package hfut.com.test1;
public class LockList {
public static final Object whyLock=new Object();
public static final Object jrLock=new Object();
}
出行任务:
/**
* @author why
* @date 2018年3月10日
* @description:
*/
package hfut.com.test1;
public class TaskOnSunday extends Thread {
private boolean flag;
public TaskOnSunday(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
// TODO Auto-generated method stub
if (flag) {
synchronized (LockList.whyLock) {
System.out.println(Thread.currentThread().getName() + ":我已经找到了一个男人,还需要一个妹子");
synchronized (LockList.jrLock) {
System.out.println(Thread.currentThread().getName() + ":我已经找到了一个男人和一个妹子,现在可以创造人类了");
}
}
} else {
synchronized (LockList.jrLock) {
System.out.println(Thread.currentThread().getName() + ":我已经找到了一个妹子,还需要一个男人");
synchronized (LockList.whyLock) {
System.out.println(Thread.currentThread().getName() + ":我已经找到了一个男人和一个妹子,现在可以创造人类了");
}
}
}
super.run();
}
}
两个神仙开始寻找:
/**
* @author why
* @date 2018年3月10日
* @description:测试类
*
* 1,两个神仙周末获取一个任务,找地球上的唯一一个男人和唯一一个妹子创造人类
* 2,如果他两个这周末能完成任务,玉皇大帝有奖励
* 3,两个神仙一个是男神仙,一个女神仙;男神仙肯定想先找妹子,再带妹子一起去
* 另一个男人;女神仙恰好相反;
* 4,由于法力相当,所以他们找到任意一个人的能力是一样的;并且找到了之后,就会
* 保管起来
*/
package hfut.com.test1;
public class TestDemo {
public static void main(String[] args) {
Thread t1=new TaskOnSunday(false);
Thread t2=new TaskOnSunday(true);
t1.setName("男神仙");
t2.setName("女神仙");
t1.start();
t2.start();
}
}
最终结果:
图 1
最后,两个人找了两天都没有找齐,回来发现,原来另一个人被对方找去了;关键之前还不知道,还在不停的找,哎...;不过他们还是很开心的,毕竟这个周末他们都没有单身度过。具体的原理就不分析了,可以去我的上一部分关于同步解决多线程共享数据安全问题中找到原因。
2,等待唤醒机制
所谓等待唤醒机制,是针对不同种类线程而言的,不像之前的多线程,都是围绕一个任务来完成;等待唤醒机制用于在不同种类线程间处理单个子任务的逻辑关系从而保证完成整个任务的场景;听起来很绕口,其实很简单,比如:
一个蛋糕店,有很多人来买蛋糕,厨房还有很多人在做蛋糕;那么做蛋糕是一个子任务,买蛋糕是一个子任务;完成这个交易需要这两个子任务组合起来,并且只有在有蛋糕的情况下,才可以执行买蛋糕操作。
模拟示例代码:
蛋糕:
/**
* @author why
* @date 2018年3月10日
* @description:
*/
package hfut.com.test2;
public class SharedPerson {
public boolean isExists = false;
private String name;
private int age;
public static int index = 1;
/**
* @param name
* @param age
*/
public SharedPerson(String name, int age) {
super();
this.name = name;
this.age = age;
}
@Override
public String toString() {
// TODO Auto-generated method stub
return "姓名:" + this.name + ";" + "年龄:" + this.age;
}
}
做蛋糕:
/**
* @author why
* @date 2018年3月10日
* @description:
*/
package hfut.com.test2;
public class MakePersonThread implements Runnable {
private SharedPerson person;
/**
* @param person
*/
public MakePersonThread(SharedPerson person) {
super();
this.person = person;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (person) {
while (person.index<= 100) {
if (!person.isExists) {
System.out.println("我刚刚制造了第" + person.index + "个人:" + person.toString());
person.isExists = true;
} else {
try {
person.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
person.isExists = false;
}
person.notify();
}
}
}
}
买蛋糕:
/**
* @author why
* @date 2018年3月10日
* @description:
*/
package hfut.com.test2;
import javax.swing.plaf.synth.SynthSpinnerUI;
public class DestroyPerson implements Runnable {
private SharedPerson person;
/**
* @param person
*/
public DestroyPerson(SharedPerson person) {
this.person = person;
}
@Override
public void run() {
synchronized (person) {
while (person.index <= 100) {
if (!person.isExists) {
try {
person.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
System.out.println("我刚刚消灭了第" + person.index + "个人:" + person.toString());
person.isExists=false;
person.index++;
}
person.notify();
}
}
}
}
蛋糕交易:
/**
* @author why
* @date 2018年3月10日
* @description:
*/
package hfut.com.test2;
public class TestDemo {
public static void main(String[] args) {
SharedPerson person = new SharedPerson("why", 24);
MakePersonThread mpt = new MakePersonThread(person);
DestroyPerson dp = new DestroyPerson(person);
Thread makePerson = new Thread(mpt);
Thread destroyPerson = new Thread(dp);
makePerson.start();
destroyPerson.start();
}
}
交易结果:
图 2
总交易结果看很完美,就造一个买一个(我这里用人来替代,原理是一模一样的);在这里就是用等待唤醒机制,简单说明几点:
(1)等待唤醒机制使用了wait()和notify()方法(也可以使用notifyAll()方法)
(2)上面上个方法是Object类中的,因为这几个方法是通过同步锁对象来调用的,而同步锁对象可以为任意对象,所以这几个方法定义在Object中很合理
(3)使用wait()方法后会释放同步锁对象,这样其他的线程就可以获取该锁对象进入业务处理;并且在重新获取锁对象的时候,会再次进入同样的代码位置
(4)两个线程必然有逻辑上的业务关系,不然这种用法毫无意义。
(5)等待唤醒机制本质上是线程间通讯的一种体现或者实现方式
总结:关于互锁现象与等待唤醒机制就说到这里了,我们还可以仔细思考一下两者的共性和在具体业务中能不能转换以及如何转换等;举一反三吗。