zoukankan      html  css  js  c++  java
  • java多线程(2)---生命周期、线程通讯

    java生命周期、线程通讯

    一、生命周期

            有关线程生命周期就要看下面这张图,围绕这张图讲解它的方法的含义,和不同方法间的区别。

       1、yield()方法

        yield()让当前正在运行的线程回到就绪,以允许具有相同优先级的其他线程获得运行的机会。但是,实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中。

        同时yield()不会放弃锁资源,所以有可能会出现死锁。

       2、wait和sleep方法的区别

    1)第一个很重要的区别就是,wait方法必须正在同步环境下使用,比如synchronized方法或者同步代码块。如果你不在同步条件下使用,会抛出IllegalMonitorStateException异常。另外,sleep方法不需要再同步条件下调用,你可以任意正常的使用。

    2)第二个区别是,wait方法用于和定义于Object类的,而sleep方法操作于当前线程,定义在java.lang.Thread类里面。

    3)第三个区别是,调用wait()的时候方法会释放当前持有的锁,而sleep方法不会释放任何锁。

       3、wait和sleep方法使用场景

         (1)wait方法定义在Object类里面,所有对象都能用到,一般wait()和notify()方法或notifyAll使用于线程间的通信

         (2)sleep()方法用于暂停当前线程的执行。

       4、join方法()

             thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程

     比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。

           这里有个待思考的案例?应该是自己对join没有理解透,留在以后再来回顾。

    /*
     * 有关join第一个疑惑就是我在t2.join()后,发现它是无效的,同样还是交叉输出。
     */
    class ThreadTesterA implements Runnable {
    
        private int counter;
    
        public void run() {
            while (counter <= 10) {
                System.out.print("Counter = " + counter + " ");
                counter++;
            }
            System.out.println();
        }
    }
    class ThreadTesterB implements Runnable {
        private int i;
        public void run() {
            while (i <= 10) {
                System.out.print("i = " + i + " ");
                i++;
            }
            System.out.println();
        }
    }
    
    public class ThreadTester {
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(new ThreadTesterA());
            Thread t2 = new Thread(new ThreadTesterB());
            t1.start();
            t2.start();
            t2.join(); //无效
    }
    }

    5、stop方法

        线程启动完毕后,在运行可能需要终止,Java提供的终止方法只有一个stop,但是不建议使用此方法,因为它有以下三个问题:

       1)stop方法是过时的。

          从Java编码规则来说,已经过时的方式不建议采用.

       2)stop方法会导致代码逻辑不完整

         stop方法是一种"恶意" 的中断,一旦执行stop方法,即终止当前正在运行的线程,不管线程逻辑是否完整,这是非常危险的.

       3)stop方法会破坏原子逻辑

          多线程为了解决共享资源抢占的问题,使用了锁的概念,避免资源不同步,但是正是因为此原因,stop方法却会带来更大的麻烦,它会丢弃所有的锁,导致原子逻辑受损

    二、线程通讯小案例

    1、如何让两个线程依次执行?

        题目:假设有两个线程,一个是线程 A,另一个是线程 B,两个线程分别依次打印 1-3 三个数字即可。我们希望 B 在 A 全部打印完后再开始打印。

    关键方法:join()

    //题目:假设有两个线程,一个是线程 A,另一个是线程 B,我们希望 B 在 A 全部打印完后再开始打印。
    public class TestJoin {
        public static void main(String[] args) {        
            demo2(); 
        }
        
        private static void demo2() {
            Thread A = new Thread(new Runnable() {
                @Override
                public void run() {
                    printNumber("A");
                }
            });
            Thread B = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("B 开始等待 A");
                    try {
                        A.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    printNumber("B");
                }
            });
            B.start();
            A.start();
        }
        
        private static void printNumber(String threadName) {
            int i=0;
            while (i++ < 3) {
                System.out.println(threadName + "print:" + i);
            }
        }
    }
    /*运行结果
     * B 开始等待 A
     * Aprint:1
     * Aprint:2
     * Aprint:3
     * Bprint:1
     * Bprint:2
     * Bprint:3
     */

    2、如何让两个线程按照指定方式有序交叉运行呢?

    题目:假设有两个线程,一个是线程 A,另一个是线程 B,两个线程分别依次打印 1-3 三个数字即可。我们希望 A和B交替打印

    关键方法:wait()和notify()或者notifyAll()

    public class Main {
        int i = 1;   //i和istrue作为多线程的共享数据
        boolean istrue = false;
    
        public static void main(String[] args) {
            Main main = new Main();
            ThreadA a = new ThreadA(main);
            ThreadB b = new ThreadB(main);
            Thread threada = new Thread(a);
            Thread threadb = new Thread(b);
            threada.start();
            threadb.start();
    
        }}
    
    class ThreadA implements Runnable {
        Main main;
    
        public ThreadA(Main main) {
            this.main = main;
        }
    
        public void run() {
            while (main.i <= 10) {
                synchronized (main) { // 必须要用一把锁对象,这个对象是main
                    if (!main.istrue) {
                        try {
                            main.wait(); // 操作wait()函数的必须和锁是同一个
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        System.out.println("奇数:" + main.i);
                        main.i++;
                        main.istrue = false;
                        main.notifyAll();
                    }
                }}}}
    
    class ThreadB implements Runnable {
        Main main;
    
        public ThreadB(Main main) {
            this.main = main;
        }
    
        public void run() {
            while (main.i <= 10) {
                synchronized (main) { // 必须要用一把锁对象,这个对象是main
                    if (main.istrue) {
                        try {
                            main.wait(); // 操作wait()函数的必须和锁是同一个
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        System.out.println("偶数:" + main.i);
                        main.i++;
                        main.istrue = true;
                        main.notifyAll();
                    }
                }}}}
    
    //梳理下流程
    //首先传入一个 A 和 B 共享的对象锁main;
    //当 A 得到锁后,直接交出锁的控制权,进入 wait 状态;
    //对 B 而言,由于 A 最开始得到了锁,导致 B 无法执行;直到 A 调用wait() 释放控制权后, B 才得到了锁,同时输出:偶数:1,同时notifyAll让A又到就绪状态
    //接下来A和B都有可能获得cpu时间碎片,当 A 得到锁后,那么打印奇数:2,如果B又获得cpu时间片,那么它会进入wait状态。
    //就这样来去循环,最终就是交叉打印运行。

     运行结果

    3、四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的。

    关键对象:CountdownLatch对象

            最开始我们介绍了 thread.join(),可以让一个线程等另一个线程运行完毕后再继续执行,那我们可以在 D 线程里依次 join A B C,不过这也就使得 A B C 必须依次执行,而我们要的是这三者能同步运行。
    或者说,我们希望达到的目的是:A B C 三个线程同时运行,各自独立运行完后通知 D;对 D 而言,只要 A B C 都运行完了,D 再开始运行。针对这种情况,我们可以利用 CountdownLatch 来实现这类通信方式。

    /*CountdownLatch基本用法是:
     * 1)创建一个计数器,设置初始值,CountdownLatch countDownLatch = new CountDownLatch(3);
     * 2)在 等待线程 里调用 countDownLatch.await() 方法,进入等待状态,直到计数值变成 0;
     * 3)在 其他线程 里,调用 countDownLatch.countDown() 方法,该方法会将计数值减小 1;
     * 4)当 其他线程 的 countDown() 方法把计数值变成 0 时,等待线程 里的 countDownLatch.await() 立即退出,继续执行下面的代码。
     */
    
    public class TestCountdownLatch {
    
        public static void main(String[] args) {
            runDAfterABC();
        }
        
        private static void runDAfterABC() {
            int worker = 3;
            CountDownLatch countDownLatch = new CountDownLatch(worker);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("D开始工作前先等ABC工作完成");
                    try {
                        
                        //因为worker初始值为3,所以在不等于0之前一直处于等待状态
                        countDownLatch.await();
                        System.out.println("ABC工作完成,D开始工作");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
            for (char threadName='A'; threadName <= 'C'; threadName++) {
                final String tN = String.valueOf(threadName);
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println(tN + "正在工作.....");
                        try {
                            Thread.sleep(100);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                        System.out.println(tN + "完成工作......");
                        
                        //每调用一次worker值减一
                        countDownLatch.countDown();
                    }
                }).start();
            }
        }    
    }

     运行结果:

    4、三个运动员各自准备,等到三个人都准备好后,再一起跑

    关键对象:CyclicBarrier

          上面的 CountDownLatch 可以用来倒计数,但当计数完毕,只有一个线程的 await() 会得到响应,无法让多个线程同时触发。

    为了实现线程间互相等待这种需求,我们可以利用 CyclicBarrier 数据结构。

    /* CyclicBarrier 基本用法
     * 1)先创建一个公共 CyclicBarrier 对象,设置 同时等待 的线程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
     * 2)这些线程同时开始自己做准备,自身准备完毕后,需要等待别人准备完毕,这时调用 cyclicBarrier.await(); 即可开始等待别人;
     * 3)当指定的 同时等待 的线程数都调用了 cyclicBarrier.await();时,意味着这些线程都准备完毕好,然后这些线程才 同时继续执行。
     */
    public class CyclicBarrierTest {
    
        public static void main(String[] args) {
            runABCWhenAllReady();
        }
        
    private static void runABCWhenAllReady() {
        int runner = 3;
        CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);
    
        for (char runnerName='A'; runnerName <= 'C'; runnerName++) {
            final String rN = String.valueOf(runnerName);
            new Thread(new Runnable() {
                @Override
                public void run() { 
                    try {
                        System.out.println(rN + " 已经准备好,等待其它线程准备");
                        cyclicBarrier.await(); // 当前运动员准备完毕,等待别人准备好
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                    System.out.println(rN + "开始跑"); // 所有运动员都准备好了,一起开始跑
                }
            }).start();
        }
    }
    }

    运行结果:

     5、子线程完成某件任务后,把得到的结果回传给主线程

     关键接口:Callable

    public class CallableTest {
    
        public static void main(String[] args) {
            doTaskWithResultInWorker();
        }
        
    private static void doTaskWithResultInWorker() {
        //看出 Callable 最大区别就是返回范型 V 结果
        Callable<Integer> callable = new Callable<Integer>() {
            
            //这里需要重写call方法,而不是run方法
            @Override
            public Integer call() throws Exception {
                System.out.println("Task starts");
                Thread.sleep(1000);
                int result = 0;
                for (int i=0; i<=100; i++) {
                    result += i;
                }
                return result;
            }
        };
        //Callable需要把对象放入FutureTask对象中,在把FutureTask对象放入Thread中,就可以启动一个线程
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        new Thread(futureTask).start();
        try {     
            System.out.println("Result: " + futureTask.get());
        } catch (Exception e) {
            e.printStackTrace();
        } }
    }
    /*输出结果:
     * Task starts
     * Result: 5050
     */

           这里我们可以学到,通过 FutureTask 和 Callable 可以直接在主线程获得子线程的运算结果,只不过需要阻塞主线程。当然,如果不希望阻塞主线程,可以考虑利用 ExecutorService,把 FutureTask 放到线程池去管理执行。

     想太多,做太少,中间的落差就是烦恼。想没有烦恼,要么别想,要么多做。少校【8】

  • 相关阅读:
    vmware ubuntu 异常关机无法连接到网络
    Speed up GCC link
    常用的一些解压命令
    Log4j 漏洞复现
    Test Case Design method Boundary value analysis and Equivalence partitioning
    CCA (Citrix Certified Administrator) exam of “Implementing Citrix XenDesktop 4”
    What is Key Word driven Testing?
    SAP AGS面试小结
    腾讯2013终端实习生一面
    指针的引用
  • 原文地址:https://www.cnblogs.com/qdhxhz/p/9170236.html
Copyright © 2011-2022 走看看