多线程学习一

2018-05-31  本文已影响0人  留给时光吧
先看一张状态图(图片来源于网络)

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检测当前可用资源数。

上一篇下一篇

猜你喜欢

热点阅读