多线程学习一

先看一张状态图(图片来源于网络)

1.join

如图中所示,一个线程调用join后,进入阻塞状态。join的作用就是等待一个线程并暂停自己,直到等待的那个线程结束为止:

    public static void main(String[] args) {
        Thread A = new Thread(){
            @Override
            public void run() {
                System.out.println("A start");
                try {
                    sleep(2000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println("A end");
            }
        };
        Thread B = new Thread(){
            @Override
            public void run() {
                System.out.println("B start");
                try {
                    A.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("B end");
            }
        };
        A.start();
        B.start();      
    }

测试结果:

第一次:
A start
B start
A end
B end
第二次:
B start
A start
A end
B end

由于B调用了join,所以无论B是否先执行,都是等到A执行完毕后再执行到结束。

2.wait/notify

这两个是配合使用的,而且这两个方法是不Thread特有的,而是属于Object,java中所有类顶级父类都是Object,所以所有类都有这两个方法。他们出现的原因是,在对临界资源访问时需要加锁,但有时候需要放弃已有的锁,所以就出现了wait,让出当前的锁,让其他申请锁的线程得以执行,另外线程执行完之后需要调用notify,使之前让出的依旧持有锁从而可以运行。我们先看一个没有wait的例子

    Object lock = new Object();
    public static void main(String[] args) {
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("A start");
                synchronized (lock) {
                    System.out.println("A 1"); 
                    try {
                        Thread.currentThread().sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }               
                    System.out.println("A 2");
                    System.out.println("A 3");
                }
                System.out.println("A end");
            }
        });

        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("B start");
                synchronized (lock) {
                    System.out.println("B 1");
                    System.out.println("B 2");
                    System.out.println("B 3");
                }
                System.out.println("B end");
            }
        });
        A.start();
        try {
            Thread.currentThread().sleep(500);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        B.start();  
    }

运行结果

A start
A 1
B start
A 2
A 3
A end
B 1
B 2
B 3
B end

可见虽然B启动了,但临界区的锁被A持有,所以要等A临界区代码执行完毕后再执行B的邻接区代码,下面再看有wait的部分:

    public static void main(String[] args) {
        Object lock = new Object();
        Thread A = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("A start");
                synchronized (lock) {
                    System.out.println("A 1"); 
                    try {
                        lock.wait();
                    } catch (InterruptedException e1) {
                        // TODO Auto-generated catch block
                        e1.printStackTrace();
                    }
                    try {
                        Thread.currentThread().sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }               
                    System.out.println("A 2");
                    System.out.println("A 3");
                }
                System.out.println("A end");
            }
        });

        Thread B = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("B start");
                synchronized (lock) {
                    System.out.println("B 1");
                    System.out.println("B 2");
                    System.out.println("B 3");

                    lock.notify(); 
                }
                System.out.println("B end");
            }
        });
        A.start();
        try {
            Thread.currentThread().sleep(500);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        B.start();  
    }

测试结果

A start
A 1
B start
B 1
B 2
B 3
B end
A 2
A 3
A end

可见A的确让出了锁资源,B得到执行,最后B唤醒A,A继续执行。如果B执行完之后没有调用notify,A是不会继续执行的,有疑问的可以试试。还有一个notifyAll方法,功能类似,是唤醒多个wait的线程

3.CountdownLatch

这是一个类似计数器的类,用于一个线程等待一定数量的线程都达到某种条件后才执行的场景,示例

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(3);

        new Thread(){
            @Override
            public void run() {
                System.out.println("A 开始等待");
                try {
                    countDownLatch.await();
                    System.out.println("A 等待结束");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();

        IntStream.of(1,2,3).forEach(i->{
            String name = "Thread"+i;
            new Thread(){
                @Override
                public void run() {
                    System.out.println(name + "开始执行");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(name + "执行完毕");
                    countDownLatch.countDown();
                }
            }.start();
        });
    }

结果:

A 开始等待
Thread2开始执行
Thread1开始执行
Thread3开始执行
Thread2执行完毕
Thread3执行完毕
Thread1执行完毕
A 等待结束

CountdownLatch的构造方法需要传入一个等待时数目,在需要等待的线程中调用await方法,之后每当一个线程执行完毕或在适当时候调用countDown,将计数器减一,减到0时,原来等待的线程就可以执行了

4.CyclicBarrier

这是一个类似于计数器的类,当一定数量的线程都达到某种状态时,这些线程才可以继续执行,示例:

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
        Random random = new Random();
        IntStream.of(1,2,3).forEach(i -> {
            String name = "Thread" + i;
            new Thread(){
                @Override
                public void run() {
                    setName(name);
                    System.out.println(getName() + "开始准备");
                    try {
                        Thread.sleep((long) (Math.random() * 1000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName()+"准备完毕");
                    try {
                        cyclicBarrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } 

                    System.out.println("所有线程准备完毕" + getName() + "开始执行");
                }
            }.start();
        });
    }

运行结果:

Thread2开始准备
Thread1开始准备
Thread3开始准备
Thread2准备完毕
Thread3准备完毕
Thread1准备完毕
所有线程准备完毕Thread1开始执行
所有线程准备完毕Thread3开始执行
所有线程准备完毕Thread2开始执行

CyclicBarrier的构造函数需要传入等待时数量,然后每个线程在合适的时候调用await等待其他线程,当指定数量的线程都调用await后,所有线程一起开始执行。
CyclicBarrier构造还可以传入一个Runnable,当调用awit的线程达到一定数量时,会挑选一个线程率先执行这个Runnable.如下:

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3,()->System.out.println(Thread.currentThread().getName() + "+++"));
        Random random = new Random();
        IntStream.of(1,2,3).forEach(i -> {
            String name = "Thread" + i;
            new Thread(){
                @Override
                public void run() {
                    setName(name);
                    System.out.println(getName() + "开始准备");
                    try {
                        Thread.sleep((long) (Math.random() * 1000));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName()+"准备完毕");
                    try {
                        cyclicBarrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } 

                    System.out.println("所有线程准备完毕" + getName() + "开始执行");
                }
            }.start();
        });
    }

Thread1开始准备
Thread3开始准备
Thread2开始准备
Thread1准备完毕
Thread2准备完毕
Thread3准备完毕
Thread3+++
所有线程准备完毕Thread1开始执行
所有线程准备完毕Thread3开始执行
所有线程准备完毕Thread2开始执行

5.Semaphore

类似于我们学过的进程同步的PV操作,看一个生产者消费者的例子:

    public static void main(String[] args) throws InterruptedException {
        Semaphore apple = new Semaphore(0);
        Semaphore plate = new Semaphore(1);
        Thread Producer = new Thread(){
            @Override
            public void run() {
                // TODO Auto-generated method stub
                super.run();
                try {
                    System.out.println("申请盘子");
                    plate.acquire();
                    System.out.println("获取盘子");
                    System.out.println("生成苹果");
                    apple.release();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        };
        Thread Consumer = new Thread(){
            @Override
            public void run() {
                // TODO Auto-generated method stub
                super.run();
                try {
                    System.out.println("申请苹果");
                    apple.acquire();
                    System.out.println("获取苹果");
                    System.out.println("释放盘子");
                    plate.release();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        };
        Consumer.start();
        Thread.sleep(1000);
        Producer.start();
    }

申请苹果
申请盘子
获取盘子
生成苹果
获取苹果
释放盘子

用起来很简单,acquire申请一个资源,release释放一个,同样也可以选择调用重载方法,一次申请或释放多个,构造方法也可以传入一个布尔类型变量,表示是否公平分配(若为false,表示多个线程竞争一个资源时,谁等的时间长,谁优先)。另外acquire为阻塞方法,一个线程可能无限等待,所有有非阻塞的方法tryAcquire,调用availablePermits检测当前可用资源数。

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