上篇文章:java多线程解说【叁】_Thread的常用API实现
上文中介绍了Thread的API,那么在多线程编程中,最常遇到的一个问题就是线程之间相互竞争而引起运行结果与预期不符,这个问题也称为竞态条件。为了避免这个问题的发生,我们需要在资源竞争的时候引入锁的概念。在java语言多线程编程中常用的锁方式有以下几种:
1.Object的wait()/notify();
2.Lock/Condition;
3.ReentrantReadWriteLock读写锁;
4.LockSupport的park()/unpark();
本篇文章先说一下wait()/notify()的锁实现:
Object的八个基本方法
首先说说java语言中万物之本Object的八大基本方法吧。
记得去年参加云栖大会的时候,期间我参加了一场《阿里巴巴java开发手册》作者孤尽老师的演讲会,会上他提出了一个观点:Object的八个方法,基本上覆盖了这个对象的一生。
getClass是用来解释自己是谁;toString是输出自己想说的话;hashCode是自己的唯一标识(身份证);equals是证明自己是自己;wait和notify是有序地在这个世界上做一些事情;clone是繁衍下一代(可能会有一些修改);finalize是相当于自己的遗嘱。
而这里要说的,就是wait和notify的使用,我们前文介绍的Thread.join()方法就是基于wait()/notify()实现的。
Object的wait()/notify()
wait和notify的使用都有一个前提,就是当前线程需要持有要操作对象的monitor,在之前介绍synchronized的文章中介绍过,当线程进入synchronized中后,就已经持有了该对象的monitor并阻止其他线程的进入,直到它退出synchronized块。
wait()方法就是让当前线程在该对象的等待集合里"休眠",不会被调度器调度,同时释放掉该对象的锁。线程在如下四种情况会被唤醒:
1.另一个线程调用了notify方法;
2.另一个线程调用了notifyAll方法;
3.另一个线程调用了该线程的interrupt方法,会抛出InterruptedException异常;
4.wait(long mills)等待时间已过;
当前线程被唤醒的时候,它就从该对象的等待集合中移除并接受调度器的调度。其实所谓的调度,可以理解为等待对方线程让出该对象的锁,同时它还需要和其他线程平等地去争抢该对象的锁,获取到了锁便是得到了调度。获取到调度之后,结束wait方法,同时仍然持有该对象的锁,继续执行同步块中的代码。
需要注意的一点是,一个等待的对象可能被欺骗唤醒,所以wait方法最好在一个循环里,循环的条件是当前线程需要等待的条件。
至于唤醒的notify()方法没有太多需要介绍的,有一点需要注意的是它的唤醒是随机的,如果在该对象上等待的线程很多那么它只会随机唤醒一个,不能指定。
一个wait/notify的例子
public class TestWaitNotify {
public static String obj = "123";
public static void main(String[] args) throws Exception{
Thread thread1 = new Thread("t1"){
@Override
public void run() {
try {
System.out.println("thread1已开始运行...");
System.out.println("5s等待开始...");
Thread.sleep(5000);
synchronized (obj) {
obj.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
System.out.println("thread1已释放obj的锁");
}
}
};
Thread thread2 = new Thread("t2"){
@Override
public void run() {
try {
System.out.println("thread2已开始运行...");
synchronized (obj) {
while(obj.equals("123")){
obj.wait();
System.out.println("thread2重新已拿到obj的锁");
obj = "456";
}
}
} catch (Exception e) {
e.printStackTrace();
}finally{
System.out.println("thread2已执行完毕");
System.out.println("obj="+obj);
}
}
};
thread1.start();
thread2.start();
}
}
通过上文我们知道,wait()/notify()模式的唤醒是随机唤醒的,那么在实际开发工作中如果我们想唤醒指定的线程,那么应该怎么去做呢?请看下文:
java多线程解说【伍】_锁实现:ReentrantLock的实现
java多线程解说【柒】_锁实现:Lock/Condition的例子