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!
原文地址:https://www.cnblogs.com/dudu2mama/p/11059925.html