Java并发编程实战(四)

一. 线程相关方法总结    

线程通信的目标是使线程间能够互相发送信号。 线程通信使线程能够等待其他线程的信号。    

与线程通信相关的几个常用方法为:wait()、notify()、notifyAll()、join()、sleep()、yield()等。

(1)wait()方法

1. 当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起,直到发生以下情况才会返回:    

(1)其他线程调用了共享对象的notify()方法或notifyAll()方法;    

(2)其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回;    

如果调用了wait()方法的线程没有事先获取该对象的监视器锁,则调用wait()方法时调用线程将会抛出IllegalMonitorStateException异常;     

实例代码:

/**
 * @ClassName: ThreadWait1
 * @Description:  当一个线程调用共享对象的wait()方法被阻塞挂起后,如果其他线程中断了该线程,则该线程将会抛出InterruptedException异常并返回;
 * @Author: liuhefei
 * @Date: 2019/4/8
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ThreadWait1 {

    static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {
        //创建线程
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + "开始执行...");
                    //阻塞当前线程
                    synchronized (obj){
                        obj.wait();
                    }
                    System.out.println(Thread.currentThread().getName() + "执行结束...");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        });

        threadA.start();

        Thread.sleep(1000);
        System.out.println("开始执行...");
        //中断线程A
        threadA.interrupt();
        //获取线程的状态
        System.out.println("当前线程的状态:" + threadA.isAlive());
        System.out.println("执行结束...");
    }
}

2. 一个线程如何才能获取一个共享变量的监视器锁呢?    

(1)执行synchronized同步代码块时,使用该共享变量作为参数;    

   synchronized(共享变量){  }     

(2)使用synchronized修饰调用该共享变量的方法;          

   synchronized void method(共享变量){ }     

3. 虚假唤醒:一个线程可以从挂起状态转变为可运行状态,即使该线程没有被其他线程调用notify()、notifyAll()方法进行唤醒,或者被中断,或者等待超时,这就是虚假唤醒。    

为了防止虚假唤醒的发生,就要不停地去测试该线程被唤醒的条件是否满足,如果不满足就继续等待,也就是说需要在一个循环中调用wait()方法,当满足唤醒线程的条件就退出循环。    

    synchronized(obj){      

         while(条件不足){   

         obj.wait();   

      }     

    }     

4. 当前线程调用共享对象的wait()方法时,当前线程只会释放当前共享对象的锁,当前线程持有的其他共享对象的监视器锁并不会被释放。    

一句话:wait()方法用于让某个线程进入等待状态,也就是阻塞挂起,它会释放对象锁;    

实例代码:

/**
 * @ClassName: ThreadWait
 * @Description: wait()方法的使用:
 * 本实例证明了当前线程调用共享对象的wait()方法时,当前线程只会释放当前共享对象的锁,当前线程持有的其他共享对象的监视器锁并不会被释放。
 * @Author: liuhefei
 * @Date: 2019/4/8
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ThreadWait {

    //创建资源
    private static volatile Object resourceA = new Object();
    private static volatile Object resourceB = new Object();

    public static void main(String[] args) throws InterruptedException {
        //创建线程A
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //获取resourceA共享资源的监视器锁
                    synchronized (resourceA){
                        System.out.println(Thread.currentThread().getName() + "获取到resourceA的监视器锁");
                        //获取resourceB共享资源的监视器锁
                        synchronized (resourceB){
                            System.out.println(Thread.currentThread().getName() + "获取到resourceB的监视器锁");

                            //线程A阻塞,并释放获取到的resourceA的锁
                            System.out.println(Thread.currentThread().getName() + "释放获取到的resourceA的锁");
                            resourceA.wait();  //阻塞
                        }
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        });

        //创建线程B
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //休眠1秒
                    Thread.sleep(1000);
                    //获取resourceA共享资源的监视器锁
                    synchronized (resourceA){
                        System.out.println(Thread.currentThread().getName() + "获取到resourceA的监视器锁");

                        System.out.println(Thread.currentThread().getName() + "尝试获取resourceB的监视器锁");
                        //获取resourceB共享资源的监视器锁
                        synchronized (resourceB){
                            System.out.println(Thread.currentThread().getName() + "获取到resourceB的监视器锁");

                            //线程B阻塞,并释放获取到的resourceA的锁
                            System.out.println(Thread.currentThread().getName() + "线程B阻塞,释放resourceA的锁");
                            resourceA.wait();
                        }
                    }
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        });

        //启动线程
        threadA.start();
        threadB.start();

        //等待两个线程结束
        threadA.join();
        threadB.join();

        //线程A先后获取到resourceA,resourceB的锁,当线程A调用wait()方法时,阻塞自己,并释放resourceA的锁,但是并没有释放resourceB的锁
        //线程B在休眠结束后,会去获取resourceA的锁,由于线程A释放了resourceA的锁,所有线程B可以获取到resourceA的锁
        //线程B在尝试获取resourceB的锁,由于线程A调用的是resourceA的wait()方法,所以线程A挂起后并没有释放resourceB的锁,所有线程B在尝试获取resourceB的锁时会被阻塞。

    }
}

(2)wait(long timeout)方法    

timeout:超时参数,单位为ms     

1. 如果一个线程调用共享对象的该方法挂起后,没有在指定的超时时间范围内被其他线程调用该共享变量的notify()或notifyAll()方法唤醒,那么该方法还是会因为超时而返回。    

2. 当timeout为0时,该方法与wait()方法作用效果一样;    

3. 当timeout为负数时,将会抛出IllegalArgumentException异常;    

(3)notify()方法     

1. 一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait()方法后被挂起的线程。   

   一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待线程是随机的。   

2. 被唤醒的线程不会马上从wait()方法返回并继续执行,它首先要获取到共享对象的监视器锁后才能返回,也就是唤醒它的线程释放了共享变量上的监视器锁

后,被唤醒的线程也不一定会获取到共享对象的监视器锁,这是因为该线程还需要和其他线程一起竞争该锁,只有该线程竞争到了共享变量的监视器锁后才可以继续执行。    

一句话:notify()方法用于随机唤醒某个处于等待状态的线程。

实例代码:

/**
 * @ClassName: ThreadNotify
 * @Description: notify(),notifyAll()唤醒线程
 * @Author: liuhefei
 * @Date: 2019/4/8
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ThreadNotify {

    //创建资源
    private static volatile Object resourceA = new Object();

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

        //创建线程A
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                //获取resourceA共享资源的监视器锁
                synchronized (resourceA){
                    System.out.println(Thread.currentThread().getName() + "获取到resourceA的监视器锁");
                    try {
                        System.out.println(Thread.currentThread().getName() + "开始执行...");
                        //共享变量resourceA调用wait()方法,挂起自己
                        resourceA.wait();
                        System.out.println(Thread.currentThread().getName() + "执行结束...");
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        });

        //创建线程B
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                //获取resourceA共享资源的监视器锁
                synchronized (resourceA){
                    System.out.println(Thread.currentThread().getName() + "获取到resourceA的监视器锁");
                    try {
                        System.out.println(Thread.currentThread().getName() + "开始执行...");
                        //共享变量resourceA调用wait()方法,挂起自己
                        resourceA.wait();
                        System.out.println(Thread.currentThread().getName() + "执行结束...");
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        });

        //创建线程C
        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA){
                    System.out.println(Thread.currentThread().getName() + "开始唤醒其他线程");
                    //resourceA.notify();  //随机唤醒threadA和threadB中的一个
                    resourceA.notifyAll();
                }
            }
        });

        //启动线程
        threadA.start();
        threadB.start();

        Thread.sleep(1000);
        threadC.start();

        //等待线程结束
        threadA.join();
        threadB.join();
        threadC.join();

        //线程A和线程B执行后遇到wait()方法,它们都会被挂起,并进入resourceA的阻塞集合中,
        //在休眠1秒后,线程C启动调用了notify()方法,它会随机唤醒resourceA阻塞集合中的任意一个线程,这里唤醒了线程A,线程A执行完毕,而线程B还在处于阻塞状态
        //将notify()方法替换为notifyAll()方法,线程C将唤醒线程A和线程B,它们都会执行完毕


    }
}

(4)notifyAll()方法    

1. notifyAll()方法用于唤醒所有在该共享变量上由于调用wait()方法而被挂起的线程。   

2. 在共享变量上调用notifyAll()方法只会唤醒调用这个方法前,调用了wait()方法而被放入共享变量阻塞集合里面的线程;    

3. 如果调用了notifyAll()后,一个线程调用了该共享变量的wait()方法而被放入阻塞集合,则该线程是不会被唤醒的。    

实例代码:

/**
 * @ClassName: ThreadWaitNotifyAll
 * @Description:  保证多线程的执行顺序
 * @Author: liuhefei
 * @Date: 2019/4/11
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ThreadWaitNotifyAll {

    private int signal;

    public synchronized void thread1(){
        while (signal != 0){
            try {
                wait(); //调试
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("thread1....");
        signal++;
        notifyAll();  //唤醒所有
    }

    public synchronized void thread2(){
        while (signal != 1){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("thread2....");
        signal++;
        notifyAll();
    }


    public synchronized void thread3(){
        while (signal != 2){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        System.out.println("thread3....");
        signal = 0;
        notifyAll();
    }

    public static void main(String[] args) {
        ThreadWaitNotifyAll twn = new ThreadWaitNotifyAll();
        MyThread1 my1 = new MyThread1(twn);
        MyThread2 my2 = new MyThread2(twn);
        MyThread3 my3 = new MyThread3(twn);

        new Thread(my1).start();
        new Thread(my2).start();
        new Thread(my3).start();

    }

}

class MyThread1 implements Runnable{

    private ThreadWaitNotifyAll threadWaitNotifyAll;

    public MyThread1(ThreadWaitNotifyAll threadWaitNotifyAll){
        this.threadWaitNotifyAll = threadWaitNotifyAll;
    }

    @Override
    public void run() {
        while(true){
            threadWaitNotifyAll.thread1();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class MyThread2 implements Runnable{

    private ThreadWaitNotifyAll threadWaitNotifyAll;

    public MyThread2(ThreadWaitNotifyAll threadWaitNotifyAll){
        this.threadWaitNotifyAll = threadWaitNotifyAll;
    }


    @Override
    public void run() {
        while(true){
            threadWaitNotifyAll.thread2();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

class MyThread3 implements Runnable{

    private ThreadWaitNotifyAll threadWaitNotifyAll;

    public MyThread3(ThreadWaitNotifyAll threadWaitNotifyAll){
        this.threadWaitNotifyAll = threadWaitNotifyAll;
    }


    @Override
    public void run() {
        while(true){
            threadWaitNotifyAll.thread3();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

wait()、notify()、notifyAll()是Java中Object类中的方法,Object类是所有类的父类,Java把所有类都需要的方法放到了Object类中。

(5)join()方法    

join()方法用于等待线程执行终止。它是Thread类提供的。

实例代码:

public class ThreadJoin1 {

    public static void main(String[] args) {
        //创建线程
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "开始执行");
                //死循环
                while (true){

                }
            }
        });

        //获取主线程
        final Thread mainThread = Thread.currentThread();

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //中断主线程
                mainThread.interrupt();

            }
        });

        System.out.println("主线程的状态:" + mainThread.isAlive());

        //启动子线程
        threadA.start();
        threadB.start();

        //等待线程A执行结束
        try {
            threadA.join();
        } catch (InterruptedException e) {
            System.out.println("抛出异常:" + e);
        }
    }
}

(6)sleep()方法      

1. 当一个执行中的线程调用了Thread类的sleep()方法时,调用线程会暂时让出CPU的执行权,在指定的时间内,该线程不参与CPU的调度,但是该线程不会是否所持有的对象锁,

指定的时间到了后该线程会继续执行。    

2. 如果在该线程睡眠期间,其他线程调用了该线程的interrupt()方法中断了该线程,则该线程会在调用了sleep()方法的地方抛出InterruptedException异常而返回。    

实例代码1:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @ClassName: ThreadSleep
 * @Description: 线程在睡眠期间,拥有的对象锁不会被释放
 * 定义一个独占锁,无论你执行多少遍,都不会出现线程A与线程B交叉执行的情况
 * @Author: liuhefei
 * @Date: 2019/4/8
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ThreadSleep {

    //创建一个独占锁
    private static final Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        //创建线程A
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                //获取独占锁
                lock.lock();
                try {
                    int sum = 0;
                    System.out.println(Thread.currentThread().getName() + "开始睡眠");
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + "开始执行");
                    for(int i=0;i<=100;i++){
                        sum += i;
                    }
                    System.out.println(Thread.currentThread().getName() + "执行完毕,sum= " + sum);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }finally {
                    //释放锁
                    lock.unlock();
                }
            }
        });

        //创建线程B
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                //获取独占锁
                lock.lock();
                try {
                    System.out.println(Thread.currentThread().getName() + "开始睡眠");
                    Thread.sleep(3000);
                    System.out.println(Thread.currentThread().getName() + "沉睡了千年...");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }finally {
                    //释放锁
                    lock.unlock();
                }
            }
        });

        threadA.start();
        threadB.start();
    }
}

实例代码2:

/**
 * @ClassName: ThreadSleep1
 * @Description:  当一个线程处于睡眠状态时,另一个线程中断该线程,将会在sleep方法处抛出异常
 * @Author: liuhefei
 * @Date: 2019/4/8
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ThreadSleep1 {

    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(Thread.currentThread().getName() + "开始昏昏欲睡...");
                    Thread.sleep(10000);
                    System.out.println(Thread.currentThread().getName() + "沉睡千年后开始苏醒");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        });

        threadA.start();

        //主线程休眠
        Thread.sleep(2000);

        //主线程中断子线程
        threadA.interrupt();

    }
}

(7)yield()方法     

1. yield()方法用于让出CPU的执行权;

2. 当一个线程调用了yield()方法时,是在告诉线程调度器自己占有的时间片还没有使用完的部分不想使用了,暗示线程调度器现在就可以进行下一轮的线程调度。    

3. 当一个线程调用yield()方法时,当前线程会让出CPU的使用权,然后处于就绪状态,线程调度器会从线程就绪队列里面获取一个处于就绪状态的优先级最高的线程,这时就有可能调度到刚刚让出CPU的那个线程来获取CPU的执行权。    

 实例代码:

/**
 * @ClassName: ThreadYield
 * @Description: 使用yield()方法让出线程Cpu的执行权
 * @Author: liuhefei
 * @Date: 2019/4/8
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ThreadYield implements Runnable{

    @Override
    public void run() {
        for(int i=0;i<5;i++){
            System.out.println("i=" + i);
            //当i=0时让出CPU的执行权,进行下一轮的调度
            if((i % 5) == 0){
                System.out.println(Thread.currentThread() + "开始让出CPU的执行权");

                //当前线程让出CPU的执行权,放弃时间片,进行下一轮的调度
                Thread.yield();
            }
        }
        System.out.println(Thread.currentThread() + " 执行结束");
    }

    public ThreadYield(){
        //创建并启动线程
        Thread t = new Thread(this);
        t.start();
    }

    public static void main(String[] args) {
        new ThreadYield();
        new ThreadYield();
        new ThreadYield();
    }
}

sleep()与yield()方法的区别:

1. 当线程调用sleep()方法时,调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程;

2. 当线程调用yield()方法时,线程只是让出自己剩余的CPU时间片,并没有被阻塞挂起,而是处于就绪状态,等待线程调度器下一次调度就有可能调度到当前线程执行。    

二. 线程中断

Java中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不会立即终止该线程的执行,而是被中断的线程会根据中断状态自行处理。   

与中断相关的方法如下:     

(1)void interrupt()方法:    

该方法用于中断线程,比如:当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志为true并立即返回。

当中断标志设置为true时,线程A并不会立即被中断,它会继续往下执行。如果线程A因为调用了wait(),join()或sleep()方法而被阻塞挂起,当线程B调用了线程A的interrupt()方法时,线程A就会在调用了这些方法的地方抛出InterruptedException异常。    

实例代码:

/**
 * @ClassName: ThreadInterrupt
 * @Description: 在线程休眠的过程中,调用interrupt()方法抛出异常,让线程立马恢复到激活状态
 * @Author: liuhefei
 * @Date: 2019/4/12
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ThreadInterrupt {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("让线程进入休眠状态");
                    Thread.sleep(40000);
                    System.out.println("线程休眠结束");
                }catch (InterruptedException e){
                    System.out.println("线程被中断,抛出异常后并返回,异常信息:" + e.getMessage());
                    return;
                }
                System.out.println("线程接着执行...");
            }
        });

        //启动线程
        thread.start();

        //确保子线程进入休眠状态
        Thread.sleep(2000);

        //中断子线程的休眠,让它在sleep()方法处抛出异常并返回
        thread.interrupt();
        //等待子线程执行完毕
        thread.join();

    }
}

(2)boolean isInterrupted()方法:      

该方法用于判断当前线程是否被中断,返回true表示被中断,返回false表示没有被中断;    

(3)boolean interrupted()方法:     

该方法用于检测当前线程是否被中断,返回true表示该线程被中断,此时它会清除中断标志,它是static类型的,可以通过Thread类直接调用。

如果返回false表示该线程没有被中断。     

实例代码:

/**
 * @ClassName: ThreadInterrupt2
 * @Description: interrupted()和isInterrupted()方法
 * @Author: liuhefei
 * @Date: 2019/4/12
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class ThreadInterrupt2 {

    //1.isInterrupted()方法判断当前线程是否被中断,是返回true,否则返回false
    //2.interrupted()方法判断当前线程是否被中断,是返回true,并清除中断标志,否则返回false
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for( ; ; ){

                }
            }
        });

        //启动线程
        thread.start();

        //设置中断标志
        thread.interrupt();

        //获取中断标志
        System.out.println("获取子线程中断标志:" + thread.isInterrupted());

        //获取中断标志并重置
        System.out.println("获取中断标志并重置:" + thread.interrupted());

        //获取中断标志并重置
        System.out.println("获取中断标志并重置:" + Thread.interrupted());

        //获取中断标志
        System.out.println("获取子线程中断标志:" + thread.isInterrupted());

        // thread.interrupted() 与 Thread.interrupted()都是获取当前线程的中断标志
    }
}

三. 线程上下文切换   

每个线程的运行都需要CPU资源。当多个线程同时执行时,CPU资源的分配采用了时间片轮转的策略,也就是给每一个线程分配一个时间片,线程在

时间片内占用CPU执行任务。在当前线程使用完时间片后,就会处于就绪状态,并让出CPU资源给其他线程使用,这个过程就是线程上下文切换。     

在线程上下文切换的过程中,需要保存线程当前线程的执行线程,以便当再次执行的时候根据保存的执行现场信息可以恢复到执行现场,进而继续执行。   

线程上下文切换的时机:   

(1)当前线程的CPU时间片用完并处于就绪状态时;   

(2)当前线程被其他线程中断时;   

四. 守护线程与用户线程    

Java中的线程分两类:daemon线程(守护线程)和user线程(用户线程);   

在JVM启动时会调用main函数,main函数所在的线程就是一个用户线程;在JVM的内部同时会启动多个守护线程,比如垃圾回收线程。    

守护线程:运行在后台的线程。      

用户线程:运行在前台的线程。     

守护线程的作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要。       

守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。

用户可以用Thread的setDaemon(true)方法设置当前线程为守护线程。

实例代码:

/**
 * @ClassName: DaemonThread
 * @Description:  创建一个守护线程
 * @Author: liuhefei
 * @Date: 2019/4/12
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class DaemonThread {

    public static void main(String[] args) {
        Thread daemonThread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread() + "守护线程启动了");
            }
        });

        //设置为 守护线程
        daemonThread.setDaemon(true);  //为true,表示设置守护线程

        //启动守护线程
        daemonThread.start();

        //判断是不是守护线程
        System.out.println("是否是守护线程:" + daemonThread.isDaemon());
    }
}

守护线程与用户线程的区别:   

当最后一个非守护线程结束时,JVM会正常退出,而不管当前是否有守护线程,也就是说守护线程是否结束并不影响JVM的退出。    

也就是说只要有一个用户线程还没有结束,正常情况下JVM就不会退出。     

JVM退出的条件:不存在用户线程时;      

实例代码:

/**
 * @ClassName: DaemonThread2
 * @Description:  JVM退出的条件:不存在用户线程时
 * @Author: liuhefei
 * @Date: 2019/4/13
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class DaemonThread2 {

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            for(;;){
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 执行了....");
            }
        },"thread-one").start();

        System.out.println("main is over");
        //Thread.currentThread().join();  //阻塞
    }
}

线程阻塞时:会导致从用户态切换到内核态执行;    

线程被唤醒时:会导致从内核态切换到用户态执行;   

线程池创建的线程默认是用户线程;       

使用守护线程应该注意的问题:    

1、setDaemon(true)必须在调用线程的start()方法之前设置,否则会抛出IllegalThreadStateException异常。       

2、在守护线程中产生的新线程也是守护线程。                

3、不要在守护线程中执行业务逻辑操作,比如读写操作,因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。       

实例代码:

/**
 * @ClassName: DaemonThread1
 * @Description: 验证不可以在守护线程中做读写操作
 * 向工程根目录的“daemon.txt”文件写入字符串,实际运行结果发现并未成功写入,且未报任何错误,
 * 原因是写入文件的线程被设置为守护线程,该线程还在 sleep 过程中时所有用户线程就全部结束了,守护线程也会随着 JVM 一起退出。
 * 将 thread.setDaemon(true); 修改为  thread.setDaemon(false); 后就可以成功写入文件了。
 * @Author: liuhefei
 * @Date: 2019/4/12
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class DaemonThread1 implements Runnable{

    @Override
    public void run() {
        //文件输出字节流
        FileOutputStream outputStream = null;
        try {
            Thread.sleep(2000);
            File file = new File("daemon.txt");
            outputStream = new FileOutputStream(file);
            outputStream.write("守护线程".getBytes());
        }catch (InterruptedException e){
            System.out.println("e1: " + e.getMessage());
            e.printStackTrace();
        }catch (IOException e){
            System.out.println("e2: " + e.getMessage());
            e.printStackTrace();
        }finally {
            if(outputStream != null){
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        DaemonThread1 dt1 = new DaemonThread1();
        Thread thread = new Thread(dt1);
        //设置为守护线程
        thread.setDaemon(true);
        //启动线程
        thread.start();
    }
}

总结:   

如果你希望在主线程结束后JVM进程马上结束,那么在创建线程时可以将其设置为守护线程;       

如果你希望在主线程结束后子线程继续工作,等子线程结束后再让JVM进程结束,那么就将子线程设置为用户线程;  

点赞