zoukankan      html  css  js  c++  java
  • Java多线程之二:Thread

    Thread图解

    Thread类包含方法过多,目前只截取部分

    Thread中的实例方法

    Thread类中的方法调用方式:

    1、this.XXX()

    这种调用方式表示的线程是线程实例本身

    2、Thread.currentThread.XXX()或Thread.XXX()

    这种调用方式表示的线程是正在执行Thread.currentThread.XXX()所在代码块的线程

    以下主要从Thread类中的实例方法和类方法的角度讲解Thread中的方法。

    Thread类中的实例方法

    实例方法,只和实例线程(也就是new出来的线程)本身挂钩,和当前运行的是哪个线程无关。

    1、start()

    start()方法的作用讲得直白点就是通知“线程规划期”,此线程可以运行了,正在等待CPU调用线程对象得run()方法,产生一个异步执行的效果。通过start()方法产生得到结论。

    示例1:

    package main.thread;
    
    public class ThreadMethodTest {
        public static void main(String[] args) {
            MyThread02 myThread02 = new MyThread02();
            myThread02.start();
            try{
                for (int i = 0; i < 3;i++){
                    Thread.sleep((int)(Math.random() * 1000));
                    System.out.println("main method run = " + Thread.currentThread().getName());
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    class MyThread02 extends Thread
    {
        public void run(){
            try{
                for(int i = 0; i < 3; i++){
                    Thread.sleep((int)(Math.random() * 1000));
                    System.out.println("MyThread02 run method run = " + Thread.currentThread().getName());
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    执行结果:

    MyThread02 run method run = Thread-0
    main method run = main
    MyThread02 run method run = Thread-0
    MyThread02 run method run = Thread-0
    main method run = main
    main method run = main

    结果表明:CPU执行哪个线程的代码具有不确定性。

    示例2:

    package main.thread;
    
    public class ThreadMethodTest2 {
        public static void main(String[] args) {
            MyThread03 myThread03_0 = new MyThread03();
            MyThread03 myThread03_1 = new MyThread03();
            MyThread03 myThread03_2 = new MyThread03();
            myThread03_0.start();
            myThread03_1.start();
            myThread03_2.start();
        }
    }
    
    class MyThread03 extends Thread{
        public void run(){
            System.out.println("MyThread03 run method, threadName = " + Thread.currentThread().getName());
        }
    }

    执行结果:

    MyThread03 run method, threadName = Thread-0
    MyThread03 run method, threadName = Thread-2
    MyThread03 run method, threadName = Thread-1

    尽管线程是按照myThread03_0,myThread03_1,myThread03_2 ,但实际上的启动顺序是myThread03_0,myThread03_2,myThread03_1,该例子表明:调用start()方法的顺序不代表线程启动的顺序,线程启动顺序具有不确定性。 

    2、run()

    线程开始执行,虚拟机调用的是线程run()方法中的内容。

    示例:

    package main.thread;
    
    public class ThreadMethodTest {
        public static void main(String[] args) {
            MyThread02 myThread02 = new MyThread02();
            //非异步执行, 方法调用
            myThread02.run();
            try{
                for (int i = 0; i < 3;i++){
                    Thread.sleep((int)(Math.random() * 1000));
                    System.out.println("main method run = " + Thread.currentThread().getName());
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    
    class MyThread02 extends Thread
    {
        public void run(){
            try{
                for(int i = 0; i < 3; i++){
                    Thread.sleep((int)(Math.random() * 1000));
                    System.out.println("MyThread02 run method run = " + Thread.currentThread().getName());
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    执行难结果:

    MyThread02 run method run = main
    MyThread02 run method run = main
    MyThread02 run method run = main
    main method run = main
    main method run = main
    main method run = main

    看到打印了6次的"run = main",说明如果只有run()没有start(),Thread实例run()方法里面的内容是没有任何异步效果的,全部被main函数执行。换句话说,只有run()而不调用start()启动线程是没有任何意义的。

    3、isAlive()

    测试线程是否属于活动状态,只要线程启动且没有终止,方法返回的就是true。

    示例:

    package main.thread;
    
    public class ThreadMethodIsAliveTest {
        public static void main(String[] args) {
            MyThread04 myThread04 = new MyThread04();
            System.out.println("before myThread04 start, isAlive="+myThread04.isAlive());
            myThread04.start();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("after myThread04 start, isAlive="+myThread04.isAlive());
    
        }
    }
    
    class MyThread04 extends Thread{
        public void run(){
            System.out.println("MyThread04 run, isAlive="+this.isAlive());
        }
    }

    执行结果:

    before myThread04 start, isAlive=false
    MyThread04 run, isAlive=true
    after myThread04 start, isAlive=false

    在start()之前,线程的isAlive是false,start()之后就是true了。main函数中加上Thread.sleep(100)的原因是为了确保Thread04的run()方法中的代码执行完,否则可能end这里打印出来的是true,可以尝试验证下哈。

    4、getId()

    在一个java应用中,有一个long型的全局唯一的线程ID生成器threadSeqNumber,每new出来一个线程都会把这个自增一次,并赋予线程的tid属性,这个是Thread自己做的,用户无法执行一个线程的Id。

    5、getName()

    我们new一个线程的时候,可以指定该线程的名字,也可以不指定。若指定,那么线程的名字就是我们自己指定的,getName()返回的也是开发者指定的线程的名字;若不指定,那么Thread中有一个int型全局唯一的线程初始号生成器threadInitNum,Java先把threadInitNum自增,然后以“”hread-threadInitNum"的方式来命名新生成的线程。

    示例:

    package main.thread;
    
    public class ThreadMethodTest2 {
        public static void main(String[] args) {
            MyThread03 myThread03_0 = new MyThread03();
            MyThread03 myThread03_1 = new MyThread03();
            MyThread03 myThread03_2 = new MyThread03();
            myThread03_0.setName("name0");
            myThread03_1.setName("name1");
            myThread03_2.setName("name2");
            myThread03_0.start();
            myThread03_1.start();
            myThread03_2.start();
        }
    }
    
    class MyThread03 extends Thread{
        public void run(){
            System.out.println("MyThread03 run method, threadName = " + Thread.currentThread().getName() + " threadTid="+ Thread.currentThread().getId());
        }
    }

    执行结果:

    MyThread03 run method, threadName = name0 threadTid=11
    MyThread03 run method, threadName = name2 threadTid=13
    MyThread03 run method, threadName = name1 threadTid=12

    6、getPriority()和setPriority(int newPriority)

    /**
    * 线程的优先级属性
    */
    private int priority;
    /**
    * 线程所能拥有的最大优先级.
    */
    public final static int MIN_PRIORITY = 1;

    /**
    * 线程默认的优先级.
    */
    public final static int NORM_PRIORITY = 5;

    /**
    * 线程所能拥有的最大优先级.
    */
    public final static int MAX_PRIORITY = 10;

     这两个方法用于获取和设置线程的优先级,优先级高的CPU得到的CPU资源比较多,设置优先级有助于帮"线程规划器"确定下一次选择哪一个线程优先执行。换句话说,两个在等待CPU的线程,优先级高的线程越容易被CU选择执行

    示例:

    package main.thread;
    
    public class ThreadMethodPriorityTest {
        public static void main(String[] args) {
            System.out.println("main thread begin priority=" + Thread.currentThread().getPriority());
            System.out.println("main thread end priority=" + Thread.currentThread().getPriority());
            PriorityThread_1 priorityThread_1 = new PriorityThread_1();
            priorityThread_1.start();
        }
    }
    
    class PriorityThread_0 extends Thread{
        public void run(){
            System.out.println("PriorityThread_0 run priority=" + this.getPriority());
        }
    }
    
    class PriorityThread_1 extends Thread{
        public void run(){
            System.out.println("PriorityThread_1 run priority=" + this.getPriority());
            PriorityThread_0 priorityThread = new PriorityThread_0();
            priorityThread.start();
        }
    }

    执行结果:

    main thread begin priority=5
    main thread end priority=5
    PriorityThread_1 run priority=5
    PriorityThread_0 run priority=5

     从上述示例看出:线程默认优先级为5,如果不手动指定,那么线程优先级具有继承性,比如线程A启动线程B,那么线程B的优先级和线程A的优先级相同

    再看一个手动设置priority的示例:

    package main.thread;
    
    public class ThreadMethodPriorityTest {
        public static void main(String[] args) {
            System.out.println("main thread priority=" + Thread.currentThread().getPriority());
            PriorityThread_0 priorityThread_0 = new PriorityThread_0();
            priorityThread_0.setPriority(1);
            priorityThread_0.start();
    
            PriorityThread_1 priorityThread_1 = new PriorityThread_1();
            priorityThread_1.setPriority(9);
            priorityThread_1.start();
        }
    }
    
    class PriorityThread_0 extends Thread{
        public void run(){
            System.out.println("PriorityThread_0 thread priority=" + Thread.currentThread().getPriority());
            long beginTime = System.currentTimeMillis();
            for (int j = 0; j < 1000000; j++){}
            long endTime = System.currentTimeMillis();
            System.out.println("PriorityThread_0 use time = " +
                    (endTime - beginTime));
        }
    }
    
    class PriorityThread_1 extends Thread{
        public void run(){
            System.out.println("PriorityThread_1 thread priority=" + Thread.currentThread().getPriority());
            long beginTime = System.currentTimeMillis();
            for (int j = 0; j < 1000000; j++){}
            long endTime = System.currentTimeMillis();
            System.out.println("PriorityThread_1 use time = " +
                    (endTime - beginTime));
        }
    }

    执行结果:

    main thread priority=5
    PriorityThread_0 thread priority=1
    PriorityThread_1 thread priority=9
    PriorityThread_0 use time = 3
    PriorityThread_1 use time = 3

    上述执行的结果并不是优先级高的先执行完,只能说CPU会尽量将执行资源让给优先级比较高的线程不要误导自己,原因如下:

    因此, 在实际的编码时, 认为高优先级一定先于低优先级的线程执行, 最后会出问题的。

    注意:

    优先级和操作系统及虚拟机版本相关。
    ++优先级只是代表告知了 「线程调度器」该线程的重要度有多大。如果有大量线程都被堵塞,都在等候运
    行,调试程序会首先运行具有最高优先级的那个线程。然而,这并不表示优先级较低的线程不会运行(换言之,不会因为存在优先级而导致死锁)。若线程的优先级较低,只不过表示它被准许运行的机会小一些而已。++

    7、isDaemon、setDaemon(boolean on)

    讲解两个方法前,首先要知道理解一个概念。Java中有两种线程,一种是用户线程,一种是守护线程。守护线程是一种特殊的线程,它的作用是为其他线程的运行提供便利的服务,最典型的应用便是GC线程如果进程中不存在非守护线程了,那么守护线程自动销毁,因为没有存在的必要,为别人服务,结果服务的对象都没了,当然就销毁了。理解了这个概念后,看一下例子:

    package main.thread;
    
    public class DaemonThreadTest {
        public static void main(String[] args) {
            DaemonThread daemonThread = new DaemonThread();
            //设置为守护线程
            daemonThread.setDaemon(true);
            daemonThread.start();
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("非守护线程要结束了");
        }
    }
    
    class DaemonThread extends Thread{
        private int i = 0;
        public void run(){
            try{
                System.out.println("DaemonThread start");
                while(true){
                    i ++;
                    System.out.println("i = " + i);
                    Thread.sleep(1000);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    执行结果:

    DaemonThread start
    i = 1
    i = 2
    i = 3
    i = 4
    i = 5
    非守护线程要结束了

    要解释一下。我们将DaemonThread 线程设置为守护线程,看到第7行的那句话,而i停在7不会再运行了。这说明,main线程运行了5秒结束,而i每隔1秒累加一次,5秒后main线程执行完结束了,MyThread11作为守护线程,main函数都运行完了,自然也没有存在的必要了,就自动销毁了,因此也就没有再往下打印数字。

    关于守护线程,有一个细节注意下,setDaemon(true)必须在线程start()之前。

    注意:守护线程在退出的时候并不会执行finnaly块中的代码,所以将释放资源等操作不要放在finnaly块中执行,这种操作是不安全的

     8、interrupt()

    这是一个有点误导性的名字,实际上Thread类的interrupt()方法无法中断线程。

    示例:

    package main.thread;
    
    public class ThreadInterruptTest {
        public static void main(String[] args) {
            InterruptThread interruptThread = new InterruptThread();
            interruptThread.start();
            try {
                Thread.sleep(10);
    
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("interrupt method end");
            interruptThread.interrupt();
            System.out.println("interrupt method end");
        }
    }
    
    class InterruptThread extends Thread{
        public void run(){
            System.out.println("InterruptThread start run");
            for (int i = 0; i < 1000; i++)
            {
                System.out.println("i = " + (i + 1));
            }
    
            System.out.println("InterruptThread end run");
        }
    }

    执行结果:

    InterruptThread start run
    i = 1
    i = 2
    ......
    i = 189
    interrupt method end
    i = 190
    interrupt method end
    i = 191
    ......
    i = 1000
    InterruptThread end run

     看结果还是打印到了50000。也就是说,尽管调用了interrupt()方法,但是线程并没有停止。interrupt()方法的作用实际上是:在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞状态。换句话说,没有被阻塞的线程,调用interrupt()方法是不起作用的。

     9、isInterrupted()

    测试线程是否已经中断,但不清除状态标识。

    10、join()

    讲解join()方法之前请确保对于http://www.cnblogs.com/xrq730/p/4853932.html一文,即wait()/notify()/notifyAll()机制已熟练掌握。

    join()方法的作用是等待线程销毁。如main线程的执行时间为1s,子线程的执行时间为10s,但是主线程依赖子线程执行的结果,怎么办?可以向生产者/消费者模型一样,搞一个缓冲区,子线程执行完把书放在缓冲区,通知main线程,main线程去拿,这样就不会浪费main线程的事件了。另一种方式就是join()了。

    package main.thread;
    
    public class ThreadJoinTest {
        public static void main(String[] args) {
            JoinThread joinThread = new JoinThread();
            joinThread.start();
            try {
                joinThread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("joinThread 执行完毕在执行该语句");
        }
    }
    
    class JoinThread extends  Thread{
        public void run(){
           try{
               int value = (int)(Math.random() * 1000);
               System.out.println("JoinThread run, value = " + value);
               Thread.sleep(value);
    
           }catch (Exception e){
               e.printStackTrace();
           }
        }
    }

     执行结果:

    JoinThread run, value = 405
    joinThread 执行完毕在执行该语句

    无论执行几次 都是这样的顺序结果, 只是value = 405值不一样而已。

    join()方法会使调用join()方法的线程(也就是joinThread线程)所在的线程(也就是main线程)无限阻塞,直到调用join()方法的线程销毁为止,此例中main线程就会无限期阻塞直到mt的run()方法执行完毕。

    join()方法的一个重点是要区分出和sleep()方法的区别。join(2000)也是可以的,表示调用join()方法所在的线程最多等待2000ms,两者的区别在于:

    sleep(2000)不释放锁,join(2000)释放锁,因为join()方法内部使用的是wait(),因此会释放锁。看一下join(2000)的源码就知道了,join()其实和join(2000)一样,无非是join(0)而已:

     1  public final synchronized void join(long millis)
     2     throws InterruptedException {
     3         long base = System.currentTimeMillis();
     4         long now = 0;
     5 
     6         if (millis < 0) {
     7             throw new IllegalArgumentException("timeout value is negative");
     8         }
     9 
    10         if (millis == 0) {
    11             while (isAlive()) {
    12                 wait(0);
    13             }
    14         } else {
    15             while (isAlive()) {
    16                 long delay = millis - now;
    17                 if (delay <= 0) {
    18                     break;
    19                 }
    20                 wait(delay);
    21                 now = System.currentTimeMillis() - base;
    22             }
    23         }
    24     }

     Thread类中的静态方法

    Thread类中的静态方法表示操作的线程是“正在执行静态方法所在的代码块的线程”,为什么Thread类中要有静态方法,这样就能对CPU当前正在运行的线程进行操作。

    1、currentThread

    currentThread()方法返回的是对当前正在执行线程对象的引用

    示例1:

    package main.thread;
    
    public class CurrentThreadMethodTest {
        public static void main(String[] args) {
            CurrentThreadMethod currentThreadMethod = new CurrentThreadMethod();
            currentThreadMethod.start();
        }
    }
    
    
    class CurrentThreadMethod extends Thread{
        static {
            System.out.println("CurrentThreadMethod static模块的打印, threadName="+Thread.currentThread().getName());
        }
    
        public CurrentThreadMethod(){
            System.out.println("CurrentThreadMethod 构造方法的打印, threadName="+Thread.currentThread().getName());
        }
        public void run(){
            System.out.println("CurrentThreadMethod run()方法的打印, threadName="+Thread.currentThread().getName());
        }
    }

    执行结果:

    CurrentThreadMethod static模块的打印, threadName=main
    CurrentThreadMethod 构造方法的打印, threadName=main
    CurrentThreadMethod run()方法的打印, threadName=Thread-0

     从执行结果看出:线程类的构造方法、静态块是被main线程调用的,而线程类的run()方法才是应用线程自己调用的

    示例2:

    package main.thread;
    
    public class CurrentThreadMethodTest {
        public static void main(String[] args) {
            CurrentThreadMethod currentThreadMethod = new CurrentThreadMethod();
            currentThreadMethod.start();
        }
    }
    
    class CurrentThreadMethod extends Thread{
        static {
            System.out.println("CurrentThreadMethod static模块的打印, threadName="+Thread.currentThread().getName());
        }
    
        public CurrentThreadMethod(){
            System.out.println("CurrentThreadMethod 构造方法, Thread.currentThread().getName()="+Thread.currentThread().getName());
            System.out.println("CurrentThreadMethod 构造方法, this.getName()="+this.getName());
        }
        public void run(){
            System.out.println("CurrentThreadMethod run()方法, Thread.currentThread().getName()="+Thread.currentThread().getName());
            System.out.println("CurrentThreadMethod run()方法, this.getName()="+this.getName());
        }
    }

     执行结果:

    CurrentThreadMethod static模块的打印, threadName=main
    CurrentThreadMethod 构造方法, Thread.currentThread().getName()=main
    CurrentThreadMethod 构造方法, this.getName()=Thread-0
    CurrentThreadMethod run()方法, Thread.currentThread().getName()=Thread-0
    CurrentThreadMethod run()方法, this.getName()=Thread-0

    上篇文章的开头就说过,要理解一个重要的概念,就是"this.XXX()"和"Thread.currentThread().XXX()"的区别,这个就是最好的例子。必须要清楚的一点就是:当前执行的Thread未必就是Thread本身。从这个例子就能看出来:

    (1)执行CurrentThreadMethod 构造方法是main,当前线程却是Thread-0

    (2)执行run()方法的Thread-0,当前线程也是Thread-0,说明run()方法就是被线程实例去执行的

     所以,再强调一下,未必在CurrentThreadMethod 里调用Thread.currentThread()返回回来的线程对象的引用就是CurrentThreadMethod 

    2、sleep(long millis)

    sleep(long millis)方法的作用是在指定的毫秒内让当前"正在执行的线程"休眠(暂停执行)。这个"正在执行的线程"是关键,指的是Thread.currentThread()返回的线程。根据JDK API的说法,"该线程不丢失任何监视器的所属权",简单说就是sleep代码上下文如果被加锁了,锁依然在但是CPU资源会让出给其他线程

    示例:

    package main.thread;
    
    import java.util.Date;
    
    public class ThreadSleepTest {
        public static void main(String[] args) {
            SleepThread sleepThread = new SleepThread();
            System.out.println("main()  before sleepThread.start() " + new Date());
            sleepThread.start();
            System.out.println("main()  after sleepThread.start() " + new Date());
        }
    }
    
    class SleepThread extends Thread{
        public void run(){
            System.out.println("SleepThread run() start, threadName=" + this.getName() + " " + new Date());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("SleepThread run() end, threadName=" + this.getName() + " " + new Date());
        }
    }

    执行结果:

    1 main()  before sleepThread.start() Fri Jun 21 19:16:32 CST 2019
    2 main()  after sleepThread.start() Fri Jun 21 19:16:32 CST 2019
    3 SleepThread run() start, threadName=Thread-0 Fri Jun 21 19:16:32 CST 2019
    4 SleepThread run() end, threadName=Thread-0 Fri Jun 21 19:16:34 CST 2019 //与第三行结果差2秒,与run()中的Thread.sleep(2000)是对应的

     3、yield()

    暂停当前执行的线程对象,并执行其他线程。这个暂停是会放弃CPU资源的,并且放弃CPU的时间不确定,有可能刚放弃,就获得CPU资源了,也有可能放弃好一会儿,才会被CPU执行。

    package main.thread;
    
    import java.util.Date;
    
    public class ThreadYieldTest {
        public static void main(String[] args) {
            System.out.println("main() start "+new Date());
            YieldThread mt = new YieldThread();
            mt.start();
            System.out.println("main() end "+new Date());
        }
    }
    
    class YieldThread extends Thread{
        public void run(){
            long beginTime = System.currentTimeMillis();
            int count = 0;
            for (int i = 0; i < 5000000; i++)
            {
                Thread.yield();
                count = count + i + 1;
            }
            long endTime = System.currentTimeMillis();
            System.out.println("用时:" + (endTime - beginTime) + "毫秒!");
        }
    }

    执行结果:

    //第一次执行结果
    main() start Fri Jun 21 19:23:19 CST 2019
    main() end Fri Jun 21 19:23:19 CST 2019
    用时:1953毫秒!
    //第二次执行结果
    main() start Fri Jun 21 19:24:15 CST 2019
    main() end Fri Jun 21 19:24:15 CST 2019
    用时:1990毫秒!
    //第三次执行结果
    main() start Fri Jun 21 19:24:35 CST 2019
    main() end Fri Jun 21 19:24:35 CST 2019
    用时:1984毫秒!

     看到,每次执行的用时都不一样,证明了yield()方法放弃CPU的时间并不确定。

    4、interrupted()

    测试当前线程是否已经中断,执行后具有将状态标识清除为false的功能。换句话说,如果连续两次调用该方法,那么返回的必定是false.

    package main.thread;
    
    
    public class ThreadYieldTest {
            Thread.currentThread().interrupt();
            System.out.println("是否停止1?" + Thread.interrupted());
            System.out.println("是否停止2?" + Thread.interrupted());
            System.out.println("end!");
        }
    }

     执行结果:

    是否停止1?true
    是否停止2?false
    end!
  • 相关阅读:
    socket注意
    PCM音频文件编码
    题外:分类篇(音乐风格分类)基于BP神经网络
    MFCC特征提取过程详解
    语音信号分析
    k-means聚类
    c++关键字详解
    vs中项目解决方案和项目的关系
    条件编译#ifdef 和#endif
    c++快捷键
  • 原文地址:https://www.cnblogs.com/dudu2mama/p/11059925.html
Copyright © 2011-2022 走看看