第三章 多线程通信

第三章 多线程通信

3.1 wait()和notify()

java.lang.Object 内置了用于线程通信的方法 wait()和notify()和notifyAll()

public final native void wait(long timeout) throws InterruptedException;
public final native void notifyAll();
public final native void notify();

注:带native的方法都不是由java实现,而是由其他语言进行实现的。

调用Object类的wait()方法之后线程会进入WAITING状态,直到有其他的线程调用notify()或者notifyAll(),他才会重新进入RUNNABLE状态,在调wait()方法之前首先要确保当前线程已经获得了监视器锁,因为调用Wait()方法的时候,线程需要释放监视器锁。

(wait()为空或者为0,代表一直等待唤醒,而wait(time) 代表一定时间内需要唤醒,时间到了自然醒。 )

3.1.1 阻塞当前线程
public class waitAndNotify {

    public static void main(String[] args) throws InterruptedException {
        Object object=new Object();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1;i<=10;i++){
                    System.out.println(Thread.currentThread().getId()+"i的值为:"+i);
                    if (i==5){
                        synchronized (object){
                            try {
                                System.out.println("开始等待");
                                object.wait();
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }
                }

            }
        }).start();
        //确保唤醒线程在等待线程之后启动
        Thread.sleep(100);

        new Thread(new Runnable() {
            @Override
            public void run() {
                        synchronized (object){
                            System.out.println("唤醒睡着的");
                            object.notify();
                        }

            }
        }).start();
    }
}

注:锁哪个对象就只能调用哪个对象的wait方法,object.notify()也只会唤醒在object下wait()的线程,并且唤醒之后之后不是立马继续运行而是进入抢占锁的阶段,需要再次获得对象的监视器锁才能继续进行运行。

3.1.2 案例分析: 厨师和侍者(1)
3.1.3 案例分析: 厨师和侍者(2)
public class cook {
    //厨师要做的订单
    public static Integer num1 = 0;

    //顾客下的订单数
    public static Integer num2 = 0;

    //服务员未上的菜品数
    public static Integer num3= 0;
    /**
     * 厨师,服务员,顾客
     */

    public static void main(String[] args) throws InterruptedException {

        //厨师
        Object cook = new Object();

        //上菜服务员
        Object waiterServing = new Object();

        //下单服务员
        Object waiterToCook = new Object();

        //厨师线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("厨师开始上班");
                while (true){
                    synchronized (cook){
                        try {
                            if (num1 == 0){
                                System.out.println("厨师在等待做饭");
                                cook.wait();
                            }
                            System.out.println("............厨师还有:"+num1+"未做");
                            System.out.println("开始做饭");
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    synchronized (waiterServing){
                        System.out.println("做出了一个美味");
                        System.out.println("准备让上菜的服务员上菜");
                        num1--;
                        num3++;
                        System.out.println("...........厨师剩余:"+num3+"道菜未上");
                        //1号
                        waiterServing.notify();
                    }
                }
            }
        }).start();

        //上菜线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("上菜服务员开始上班");
                while (true){
                        synchronized (waiterServing){
                            try {
                                if (num1 == 0){
                                    System.out.println("没有菜要上在等待");
                                }else {
                                    System.out.println("等待厨师做饭");
                                }
                                //不用担心1号会在wait()之前执行,因为三个线程同时开始等待的地方就是这里,所在在1号唤醒线程之前,这里肯定是等待状态
                                waiterServing.wait();
                                System.out.println("服务员去上菜");
                                num3--;
                                System.out.println(".............还剩:"+num3+"道菜未上");
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                }
            }
        }).start();

        //下单服务员线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("下单服务员开始上班");
                while (true){
                    System.out.println("下单服务员等待开工");
                    if (num2 == 0){
                        synchronized (waiterToCook){
                            try {
                                System.out.println("没有订单等待");
                                //下单线程被唤醒之后,下面会唤醒厨师线程
                                waiterToCook.wait();
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                    }else {
                        synchronized (cook){
                            System.out.println("服务员接到下单叫厨师做饭");
                            cook.notify();
                            //不用担心顾客下单的时候修改num2的值,因为只有当num2为0的时候两个线程才会去竞争
                            //waiterToCook的监视器锁,但是当此线程获得监视器锁的时候,会进入到等待状态并且释放监视器锁
                            //然后下单的线程获取到监视器,这不会引起什么冲突。
                            num2--;
                            num1++;
                        }
                    }
                }
            }
        }).start();
        //确保服务员和厨师上班后,顾客在点单
        Thread.sleep(1000);
        //点单线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i<= 3 ; i++){
                    synchronized (waiterToCook){
                        System.out.println("顾客"+i+"下单");
                        num2++;
                        waiterToCook.notify();
                        System.out.println(".........当前顾客所下并且未通知厨师的订单"+num2);
                    }
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }).start();
    }

    /**
     *  1. 厨师,下单服务员,上菜服务员三个线程启动之后全部进入WAITING状态,并且释放监视器锁
     *  2. 顾客线程启动之后,进行下单之后,会唤醒下单服务员的线程
     *  3. 下单服务员的线程被唤醒之后,他会唤醒厨师线程,给厨师设置没有做的菜数num1,如果没有新的订单的时候,下单服务员会再次进入等待状态,等待被唤醒
     *  4. 厨师线程被下单线程唤醒之后,开始做饭,做完饭之后会唤醒上菜服务员线程,给上菜服务员设置num3,未上的菜品数,如果没有菜要做,厨师会再次进入等待状态
     *  5. 上菜服务员唤醒之后,开始上菜,没有菜要上就开始等待或者等待厨师做饭
     */
}
3.1.4 案例分析: 两个线程交替输出信息
public class Test {

    public static void main(String[] args) {
        Object object = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (object){
                        System.out.println("aa");
                        //这里的唤醒和等待的位置有讲究,(保证两个线程不会都释放锁,进入等待状态)
                        object.notify();
                        try {
                            object.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    synchronized (object){
                        System.out.println("bb");
                        object.notify();
                        try {
                            object.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }

                    }
                }
            }
        }).start();
    }
}
3.2 join 线程排队

线程A调用线程B的join方法,会阻塞当前A线程,知道B线程完成或者超时,A线程菜才能继续运行。

public class join {
    /**
     * 当两个线程同时运行之后可以调用join方法,阻塞当前线程,知道另一个线程运行结束之后才会继续运行当前线程。
     */
    public static void main(String[] args) {
        Thread mm = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                for (int j=0;j<=5;j++){
                    System.out.println("22222");
                }
            }
        });
        mm.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0;i<10;i++){
                    System.out.println("1111");
                    if (i==5){
                        try {
                            mm.join();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }).start();


    }

输出:

1111
1111
1111
1111
1111
1111
22222
22222
22222
22222
22222
22222
1111
1111
1111
1111
3.2.2 案例:紧急任务处理

就是在运行一个线程,突然有另一个线程要加入,优先运行另一个线程,等另一个线程运行结束之后在运行当前的线程。

3.2.3 join限时阻塞

join(time)意思就是另一个线程加入之后,只会阻塞time秒,时间结束之后。不管有没有运行完成,继续运行自己的。

join()方法的底层实现依赖的是wait()方法,执行新线程执行join()之后进入WAITING状态,join()延时结束之后,通过notify()通知,会唤醒处于WAITING状态的休眠线程。

3.3 线程中断

想要线程中断可以使用interrupt()方法,我们还可以通过isinterrupt(),判断当前线程是否处于中断状态。

interrupt()方法并不能马上停止线程的运行,他只是给线程设置一个中断状态值,这相当于一个停止线程运行的建议,线程能否停止,由操作系统和CPU决定。

3.3.1 中断运行态线程
public class interrupt {

    public static void main(String[] args){
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<10;i++){
                    if (Thread.currentThread().isInterrupted()){
                        System.out.println("收到中断通知了");
                        break;
                    }else {
                        System.out.println("1111");
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                        }

                    }

                }
            }
        });
        t.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        t.interrupt();
        System.out.println("中断状态:"+Thread.currentThread().isInterrupted());
    }
}
输出:
    可能1: (基本上都是这种情况,(最多))
        1111
        1111
        1111
        1111
        1111
        1111
        中断状态:false
        1111
        1111
        1111
        1111
   可能2:(被设置了true,但是并没有起作用)
        1111
        1111
        1111
        1111
        1111
        1111
        中断状态:true
        1111
        1111
        1111
        1111
   可能3:(被设置了true,起作用了,出现的次数很少)
        1111
        1111
        1111
        1111
        1111
        1111
        中断状态:true
        收到中断通知了
3.3.2 中断阻塞态线程

处于WAITING状态阻塞态和休眠阻塞态的线程,调用Interrupt()方法会抛出InterruptedException:异常,抛出异常之后中断状态被清楚,但是BLOCK阻塞态的线程调用Interrupt()不会产生中断影响。

3.3.3 如何停止线程

想停止一个线程最好的办法就是反复判断flag状态值,当线程需要停止时,只需要将falg设置为false就可以了。


public class stop {

    public static boolean flag= true;

    public static void main(String[] args) throws InterruptedException {



        new Thread(new Runnable() {
            @Override
            public void run() {
                while (flag){
                    System.out.println("111");
                }
            }
        }).start();

        Thread.sleep(5);
        System.out.println("中断线程");
        flag=false;
    }
}

输出:

111
111
111
111
111
111
111
中断线程
111
3.4 countDownLatch计数器

用于一个线程或者多个线程阻塞等待,countDownlatch类的构造函数需要输入一个等待的任务数量,每调用一次countDownLatch.countDown();就会减少一个等待任务的数量。一个线程启动之后调用await()方法,就会进入计数器等到,当countDownLatch.countDown();之后,getCount的数量为0的时候就会被唤醒继续执行任务。

public class CountDown {


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

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                for (int i=0;i<5;i++) {
                    System.out.println("111");
                    countDownLatch.countDown();
                    System.out.println(countDownLatch.getCount());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("222");
                try {
                    countDownLatch.await();
                    System.out.println("3333");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();

        new Thread(runnable).start();
    }
}

输出:
222
111
2
111
1
111
0
3333
111
0
111
0

CountDownLatch 提供了一些方法:
await():

​ 使当前线程进入同步队列进行等待,直到latch的值被减到0或者当前线程被中断,当前线程就会被唤醒。
await(long timeout, TimeUnit unit):

​ 带超时时间的await()。
countDown():

​ 使latch的值减1,如果减到了0,则会唤醒所有等待在这个latch上的线程。
getCount():

​ 获得latch的数值。

3.5 cyclicBarrier 屏障

屏障适用于这种情况,你想创建一组任务的时候,他们并行的去工作,然后同一在下一步任务前等待。屏障可以重复使用

屏障的使用,定义好一个屏障,线程到达屏障的时候进行等待,直到达到临界点,全部继续执行任务,下一批继续等待达到临界点然后继续执行任务。

//屏障
public class cyclic {

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(10);

        Runnable runnable= new Runnable() {
            @Override
            public void run() {
                System.out.println("1111");
                try {
                    cyclicBarrier.await();
                    System.out.println("释放");
                    System.out.println("2222");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } catch (BrokenBarrierException e) {
                    throw new RuntimeException(e);
                }
            }
        };

        for (int i = 0;i<20;i++){
            new Thread(runnable).start();
        }

    }
}
输出:
1111
1111
1111
1111
1111
1111
1111
1111
1111
1111
释放
2222
1111
释放
2222
释放
1111
释放
2222
释放
2222
1111
1111
1111
1111
1111
释放
2222
释放
2222
1111
2222
释放
2222
释放
2222
1111
释放
2222
1111
释放
2222
释放
释放
2222
释放
2222
释放
2222
释放
2222
2222
释放
2222
释放
2222
释放
2222
释放
2222
3.6 Exchanger 交换信息通道

Exchanger类时在两个任务之间交换对象的双向通道,当任务进入通道时,他们各自拥有一个对象,当他们离开时,他们都拥有之前由对方拥有的对象。

Exchager的典型应用场景:一个任务在创建对象,一个任务在消费对象,通过Exchanger来进行对象交换,可以使对象刚创建就会马上被消费

当线程调用exchanger.exchange(user)方法的时候,会进入阻塞状态,知道另一个线程也调用exchanger.exchange(user),这时两个线程进行交换对象。

public class Exchange {

    public static void main(String[] args) {

        Exchanger<String> exchanger = new Exchanger<>();

        new Thread(new Runnable() {
            @Override
            public void run() {
                String user = "hahah";
                try {
                    Thread.sleep(5000);
                    String user2 = exchanger.exchange(user);
                    System.out.println(user+user2);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                String user = "莎侬";
                try {
                    String user2 = exchanger.exchange(user);
                    System.out.println(user+user2);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }).start();
    }
}
输出:
hahah莎侬
莎侬hahah
3.7 semphore 信号灯

semphore 信号灯他拥有许可证,调用semaphore.acquire();获取许可证,如果没有获取到则进入阻塞状态,等待其他的线程释放释放许可证,semaphore.release();释放许可证。阻塞的线程获取许可证之后继续执行任务

public class semaphoreClass {

    public static void main(String[] args) {
        Semaphore semaphore= new Semaphore(1);

        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("等待获取信号等");
                    semaphore.acquire();
                    System.out.println("获取到了信号灯");

                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("1111");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                semaphore.release();
            }
        };

        for (int i = 0; i <=5;i++){
            new Thread(runnable).start();
        }
    }
}
输出:
等待获取信号等
等待获取信号等
等待获取信号等
等待获取信号等
获取到了信号灯
1111
等待获取信号等
等待获取信号等
获取到了信号灯
1111
获取到了信号灯
1111
获取到了信号灯
1111
获取到了信号灯
1111
获取到了信号灯
1111
3.8 死锁

产生死锁的四个必要条件?

(1)互斥条件:进程对所分配到的资源不允许其他进程进行访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源
(2)请求和保持条件:进程获得一定的资源之后,又对其他资源发出请求,但是该资源可能被其他进程占有,此事请求阻塞,但又对自己获得的资源保持不放
(3)不可剥夺条件:是指进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用完后自己释放

(4)环路等待条件:是指进程发生死锁后,必然存在一个进程–资源之间的环形链

死锁发生的情况时四个条件都满足,所以只要破坏其中的一个即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值