面试总结(2):线程同步

来源:互联网 发布:软件导刊怎么样 编辑:程序博客网 时间:2024/05/16 18:18

前言

面试的时候被问起了这个东西,在开发应用的时候确实用的不多,对他的用法记得也不是很清晰了,但是还是记得几个关键字和api,例如wait(),notify()等等,面试结束之后仔细回忆一下,才有种恍然大悟的感觉,看来应该好好巩固一下了。

正文

Synchronize

说到同步这个概念,首先想起的就是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代码块,使用相对复杂,需要对功能逻辑有完整的了解,但是仅仅是同步了某一块代码,效率也大幅提升。

注意:synchronized代码块指定同步对象不能为空对象。

Wait()、notify()、notifyAll()

另外一种线程同步方法,就是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,就下一篇再说吧,我也需要好好的研究一下。

点击下Demo

0 0
原创粉丝点击