前言#
面试的时候被问起了这个东西,在开发应用的时候确实用的不多,对他的用法记得也不是很清晰了,但是还是记得几个关键字和api,例如wait(),notify()等等,面试结束之后仔细回忆一下,才有种恍然大悟的感觉,看来应该好好巩固一下了。
正文#
<h2>Synchronize</h2>
说到同步这个概念,首先想起的就是synchronize,这个在应用开发还是非常常用的,例如单例模式:
/**
* Created by li.zhipeng on 2017/4/17.
*
* 一个单例模式的工具类
*/
public class MainActivityController {
private static MainActivityController instance;
private static Object obj = new Object();
private MainActivityController(){}
/**
* synchronized 修饰的同步方法
* */
public static synchronized MainActivityController getInstance(){
if (instance == null){
instance = new MainActivityController();
}
return instance;
}
/**
* 含有synchronized 同步快的方法
* */
public static MainActivityController getInstance(){
if (instance == null){
synchronized (obj){
instance = new MainActivityController();
}
}
return instance;
}
}
synchronized有两种用法,上面已经都展示了:
1、synchronized修饰方法,表示不同线程访问相同对象的相同方法,必须要排队,相当于synchronized对这个对象上了锁,只能获取这个对象的锁的线程才能使用这个方法,使用完毕自动释放锁。
2、synchronized修改某一段代码,指定这段代码块要同步的对象进行上锁解锁。例如例子中的代码,先去判断intance是否初始化,没有就对obj进行上锁,防止创建多次,破坏了单例模式。
通过这两种用法,我们总结一下他们的好处和坏处:
1、synchronized修饰方法,使用简单,但是效率低下,不需要同步的操作也被迫同步。
2、synchronized代码块,使用相对复杂,需要对功能逻辑有完整的了解,但是仅仅是同步了某一块代码,效率也大幅提升。
<strong>注意:synchronized代码块指定同步对象不能为空对象。</strong>
<h2>Wait()、notify()、notifyAll()</h2>
另外一种线程同步方法,就是Object自带的wait,notify,notifyAll方法,当我们刚刚接触java的时候,就必须要会写这个东西,典型例子就是生产者和消费者,让我们来回顾一下实现的过程。
1、生产者有5个面包,每两秒生产一个面包。
2、消费者吃完一个面包的时间为1秒。
3、当没有面包时,消费者需要等待生产者去生产面包。
4、当生产者已经有 5个面包的库存,就停止生产,小于5个面包继续生产。
ok,需求已经弄清楚了,下面就开始着手实现这个功能,通过面向对象来模拟一下真实场景,我们需要创建三个类:
1、后厨(Cooker),负责生产面包,作为土豪,我决定雇佣5个后厨。
2、店面服务台,负责通知厨房生产面包,通知顾客来取面包。
3、顾客,复杂消费面包,拿完就走。
ok,做生意,肯定要先有个店面装修一下,所以我们来先写店铺的代码:
/**
* Created by li.zhipeng on 2017/4/17.
* <p>
* 店铺前台bean
*/
public class Shop {
/**
* 这是一个静态变量,显示目前的面包数量
*/
public int CAKE_NUMBER = 5;
/**
* 这是告诉生产者开始生产的信号
*/
public Object cookerBell = new Object();
/**
* 通知消费者来取面包的信号
*/
public Object customerBell = new Object();
/**
* 通知后厨生产面包
*/
public void notifyCook() {
synchronized (cookerBell) {
cookerBell.notify();
}
}
/**
* 通知消费者来取面包
*/
public void notifyEat() {
synchronized (customerBell) {
customerBell.notify();
}
}
}
实现了通知后厨和顾客的相关功能,然后我开始布置后厨,我对后厨的工作做了一下安排:
/**
* Created by li.zhipeng on 2017/4/17.
* <p>
* 生产者线程
*/
public class CookerThread extends Thread {
private Shop shop;
/**
* 开始生产的信号器
* */
private Object bell;
public CookerThread(Shop shop) {
super();
this.shop = shop;
this.bell = shop.cookerBell;
}
@Override
public void run() {
try {
while (true) {
// 小于5个开始生产
if (shop.CAKE_NUMBER < 5) {
cook();
}
//当面包数量大于等于5个时,暂停生产
else {
Log.e("CookerThread", "库存5个已满,暂停生产...");
// 对bell进行上锁,等待唤醒
synchronized (bell) {
bell.wait();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
// 如果生产出错了
finally {
}
}
private synchronized void cook() {
Log.e("CookerThread", "面包生产中...");
// 数量加1,告诉其他人,我已经开始做了,所以提前+1
shop.CAKE_NUMBER++;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 通知前台生产完成,可以取面包了
shop.notifyEat();
}
}
注释已经写的很详细了,需求就是如果库存小于5个,开始生产,大于5个就暂停生产。
然后我把已经研究很久的顾客消费行为调查表作为参考,对顾客的消费流程进行了以下安排:
/**
* Created by li.zhipeng on 2017/4/17.
* <p>
* 消费者线程
*/
public class CustomerThread extends Thread {
private Shop shop;
/**
* 可以取面包的信号
*/
public Object customerBell;
public CustomerThread(Shop shop) {
super();
this.shop = shop;
this.customerBell = shop.customerBell;
}
@Override
public void run() {
// 当没有面包的时候,等待
while (shop.CAKE_NUMBER <= 0) {
// 等待叫号
synchronized (shop.customerBell) {
try {
Log.e("CustomerThread", "面包库存不足,等待生产...");
shop.customerBell.wait();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
}
}
}
// 有面包就消费
eat();
}
private void eat() {
// 吃完一个面包,通知店铺生产面包
shop.CAKE_NUMBER--;
shop.notifyCook();
Log.e("CustomerThread", "消费者吃掉一个面包,目前库存" + shop.CAKE_NUMBER + "个...");
}
}
顾客拿完面包就走,一身轻松,如果没有面包了就等待一会。
经过我的精心安排,店铺终于开张了:
public class MainActivity extends AppCompatActivity {
private Shop shop;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
shop = new Shop();
/**
* 启动五个生产者
* */
for (int i = 0; i < 5; i ++){
new CookerThread(shop).start();
}
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new CustomerThread(shop).start();
}
});
}
}
上面的代码也没啥说的,雇佣了5个后厨,然后每开一次门,就来了一个消费者。
快来看看店铺的运行情况:
我截取了一段log,从log上看,生产和消费比较稳定,从此走上人生巅峰不是梦。
总结一下wait / notify:我们看到了Obejct的wait和notify的使用都是依赖于synchronized,这是为什么呢?首先我们需要排除两个干扰性的概念
1、wait/notify 是Obejct的方法,不是单单只是Thread,理解不清晰,就很容易混淆,觉得wait/notify是线程的专有特性。
2、使用了synchronized,指定你要wait/notify绑定的对象,例如你需要面包就去绑定面包,不能绑定到店铺上去,当面包准备好的时候,需要通过面包才能找到你,起到了一个绑定的作用。
这就很容易理解多线程同步的思路了,其实跟单用synchronized的中心思想是类似的,只不过在这个基础上增加了手动等待和手动通知的功能,而之前是自动的。
总结#
好像又找到了当初刚刚加入IT大军的感觉,随着工作,某些方面的技术我们越来越强,但是同时也慢慢的淡忘了一些东西。所以不能膨胀,脚踏实地才是硬道理啊。
刚刚了解到还有一个新的类ReentrantLock,就下一篇再说吧,我也需要好好的研究一下。