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

前言#

面试的时候被问起了这个东西,在开发应用的时候确实用的不多,对他的用法记得也不是很清晰了,但是还是记得几个关键字和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,就下一篇再说吧,我也需要好好的研究一下。

点击下Demo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容