二:线程同步synchronized和volatile关键字

  上篇通过一个简单的例子说明了线程安全与不安全,在例子中不安全的情况下输出的结果恰好是逐个递增的(其实是巧合,多运行几次,会产生不同的输出结果),为什么会产生这样的结果呢,因为建立的Count对象是线程共享的,一个线程改变了其成员变量num值,下一个线程正巧读到了修改后的num,所以会递增输出。

        要说明线程同步问题首先要说明Java线程的两个特性,可见性和有序性。多个线程之间是不能直接传递数据交互的,它们之间的交互只能通过共享变量来实现。拿上篇博文中的例子来说明,在多个线程之间共享了Count类的一个对象,这个对象是被创建在主内存(堆内存)中,每个线程都有自己的工作内存(线程栈),工作内存存储了主内存Count对象的一个副本,当线程操作Count对象时,首先从主内存复制Count对象到工作内存中,然后执行代码count.count(),改变了num值,最后用工作内存Count刷新主内存Count。当一个对象在多个内存中都存在副本时,如果一个内存修改了共享变量,其它线程也应该能够看到被修改后的值,此为可见性。多个线程执行时,CPU对线程的调度是随机的,我们不知道当前程序被执行到哪步就切换到了下一个线程,一个最经典的例子就是银行汇款问题,一个银行账户存款100,这时一个人从该账户取10元,同时另一个人向该账户汇10元,那么余额应该还是100。那么此时可能发生这种情况,A线程负责取款,B线程负责汇款,A从主内存读到100,B从主内存读到100,A执行减10操作,并将数据刷新到主内存,这时主内存数据100-10=90,而B内存执行加10操作,并将数据刷新到主内存,最后主内存数据100+10=110,显然这是一个严重的问题,我们要保证A线程和B线程有序执行,先取款后汇款或者先汇款后取款,此为有序性。本文讲述了JDK5.0之前传统线程的同步方式,更高级的同步方式可参见Java线程(八):锁对象Lock-同步问题更完美的处理方式

 

例子:子线程先循环10次,主线程再循环100次,这样总共循环50次;代码实现:

public class demo {
    public static void main(String[] args) {
        final bussiness bbb= new bussiness();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int z=0;z<50;z++){
                    bbb.sub();

                }
            }
        }).start();
        for(int z=0;z<50;z++){
            bbb.submain();

        }
    }
}

class bussiness{
    public synchronized void sub(){
        for(int i=0;i<10;i++){
            System.out.println("sub"+i);
        }
    }
    public synchronized void submain(){
        for(int j=0;j<100;j++){
            System.out.println("subless"+j);
        }
    }

}

注:以上代码做到了互斥,在本线程执行期间,没有外来得干扰;

接下来是实现通信,让他们相互交互:

public class demo {
    public static void main(String[] args) {
        final bussiness bbb= new bussiness();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for(int z=0;z<50;z++){
                    bbb.sub();

                }
            }
        }).start();
        for(int z=0;z<50;z++){
            bbb.submain();

        }
    }
}

class bussiness{
    private boolean iswait=true;
    public synchronized void sub(){
        if(!true){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for(int i=0;i<10;i++){
            System.out.println("sub"+i);
        }
        iswait=true;
        this.notify();
    }
    public synchronized void submain(){
        if(iswait){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        for(int j=0;j<100;j++){
            System.out.println("subless"+j);
        }
        iswait=false;
        this.notify();
    }

}

注:以上代码中,wait()方法使得线程处于等待,notify()则是唤醒等待得线程;达到交互;

互斥得方法要写在一个类中,这样比较容易实现!if换成while也可以,且更好!线程中有可能存在被伪唤醒得情况,所以用while更好!

实例变量与线程安全 :

 不共享数据:

 实现结果:

 可以看到,这三个线程之间的count变量不共享;原因在于定义多线程时采用 mythread a=new  mythread('A');这种方法来进行生成,能够生成3个类;

共享数据:

运行结果:

 

 以上共享数据的原因在于;使用 Mythread  mythread=new  Mythread (); Thread A= new Thread(mythread,'A');传入的都是同一个mythread对象;

 多个线程共享数据与对象,可以有两个方法:

1:将共享数据封装到一个对象中,将这个对象逐一传递给各个RUNable对象,每个线程对共享数据的操作方法也分配到哪个对象上去完成,

2:将这些Runable对象作为某一个类的内部类,共享数据作为这个外部类的成员变量,每个线程对共享数据的操作方法也分配给外部类,

1:

 

2:

完整代码:

public class MultiThreadShareData {

    private static ShareData1 data1 = new ShareData1();
    
    public static void main(String[] args) {
        ShareData1 data2 = new ShareData1();
        new Thread(new MyRunnable1(data2)).start();
        new Thread(new MyRunnable2(data2)).start();
        
        final ShareData1 data1 = new ShareData1();
        new Thread(new Runnable(){
            @Override
            public void run() {
                data1.decrement();
                
            }
        }).start();
        new Thread(new Runnable(){
            @Override
            public void run() {
                data1.increment();
                
            }
        }).start();

    }

}
    
    class MyRunnable1 implements Runnable{
        private ShareData1 data1;
        public MyRunnable1(ShareData1 data1){
            this.data1 = data1;
        }
        public void run() {
            data1.decrement();
            
        }
    }
    
    class MyRunnable2 implements Runnable{
        private ShareData1 data1;
        public MyRunnable2(ShareData1 data1){
            this.data1 = data1;
        }
        public void run() {
            data1.increment();
        }
    }

    class ShareData1 /*implements Runnable*/{
/*        private int count = 100;
        @Override
        public void run() {
            // TODO Auto-generated method stub
            while(true){
                count--;
            }
        }*/
        
        
        private int j = 0;
        public synchronized void increment(){
            j++;
        }
        
        public synchronized void decrement(){
            j--;
        }
    } 

 

        下面同样用代码来展示一下线程同步问题。

        TraditionalThreadSynchronized.java:创建两个线程,执行同一个对象的输出方法。

 

  1. public class TraditionalThreadSynchronized {  
  2.     public static void main(String[] args) {  
  3.         final Outputter output = new Outputter();  
  4.         new Thread() {  
  5.             public void run() {  
  6.                 output.output("zhangsan");  
  7.             };  
  8.         }.start();        
  9.         new Thread() {  
  10.             public void run() {  
  11.                 output.output("lisi");  
  12.             };  
  13.         }.start();  
  14.     }  
  15. }  
  16. class Outputter {  
  17.     public void output(String name) {  
  18.         // TODO 为了保证对name的输出不是一个原子操作,这里逐个输出name的每个字符  
  19.         for(int i = 0; i < name.length(); i++) {  
  20.             System.out.print(name.charAt(i));  
  21.             // Thread.sleep(10);  
  22.         }  
  23.     }  
  24. }  

        运行结果:


 

  1. zhlainsigsan  

        显然输出的字符串被打乱了,我们期望的输出结果是zhangsanlisi,这就是线程同步问题,我们希望output方法被一个线程完整的执行完之后再切换到下一个线程,Java中使用synchronized保证一段代码在多线程执行时是互斥的,有两种用法:

 

        1. 使用synchronized将需要互斥的代码包含起来,并上一把锁。

  1. synchronized (this) {  
  2.     for(int i = 0; i < name.length(); i++) {  
  3.         System.out.print(name.charAt(i));  
  4.     }  
  5. }  

        这把锁必须是需要互斥的多个线程间的共享对象,像下面的代码是没有意义的。


 

  1. Object lock = new Object();  
  2. synchronized (lock) {  
  3.     for(int i = 0; i < name.length(); i++) {  
  4.         System.out.print(name.charAt(i));  
  5.     }  
  6. }  

        每次进入output方法都会创建一个新的lock,这个锁显然每个线程都会创建,没有意义。

 

        2. 将synchronized加在需要互斥的方法上。

 

  1. public synchronized void output(String name) {  
  2.     // TODO 线程输出方法  
  3.     for(int i = 0; i < name.length(); i++) {  
  4.         System.out.print(name.charAt(i));  
  5.     }  
  6. }  

        这种方式就相当于用this锁住整个方法内的代码块,如果用synchronized加在静态方法上,就相当于用××××.class锁住整个方法内的代码块。使用synchronized在某些情况下会造成死锁,死锁问题以后会说明。使用synchronized修饰的方法或者代码块可以看成是一个原子操作

 

线程范围内得数据共享;获得不同线程中得不同值:使用Map存储唯一线程,取得其中得值;

public class ThreadScopeShareData {

    private static int data = 0;
    private static Map<Thread, Integer> threadData = new HashMap<Thread, Integer>();
    public static void main(String[] args) {
        for(int i=0;i<2;i++){
            new Thread(new Runnable(){
                @Override
                public void run() {
                    int data = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName() 
                            + " has put data :" + data);
                    threadData.put(Thread.currentThread(), data);
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }
    
    static class A{
        public void get(){
            int data = threadData.get(Thread.currentThread());
            System.out.println("A from " + Thread.currentThread().getName() 
                    + " get data :" + data);
        }
    }
    
    static class B{
        public void get(){
            int data = threadData.get(Thread.currentThread());            
            System.out.println("B from " + Thread.currentThread().getName() 
                    + " get data :" + data);
        }        
    }
}

以上的这种线程范围内数据共享采用自定义map的形式,其实,有个类threadLocal能替代这种功能

 注:一个ThreadLocal代表一个变量,其中只能存放一个数据;如果想存放多个数据。就把这些数据放到一个实体中去:

 

 

        每个锁对(JLS中叫monitor)都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,当一个线程被唤醒(notify)后,才会进入到就绪队列,等待CPU的调度,反之,当一个线程被wait后,就会进入阻塞队列,等待下一次被唤醒,这个涉及到线程间的通信,下一篇博文会说明。看我们的例子,当第一个线程执行输出方法时,获得同步锁,执行输出方法,恰好此时第二个线程也要执行输出方法,但发现同步锁没有被释放,第二个线程就会进入就绪队列,等待锁被释放。一个线程执行互斥代码过程如下:

        1. 获得同步锁;

        2. 清空工作内存;

        3. 从主内存拷贝对象副本到工作内存;

        4. 执行代码(计算或者输出等);

        5. 刷新主内存数据;

        6. 释放同步锁。

        所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。

        volatile是第二种Java多线程同步的机制,根据JLS(Java LanguageSpecifications)的说法,一个变量可以被volatile修饰,在这种情况下内存模型(主内存和线程工作内存)确保所有线程可以看到一致的变量值,来看一段代码:

 

  1. class Test {  
  2.     static int i = 0, j = 0;  
  3.     static void one() {  
  4.         i++;  
  5.         j++;  
  6.     }  
  7.     static void two() {  
  8.         System.out.println("i=" + i + " j=" + j);  
  9.     }  
  10. }  

        一些线程执行one方法,另一些线程执行two方法,two方法有可能打印出j比i大的值,按照之前分析的线程执行过程分析一下:

 

        1. 将变量i从主内存拷贝到工作内存;

        2. 改变i的值;

        3. 刷新主内存数据;

        4. 将变量j从主内存拷贝到工作内存;

        5. 改变j的值;

        6. 刷新主内存数据;

        这个时候执行two方法的线程先读取了主存i原来的值又读取了j改变后的值,这就导致了程序的输出不是我们预期的结果,要阻止这种不合理的行为的一种方式是在one方法和two方法前面加上synchronized修饰符:

 

  1. class Test {  
  2.     static int i = 0, j = 0;  
  3.     static synchronized void one() {  
  4.         i++;  
  5.         j++;  
  6.     }  
  7.     static synchronized void two() {  
  8.         System.out.println("i=" + i + " j=" + j);  
  9.     }  
  10. }  

       根据前面的分析,我们可以知道,这时one方法和two方法再也不会并发的执行了,i和j的值在主内存中会一直保持一致,并且two方法输出的也是一致的。另一种同步的机制是在共享变量之前加上volatile:


 

  1. class Test {  
  2.     static volatile int i = 0, j = 0;  
  3.     static void one() {  
  4.         i++;  
  5.         j++;  
  6.     }  
  7.     static void two() {  
  8.         System.out.println("i=" + i + " j=" + j);  
  9.     }  
  10. }  

       one方法和two方法还会并发的去执行,但是加上volatile可以将共享变量i和j的改变直接响应到主内存中,这样保证了主内存中i和j的值一致性,然而在执行two方法时,在two方法获取到i的值和获取到j的值中间的这段时间,one方法也许被执行了好多次,导致j的值会大于i的值。所以volatile可以保证内存可见性,不能保证并发有序性。

       没有明白JLS中为什么使用两个变量来阐述volatile的工作原理,这样不是很好理解。volatile是一种弱的同步手段,相对于synchronized来说,某些情况下使用,可能效率更高,因为它不是阻塞的,尤其是读操作时,加与不加貌似没有影响,处理写操作的时候,可能消耗的性能更多些。但是volatile和synchronized性能的比较,我也说不太准,多线程本身就是比较玄的东西,依赖于CPU时间分片的调度,JVM更玄,还没有研究过虚拟机,从顶层往底层看往往是比较难看透的。在JDK5.0之前,如果没有参透volatile的使用场景,还是不要使用了,尽量用synchronized来处理同步问题,线程阻塞这玩意简单粗暴。另外volatile和final不能同时修饰一个字段,可以想想为什么。

线程的状态

在正式学习Thread类中的具体方法之前,我们先来了解一下线程有哪些状态,这个将会有助于后面对Thread类中的方法的理解。

  • 创建(new)状态: 准备好了一个多线程的对象
  • 就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
  • 运行(running)状态: 执行run()方法
  • 阻塞(blocked)状态: 暂时停止执行, 可能将资源交给其它线程使用
  • 终止(dead)状态: 线程销毁

线程常用方法:

静态方法

currentThread()方法

currentThread()方法可以返回代码段正在被哪个线程调用的信息。

1

2

3

4

5

public class Run1{

    public static void main(String[] args){                

    System.out.println(Thread.currentThread().getName());

    }

}

sleep()方法

方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。

sleep方法有两个重载版本:

1

2

sleep(long millis)     //参数为毫秒

sleep(long millis,int nanoseconds)    //第一参数为毫秒,第二个参数为纳秒

sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。看下面这个例子就清楚了:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

public class Test {

 

    private int i = 10;

    private Object object = new Object();

 

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

        Test test = new Test();

        MyThread thread1 = test.new MyThread();

        MyThread thread2 = test.new MyThread();

        thread1.start();

        thread2.start();

    }

 

    class MyThread extends Thread{

        @Override

        public void run() {

            synchronized (object) {

                i++;

                System.out.println("i:"+i);

                try {

                    System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态");

                    Thread.currentThread().sleep(10000);

                } catch (InterruptedException e) {

                    // TODO: handle exception

                }

                System.out.println("线程"+Thread.currentThread().getName()+"睡眠结束");

                i++;

                System.out.println("i:"+i);

            }

        }

    }

}

输出结果:

从上面输出结果可以看出,当Thread-0进入睡眠状态之后,Thread-1并没有去执行具体的任务。只有当Thread-0执行完之后,此时Thread-0释放了对象锁,Thread-1才开始执行。

注意,如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。

yield()方法

调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。

注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

public class MyThread  extends Thread{

    @Override

    public void run() {

        long beginTime=System.currentTimeMillis();

        int count=0;

        for (int i=0;i<50000000;i++){

            count=count+(i+1);

            //Thread.yield();

        }

        long endTime=System.currentTimeMillis();

        System.out.println("用时:"+(endTime-beginTime)+" 毫秒!");

    }

}

 

public class Run {

    public static void main(String[] args) {

        MyThread t= new MyThread();

        t.start();

    }

}

执行结果:

1

用时:3 毫秒!

如果将 //Thread.yield();的注释去掉,执行结果如下:

1

用时:16080 毫秒!

对象方法

start()方法

start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。

run()方法

run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。

getId()

getId()的作用是取得线程的唯一标识
代码:

1

2

3

4

5

6

public class Test {

    public static void main(String[] args) {

        Thread t= Thread.currentThread();

        System.out.println(t.getName()+" "+t.getId());

    }

}

输出:

1

main 1

isAlive()方法

方法isAlive()的功能是判断当前线程是否处于活动状态
代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

public class MyThread  extends Thread{

    @Override

    public void run() {

        System.out.println("run="+this.isAlive());

    }

}

public class RunTest {

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

        MyThread myThread=new MyThread();

        System.out.println("begin =="+myThread.isAlive());

        myThread.start();

        System.out.println("end =="+myThread.isAlive());

    }

}

程序运行结果:

1

2

3

begin ==false

run=true

end ==false

方法isAlive()的作用是测试线程是否偶处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。
有个需要注意的地方

1

System.out.println("end =="+myThread.isAlive());

虽然上面的实例中打印的值是true,但此值是不确定的。打印true值是因为myThread线程还未执行完毕,所以输出true。如果代码改成下面这样,加了个sleep休眠:

1

2

3

4

5

6

7

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

        MyThread myThread=new MyThread();

        System.out.println("begin =="+myThread.isAlive());

        myThread.start();

        Thread.sleep(1000);

        System.out.println("end =="+myThread.isAlive());

    }

则上述代码运行的结果输出为false,因为mythread对象已经在1秒之内执行完毕。

join()方法

在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

public class Thread4 extends Thread{

    public Thread4(String name) {

        super(name);

    }

    public void run() {

        for (int i = 0; i < 5; i++) {

            System.out.println(getName() + "  " + i);

        }

    }

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

        // 启动子进程

        new Thread4("new thread").start();

        for (int i = 0; i < 10; i++) {

            if (i == 5) {

                Thread4 th = new Thread4("joined thread");

                th.start();

                th.join();

            }

            System.out.println(Thread.currentThread().getName() + "  " + i);

        }

    }

}

执行结果:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

main  0

main  1

main  2

main  3

main  4

new thread  0

new thread  1

new thread  2

new thread  3

new thread  4

joined thread  0

joined thread  1

joined thread  2

joined thread  3

joined thread  4

main  5

main  6

main  7

main  8

main  9

由上可以看出main主线程等待joined thread线程先执行完了才结束的。如果把th.join()这行注释掉,运行结果如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

main  0

main  1

main  2

main  3

main  4

main  5

main  6

main  7

main  8

main  9

new thread  0

new thread  1

new thread  2

new thread  3

new thread  4

joined thread  0

joined thread  1

joined thread  2

joined thread  3

joined thread  4

getName和setName

用来得到或者设置线程名称。

getPriority和setPriority

用来获取和设置线程优先级。

setDaemon和isDaemon

用来设置线程是否成为守护线程和判断线程是否是守护线程。

守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。

在上面已经说到了Thread类中的大部分方法,那么Thread类中的方法调用到底会引起线程状态发生怎样的变化呢?下面一幅图就是在上面的图上进行改进而来的:

 

停止线程

停止线程是在多线程开发时很重要的技术点,掌握此技术可以对线程的停止进行有效的处理。
停止一个线程可以使用Thread.stop()方法,但最好不用它。该方法是不安全的,已被弃用。
在Java中有以下3种方法可以终止正在运行的线程:

  • 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
  • 使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果。
  • 使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。

暂停线程

interrupt()方法

线程的优先级

在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
设置线程优先级有助于帮“线程规划器”确定在下一次选择哪一个线程来优先执行。
设置线程的优先级使用setPriority()方法,此方法在JDK的源码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

public final void setPriority(int newPriority) {

        ThreadGroup g;

        checkAccess();

        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {

            throw new IllegalArgumentException();

        }

        if((g = getThreadGroup()) != null) {

            if (newPriority > g.getMaxPriority()) {

                newPriority = g.getMaxPriority();

            }

            setPriority0(priority = newPriority);

        }

    }

在Java中,线程的优先级分为1~10这10个等级,如果小于1或大于10,则JDK抛出异常throw new IllegalArgumentException()。
JDK中使用3个常量来预置定义优先级的值,代码如下:

1

2

3

public final static int MIN_PRIORITY = 1;

public final static int NORM_PRIORITY = 5;

public final static int MAX_PRIORITY = 10;

线程优先级特性:

  • 继承性
    比如A线程启动B线程,则B线程的优先级与A是一样的。
  • 规则性
    高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
  • 随机性
    优先级较高的线程不一定每一次都先执行完。

守护线程

在Java线程中有两种线程,一种是User Thread(用户线程),另一种是Daemon Thread(守护线程)。
Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。

守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on) ;但是有几点需要注意:

  • thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (备注:这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)
  • 在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)
  • 不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

注:本文装载自:https://blog.csdn.net/z69183787/article/details/28644227

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值