Java多线程技术详解(全都是细节!)

2023-11-07

多线程启动

线程有两种启动方式:实现Runnable接口:继承Thread类并重写run()方法。

1、Thread 与 Runnable

 Runnable接口表示线程要执行的任务。当Runnable中的run()方法执行时,表示线程在激活状态,
 run()方法一旦执行完毕,即表示任务完成,则表示任务完成,则线程将被停止。

public interface Runnable {
     void run();
 }

Thread类默认实现了Runnable接口,并且其构造方法的重载形式允许传入Runnable接口对象作为任务。

 * Thread类的源代码片段如下:
 
 public class Thread implements  Runnable {
     private Runnable target;
     public void run() {
         if (target != null) {
             target.run();
         }
     }
 }

通过Thread类的源代码可以发现,线程的两种启动方式,其本质都是实现Thread类中run()方法。而实现Runnable接口,然后传递给Thread类的方式,比Thread子类重写run()方法更加灵活。 

2、线程标识

Thread类用于管理线程,如设置线程优先级、设置Daemon属性、读取线程名字和ID、启动线程任务、
暂停线程任务、中断线程等。
为了管理线程、每个线程在启动后都会生成一个唯一的标识符,并且在其生命周期内保持不变。
当线程被终止时,该线程ID可以被重用。而线程的名字更加直观,但是不具有唯一性。
//线程标识测试
class MyRunnable implements Runnable {
    @Override
    public void run(){
        System.out.println("我的任务开始执行。。。");
    }
}

public class ThreadTest01 {
    public static void main(String[] args) {
        for (int i = 0; i < 5 ; i++) {
            MyRunnable mr = new MyRunnable();
            //启动线程,申请执行任务
            Thread th = new Thread(mr);
            th.start();
            //读取线程名字和ID
            System.out.println(th.getId());
            System.out.println(th.getName());
        }
    }
}

 运行结果:

 

3、run()与start()方法

Thread类中的run()方法与start()方法很容易混淆,我们对比分析一下。

public class Thread implements Runnable{
  public void run() {
  }
  public synchronized void start() {
  }
}


调动Thread对象的start()方法,使线程对象开始执行任务,这会触发Java虚拟机调用当前线程对象的run()方法。调用后start()方法后,将会导致两个线程并行运行,一个是调用start()方法的当前线程,另一个是执行run()方法的线程。

如果重复调用start()方法,这是一个非法操作,它不会长生更多的线程,反而会导致IllegalThreadStateExcepyion异常

测试如下:

class MyRunnable1 implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getId() + "-w我的任务开始执行。。。");
    }
}

public class ThreadTest02 {
    public static void main(String[] args) {
        //创建任务
        MyRunnable1 mr = new MyRunnable1();
        //创建线程对象,任务还未执行
        Thread th = new Thread(mr);
        //启动线程,申请执行任务
        th.start();
        //再次调用start()会引发异常
        th.start();

        /**
         * 这里我们要特别注意的是,Thread对象调用start()方法之后,触发了JVM底层调用run()方法。
         * 如果我们主动调用Thread对象的run()方法,并不能启动一个新线程。
         */
//        th.run();  // 测试结果:执行run()方法的线程与主线程ID一样,表明它们是同一个线程。
        System.out.println("主线程:" + Thread.currentThread().getId());
    }
}

测试1:

//启动线程,申请执行任务
th.start();

 

 测试2:IllegalThreadStateExcepyion异常

 //再次调用start()会引发异常
   th.start();

   th.start();

 

 

Thread源码分析

创建Thread类实例,首先会执行registerNatives()方法,它在静态代码块中加载。线程的启动、运行、生命周期管理和调度等都依赖于操作系统,Java本身并不具备与底层操作系统交互的能力。因此线程的底层操作都是使用了native方法,registerNatives()就是用C语言编写的底层线程注册方法。  

public class Thread implements Runnable{
     private static native void registerNatives();
     static {
           registerNatives();
     }
}

 无论通过Thread类的哪种构造方法去创建线程,都需要首先调用init()方法,初始化线程环境,源码如下:

 

public class Thread{
     public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
     private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }
}

在init()方法中,做了如下操作:

1、设置线程名称。

2、将线程的父线程设置为当前线程。

3、获取系统的安全管理SecurityMannager,并获得线程组。SecurityManagezr在Java中被        用来检查应用程序是否能访问一些受权限资源,如文件、套接字(scoket)等。它可以          用在哪些具有高安全的应用程序中。

4、获取线程组的权限检查。

5、在线程组中增加未启动的线程数量

6、设置新线程的属性,包括守护线程属性(默认继承父线程)、优先级(默认继承父线程)、        堆栈大小(如果为0,则默认由JVM分配)、线程组、线程安全控制上下文(一种Java安        全模式,设置访问控制权限)等。

Thread类中start()方法源码:

 public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }

 它做了如下操作:

1、在线程组中减少未知启动的线程数量。

2、调用底层的native方法start()进行线程启动。

3、最终由底层native方法调用run()执行。

4、如果启动失败,从线程组中移除该线程,并且增加未知启动线程数量。

 

线程状态

线程对象在不同的运行时期存在着不同的状态,在Thread类中通过一个内部枚举类Start保持状态信息,了就线程状态对于并发编程非常重要。

public class Thread{
    public enum State {
          NEW,
          RUNNABLE,
          BLOCKED,
          WAITING,
          TIMED_WAITING,
          TERMINATED;
    }
}

Java中的线程存在6种状态,我们可以通过Thread类中的Thread.getState()方法获取线程在某个时期的线程状态。在给定的时间点,线程只能处于一种状态。

NEW状态

NEW代表着线程新建状态,一个已创建但是未起动(start)的线程处于NEW状态。

public class ThreadTest03 {
    public static void main(String[] args) {
        //NEW状态
        Thread th = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getId() + "-我的任务开始执行。。。");
            }
        });
        System.out.println(th.getName() + ":状态:" + th.getState());
        System.out.println("主线程 :" + Thread.currentThread().getId());
    }
}

运行:

RUNNABLE状态

RUNNABLE状态表示一个线程正在Java虚拟机中运行,但是这个线程是否获得了出现器分配资源并不确定。调用Thread的start()方法后,线程从NEW状态切换到了RUNNABLE状态。

测试代码:

public class ThreadTest03 {
    public static void main(String[] args) {
//测试RUNNABLE状态
        Thread th = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ": 状态 :" + Thread.currentThread().getState());
                System.out.println(Thread.currentThread().getName() + "-我的任务开始执行。。");
                System.out.println(Thread.currentThread().getName() + ": 状态 :" + Thread.currentThread().getState());
            }
        });
        System.out.println(th.getName() + ": 状态 :" + th.getState());
        th.start();
        System.out.println("主线程:" + Thread.currentThread().getName() + ":状态 :" + Thread.currentThread().getState());
   }

 运行结果:

BLOCKED状态

 BLOCKED为阻塞状态,表示当前线程正在阻塞等待获得监视器锁。当一个线程要访问被其他线程synchronized锁定的资源时,当前线程需要阻塞等待。

代码测试:在主函数中分别启动了两个线程,它们都需要获得Object对象的监听器锁后执行任务。第一个线程启动后,首先获得了Object监视器锁。由于在run()方法中使用了死循环while(true),因此第一个线程获得了Object监视器后不会释放,这导致第二个线程长期处于阻塞等待状态。

public class ThreadTest03 {
    public static void main(String[] args) {
        Object object = new Object();
        Thread th = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    while (true){

                    }
                }
            }
        });
        Thread th2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    while (true) {
                    }
                }
            }
        });
        System.out.println(th.getName() + ": 状态 :" + th.getState());
        System.out.println(th2.getName() + ": 状态 :" + th2.getState());
        th.start();
        System.out.println(th.getName() + ": 状态 :" + th.getState());
        for (int i = 0; i < 300000 ; i++) {
            //适当延迟后,启动第二个线程
        }
            th2.start();
            System.out.println(th2.getName() + ": 状态 :" + th2.getState());
            for (int j = 0; j < 3000000; j++) {
                //适当延迟后,重新获取线程状态
            }
            System.out.println(th.getName() + ": 状态 :"  +th.getState());
            System.out.println(th2.getName() + ": 状态 :"  +th2.getState());
            System.out.println("主线程:" + Thread.currentThread().getName() + ": 状态 : " +
                    Thread.currentThread().getState());


        }
}
}

 运行结果:

 

WAITING状态

WAITING表示线程处于等待状态。

在当前线程中调用如下方法之一时,会使当前线程程序进入等待状态:

     Object类的wait()方法(没有超时设置);

     Thread类的join()方法(没有超时设置);

     LockSupport类的park()方法。

处于等待状态的线程,正在等待另外一个线程去完成某个特殊操作。例如,在某个线程中调用了Object 对象的wait()方法,它会进入等待状态,等待Object 对象调用notify()或notiyAll()方法。一个线程对象调用了join()方法,则会等待指定的线程终止任务。

代码测试:注意调用object对象的wait()方法时,必须要先用synchronized锁定object对象。当线程1进入WAITING状态后,后续代码不再执行。直到object对象调用了notify()方法

//测试代码
public class ThreadTest04 {
    //测试WAITING状态
    public static void main(String[] args) {
        Object object = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + ", i =" +i);
                    if (i == 5) {
                        synchronized (object) {
                            try {
                                System.out.println(Thread.currentThread().getName() +
                                        "开始等待。。。");
                                object.wait();
                            }catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "running...");
                synchronized (object){
                    try {
                        System.out.println(Thread.currentThread().getName() +
                                ", 发送notify通知。。。");
                        object.notify();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        //启动第一个线程
        t1.start();
        double d = 0;
        for (int i = 0; i < 100000; i++) {
            //等待第一个线程调用wait()方法
            d += (Math.PI + Math.E) / (double)i;
        }
        //在主线程读取第一个线程的状态
        System.out.println(t1.getName() + "状态:" + t1.getState());
        //启动第二个线程,发送notify()通知
        t2.start();
    }
}

运行结果:

 注意:调用wait()/notify()方法前需要先锁定object对象,而调用park()和unpark()方法前无须锁定对象。

TIMED_WATING状态

TIMED_WATING 表示线程处于定时等待状态。

在当前线程中调用如下方法之一时,使当前线程进入定时等待状态:

Object 类的 wait()方法(有超时设置);

Thread类的join()方法(有超时设置);

Thread类的sleep()方法(有超时设置);

LockSupport() 类的 parkNanos()方法;

LockSupport()类的 parkUntil()方法。

代码测试:调用object.wait(3000),在指定时间内没有调用object对象notify()或notifyAII(),就会触发超时等待结束,当前线程重新进入RUNNABLE状态。

//测试TIMED_WAITING状态
    public static void main(String[] args) {
        Object object = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + ",i=" + i);
                    if (i==5) {
                        synchronized (object){
                            try{
                                System.out.println(Thread.currentThread().getName() +
                                        "开始等待。。。");
                                object.wait(3000);
                            }catch (Exception e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        });
        t1.start();
        try {
            //延迟1秒,等待t1进入等待状态
            Thread.sleep(1000);
        }catch (Exception e){

        }
        System.out.println(t1.getName() + "状态:" + t1.getState());
    }

运行结果:

 调用Thread.sleep(3000),当前线程也会进入定时等待状态。

WAITING 与 BLOCKED 的区别

WAITING、TIMED_WAITING、BLOCKED 这几个线程状态,都会使当前线程处于停顿状态,因此容易混淆。

总结以上这些状态之间的区别:

  • Thread.sleep()不会释放占有的对象锁,因此会持续占用CPU。

  • Object.wait() 会释放占有的对象锁,不会占用CPU。

  • BLOCKED 使当前线程进入阻塞后,为了抢占对象监听器锁,一般操作系统都会给这个线程持续的CPU使用权。

  • LockSupport.park()底层调用 UNSAFE.park()方法实现,它没有使用对象监听器锁,不会占用CPU。

TERMINATED 状态

TERMINATED 表示线程为完成状态。当线程完成其run()方法中的任务,或者因为某些未知的异常而强制中断时,线程状态变为TERMINATED。

测试代码:

 //测试TERMINATED状态:
       public static void main(String[] args) {
           Thread t1 = new Thread(new Runnable() {
               @Override
               public void run() {
                   double d = 0;
                   for (int i = 0; i < 1000; i++) {
                       d += (Math.PI + Math.E)/(double)i;
                   }
               }
           });
           t1.start();
           try {
               //等待线程运行结束
               Thread.sleep(2000);
           } catch (Exception e){

           }
           System.out.println("t1 线程状态:" + t1.getState());
       }

运行结果:

线程状态装换  

把线程RUNNABLE状态细分为两种:runnable(准备就绪)和 running(运行中),runnable表明线程刚刚被JVM启动,还没有获得CPU的使用权:running 表示线程获取获得了CPU的使用权,正在运行。

 

sleep()与yield()方法

线程休眠sleep()

Thread类的sleep()方法,使当前正在执行的线程以指定的毫秒数暂时停止执行,具体停止时间取决于系统定时器和调度程序的精度和准确性。当前线程状态由RUNNABLE切换到TIMED_WAITING。调用sleep()方法不会使线程丢失任何监视器所有权,因此当前线程仍然占用CPU分片。

public class Thread{
     public static native void sleep(long millis) 
                throws InterruptedException;
}

调用sleep()方法可能会抛出InterruptedException异常,它应该在run()方法中被捕获,因为异常无法传递到其他线程,如主线程就无法捕获子线程抛出的异常。

Java SE5 引入了更加显式sleep()版本,作为TimeUit类的一部分。例如,TimeUnit.MILLISECONDS.sleep(1000) 等价余Thread.Sleep(1000),表示休眠1秒,TimeUnit类提供了更好的可读性。

 

//测试sleep()方法
class Task implements Runnable {

    @Override
    public void run() {
        try {
            long beginTime = System.currentTimeMillis();
            System.out.println("每隔1秒输出[0,10]区间的整数,开始...");
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println("数字:" + i);
            }
            System.out.println("每隔1秒输出[0,10]区间的整数,结束...");
            long endTime = System.currentTimeMillis();
            System.out.println("共耗时:" + (endTime - beginTime)/1000 + "秒.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class ThreadTest05 {
    public static void main(String[] args) {
        Task t = new Task();
        new Thread(t).start();
    }
}

输出结果:

使用独立线程输出【0,10】区间的整数,每输出一个数字,线程通过TimeUnit.SECONDS.sleep(1)语句休眠1秒。

线程让步yield()方法

Thread类都yield()方法对线程调度器发出一个暗示,即当前线程愿意让出正在使用使用的处理器。调度程序可以响应暗示请求,也可以自由地忽略这个提供。(线程调用yield()方法后,可能从running状态转为runnable状态)。

public class Thread{
       public static native void yield();
 }

 

需要强调的是:yield()仅仅是一个暗示,没有任务机制保证它一定会被采纳。线程调度器是Java线程机制的底层对象,可以把CPU的使用权从一个线程转移到另一个线程。如果你的计算机是多核处理器,那么分配线程到不同的处理器执行任务要依赖线程调度器。

1、新建任务类ListOff,用于倒计时显示,使用sleep()作为线程延时:

  class ListOff implements Runnable {
    private int countDown = 5;
    @Override
    public void run() {
        while(countDown--  > 0) {
            String info = Thread.currentThread().getId() + "#" + countDown;
            System.out.println(info);
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (Exception e) {
            }
        }
    }
}

2、创建一个倒计时器,两个线程同时使用。参见运行2结果可知吗,每次的运行结果都不同,而且都不正确。 其关键原因是countDown唯一,两个线程可能同时访问这块内存,后面我们可以通过加锁的方式解决这个问题:

/**
 创建一个倒计时器,两个线程同时使用。参见运行2结果可知吗,每次的运行结果都不同,而且都不正确。
 其关键原因是countDown唯一,两个线程可能同时访问这块内存,后面我们可以通过加锁的方式解决这个问题。
 */

public class ThreadTest05 {
    public static void main(String[] args) {
        ListOff lf = new ListOff();   //创建一个倒计时器
        new Thread(lf).start();
        new Thread(lf).start();
    }
}

 第一次运行:

第二次运行:

 

 每次运行都不一样。

3、把sleep()代码修改为yield(),三次测试的结果输出都是正确的。而且yield()方法起到了线程让步的效果(此时没有使用锁,不能保证每次的运行结果都正确):

class ListOff implements Runnable {
    private int countDown = 5;
    @Override
    public void run() {
        while(countDown--  > 0) {
            String info = Thread.currentThread().getId() + "#" + countDown;
            System.out.println(info);
            Thread.yield();     //让步暗示
        }
    }
}

运行结果1:

运行结果2:

 

 

线程优先级

每个线程都有优先级,具有较高优先级的线程可能优先获得CPU的使用权。创建一个新的Thread对象时,新线程的优先级默认与创建线程的优先级一致。

JDK中实际上存在着10个优先级,但是这与大多数操作系统不能建立很好的映射关系。比如Windows有7个线程优先级设置,而Sun的Solaris只有两线程优先级,因此在Java中一般使用下面的三种优先级设置。

public class Thread{
    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;
}

可以通过Thread类中setPriority()方法对线程的优先级进行设置,参考JDK的源码:

 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);
        }
    }

这里面需要注意的是,如果设置的线程优先级小于 1 (MIN_PRIORITY) 或者大于10 (MAX_PRIOPITY) 都将抛出异常 : IllegalArgunmentException异常

注意:不能过分依赖于线程优先级的设置,理论上线程优先级高的优先执行,但实际情况可能并不明确,例如,线程调度机制还没有来得及介入时,线程可能就已经执行完了。所以优先级具有一定的“随机性”。

线程优先级与资源竞争

具有较高优先级的线程会优先得到调度系统资源分配。也就是说优先级高的线程可以优先竞选共享资源。但线程的优先级调度和底层操作系统有密切的关系,在各个平台上表现不一并且无法精确控制。因此在要求严格的场合,需要开发者在应用层解决线程调度问题。

当调用Thread.yield()方法时,会给线程调度器一个暗示,即优先级高的其他线程或相同优先级的其他线程,都可以优先获得CPU分片

案例:大型浮点运算测试

案例场景描述:创建6个线程,每个线程都计算了足够量级的浮点运算(浮点运算比较费时),目的是线程调度机制来得及介入。其中将1个线程的优先级设置为最高(MAX_PRIORITY=10),1个线程的优先级设置为默认(NORM_PRIORITY=5),其余4个线程优先级设置为最低(MIN_PRIORITY=1)。为了保证计算的精度,在代码中使用了BigDecimal对象。

//案例测试
public class ThreadTest06 implements Runnable {
    private int priority;
    public ThreadTest06(int priority) {
        this.priority = priority;
    }
    @Override
    public void run() {
        BigDecimal value = new BigDecimal("0");
        //按照参数传递值修改当前线程优先级
        Thread.currentThread().setPriority(priority);
        BigDecimal pi = new BigDecimal(Math.PI);
        BigDecimal e = new BigDecimal(Math.E);
        //足够耗时的计算,使任务调度器可以反应
        for (int i = 0; i < 3000; i++) {
            for (int j = 0; j < 3000; j++) {
                value = value.add(pi.add(e).divide(pi,4));
            }
        }
        Thread self = Thread.currentThread();
        System.out.println("线程编号为" + self.getId() + ", 优先级为"+
                self.getPriority() + ", 计算结果为" + value.doubleValue());
    }

    public static void main(String[] args) {
        new Thread(new ThreadTest06(Thread.MIN_PRIORITY)).start();
        new Thread(new ThreadTest06(Thread.MIN_PRIORITY)).start();
        new Thread(new ThreadTest06(Thread.MIN_PRIORITY)).start();
        new Thread(new ThreadTest06(Thread.MIN_PRIORITY)).start();
        new Thread(new ThreadTest06(Thread.NORM_PRIORITY)).start();
        new Thread(new ThreadTest06(Thread.MAX_PRIORITY)).start();
    }
}

 运行结果:

守护线程:

守护线程的概念

 

在Java线程中有两种线程,一种是用户线程另一种是守护线程(Daemon)

所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如,垃圾回收线程就是一个很职称的守护线程者(当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用)。

Deamon线程与用户线程在使用时没有任何区别,唯一的不同是:当所有用户线程结束时,程序也会终止,Java虚拟机不管是否存在守护线程,都会退出。

public class ThreadTest07{
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while(true) {
                    System.out.println(Thread.currentThread().getId() +
                            ",run...");
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e){

                    }
                }
            }
        });
        t1.setDaemon(true);      //设置为守护线程  ,会用户线程结束后,此线程也结束!
        t1.start();
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getId() + ", i =" + i);  //运行5次结束
                    try {
                        Thread.sleep(1000);
                    }catch (Exception e) {

                    }
                }
            }
        });
        t2.start();
        System.out.println(Thread.currentThread().getName() + "-主线程结束...");
    }

}

运行结果:

 结论:

守护线程会随着用户线程的结束然后自动退出。

这里提出一个问题,是不是每使用用户线程会紧跟着一个守护线程?

在使用守护线程时需要注意以下几点:

1、setDeamon()方法必须在start()方法之前设置,否则会抛出一个IllegalThreadStateException异常。不能把正在运行的常规线程设置为守护线程。

2、在守护线程Deamon中产生的新线程也是守护线程,存在着继承性。

3、守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

4、守护线程通常都使用while(true)的死循环来持续执行任务。

案例:清道夫与工作者

案例场景描述:设置守护线程判断每位员工是否可以下班。要求:员工工作时间必须大于或等于8小时才能够下班,否则守护进程不能同意员工下班。

测试:

1、使用随机数模拟工人的上下班时间,工人能否下班由守护线程决定:

class Worker extends Thread {
    //上班打卡时间
    private Date beginTime;
    //下班打卡时间
    private Date endTime;
    //下班状态
    private boolean isStop;

    public Worker(Date beginTime) {
        this.beginTime = beginTime;
    }

    public void run() {
        Thread self = Thread.currentThread();
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        String bStr = sdf.format(beginTime);
        System.out.println(self.getId() + "号员工" + bStr + "打卡上班");
        Calendar cal = Calendar.getInstance();
        cal.setTime(beginTime);
        Random random = new Random();
        while (true) {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {

            }
            if (isStop) {
                String eStr = sdf.format(endTime);
                System.out.println(self.getId() + "员工号" + eStr + "下班------");
                break;
            } else {
                int hour = random.nextInt(5);
                cal.set(Calendar.HOUR_OF_DAY, cal.get(Calendar.HOUR_OF_DAY) + hour);
                endTime = cal.getTime();
            }
        }
    }

    public void setStop(boolean stop) {
        isStop = stop;
    }
    public boolean getStop(){
        return isStop;
    }
    public Date getEndTime() {
        return endTime;
    }
    public double getWorkerLongTime() {
        return (endTime.getTime() - beginTime.getTime())/(1000*60*60);
    }
}

 2、定义一个守护线程,定时监督工人的下班状态:

class Sweeper extends  Thread {
    private List<Worker> workers = new ArrayList<Worker>();
    public Sweeper() {
        this.setDaemon(true);           //守护线程
    }
    public void run(){
        while(true) {
            for (int i = 0; i < workers.size(); i++) {
                Worker worker = workers.get(i);
                if (worker.getEndTime() != null && !worker.getStop()) {
                    double longTime = worker.getWorkerLongTime();
                    if (longTime >= 8) {
                        worker.setStop(true);
                        System.out.println(worker.getId() + "号员工超过8小时,可以下班!");
                    }else{
                        System.out.println(worker.getId() + "号员工不足8小时,不能下班...");
                    }
                }
               try {
                   TimeUnit.SECONDS.sleep(1);
               }catch (Exception e){

               }
            }
        }
    }
    public void addWorker (Worker worker) {
        this.workers.add(worker);
    }
}

3、创建三个工人和一个守护线程,模拟下班监督的工作场景:

public class WorkerTest {
    public static void main(String[] args) {
        Sweeper sweeper = new Sweeper();
        sweeper.start();
        Worker w1 = new Worker(new Date());
        sweeper.addWorker(w1);
        Worker w2 = new Worker(new Date());
        sweeper.addWorker(w2);
        Worker w3 = new Worker(new Date());
        sweeper.addWorker(w3);
        w1.start();
        w2.start();
        w3.start();

    }
}

运行结果:

补充: 

单核CPU与多核CPU的理解:

单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费只有一个工作人员在收费,只有收了费才能通过,那么CPU             就好比收费人员。如果有某个人不想交钱,那么收费人员可以把他 “挂起” (晾着他,               等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不               出来。如果是多线程的话,才能更好的发挥多线程的效率。(现在的服务器都是多

           核的)

一个Java应用程序Java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

并行与并发的理解:

  • 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事

  • 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java多线程技术详解(全都是细节!) 的相关文章

  • Java Runtime.getRuntime().freeMemory() 问题

    我搜索并看到了一些线程 但没有一个能够解决我遇到的具体问题 我正在尝试使用以下方式监视我的内存使用情况Runtime getRuntime freeMemory Runtime getRuntime maxMemory and Runtim
  • Spring Security 自定义过滤器

    我想自定义 Spring security 3 0 5 并将登录 URL 更改为 login 而不是 j spring security check 我需要做的是允许登录 目录并保护 admin report html 页面 首先 我使用教
  • 如何在 Antlr4 中为零参数函数编写语法

    我的函数具有参数语法 如下面的词法分析器和解析器 MyFunctionsLexer g4 lexer grammar MyFunctionsLexer FUNCTION FUNCTION NAME A Za z0 9 DOT COMMA L
  • Java:在 eclipse 中导出到 .jar 文件

    我正在尝试将 Eclipse 中的程序导出到 jar 文件 在我的项目中 我添加了一些图片和 PDF s 当我导出到 jar 文件时 似乎只有main已编译并导出 我的意愿是如果可能的话将所有内容导出到 jar 文件 因为这样我想将其转换为
  • Spring Data JPA 选择不同

    我有一个情况 我需要建立一个select distinct a address from Person a 其中地址是 Person 内的地址实体 类型的查询 我正在使用规范动态构建我的 where 子句并使用findAll Specifi
  • 通往楼梯顶部的可能路径

    这是一个非常经典的问题 我听说谷歌在他们的面试中使用过这个问题 问题 制定一个递归方法 打印从楼梯底部到楼梯顶部的所有可能的独特路径 有 n 个楼梯 您一次只能走 1 步或 2 步 示例输出 如果它是一个有 3 级楼梯的楼梯 1 1 1 2
  • 如何将 Mat (opencv) 转换为 INDArray (DL4J)?

    我希望任何人都可以帮助我解决这个任务 我正在处理一些图像分类并尝试将 OpenCv 3 2 0 和 DL4J 结合起来 我知道DL4J也包含Opencv 但我认为它没什么用 谁能帮我 如何转换成 INDArray 我尝试阅读一些问题here
  • ConcurrentHashMap 内部是如何工作的?

    我正在阅读有关 Java 并发性的 Oracle 官方文档 我想知道Collection由返回 public static
  • 如何检测 Java 字符串中的 unicode 字符?

    假设我有一个包含 的字符串 我如何找到所有这些 un icode 字符 我应该测试他们的代码吗 我该怎么做呢 例如 给定字符串 A X 我想将其转换为 AYXY 我想对其他 unicode 字符做同样的事情 并且我不想将它们存储在某种翻译映
  • 了解joda时间PeriodFormatter

    我以为我明白了 但显然我不明白 你能帮我通过这些单元测试吗 Test public void second assertEquals 00 00 01 OurDateTimeFormatter format 1000 Test public
  • 内部存储的安全性如何?

    我需要的 对于 Android 我需要永久保存数据 但也能够编辑 并且显然是读取 它 用户不应访问此数据 它可以包含诸如高分之类的内容 用户不得对其进行编辑 我的问题 我会 并且已经 使用过Internal Storage 但我不确定它实际
  • Java实现累加器类,提供Collector

    A Collector具有三种通用类型 public interface Collector
  • Freemarker 和 Struts 2,有时它计算为序列+扩展哈希

    首先我要说的是 使用 Struts2 Freemarker 真是太棒了 然而有些事情让我发疯 因为我不明白为什么会发生这种情况 我在这里问是因为也许其他人有一个想法可以分享 我有一个动作 有一个属性 说 private String myT
  • 使用架构注册表对 avro 消息进行 Spring 云合约测试

    我正在查看 spring 文档和 spring github 我可以看到一些非常基本的内容examples https github com spring cloud samples spring cloud contract sample
  • Hamcrest Matchers - 断言列表类型

    问题 我目前正在尝试使用 Hamcrest Matchers 来断言返回的列表类型是特定类型 例如 假设我的服务调用返回以下列表 List
  • Resteasy 可以查看 JAX-RS 方法的参数类型吗?

    我们使用 Resteasy 3 0 9 作为 JAX RS Web 服务 最近切换到 3 0 19 我们开始看到很多RESTEASY002142 Multiple resource methods match request警告 例如 我们
  • 使用按钮作为列表的渲染器

    我想使用一个更复杂的渲染器 其中包含列表的多个组件 更准确地说 类似于this https stackoverflow com questions 10840498 java swing 1 6 textinput like firefox
  • 如何重新启动死线程? [复制]

    这个问题在这里已经有答案了 有哪些不同的可能性可以带来死线程回到可运行状态 如果您查看线程生命周期图像 就会发现一旦线程终止 您就无法返回到新位置 So 没有办法将死线程恢复到可运行状态 相反 您应该创建一个新的 Thread 实例
  • Java中HashMap和ArrayList的区别?

    在爪哇 ArrayList and HashMap被用作集合 但我不明白我们应该在哪些情况下使用ArrayList以及使用时间HashMap 他们两者之间的主要区别是什么 您具体询问的是 ArrayList 和 HashMap 但我认为要完
  • org.apache.commons.net.io.CopyStreamException:复制时捕获 IOException

    我正在尝试使用以下方法中的代码将在我的服务器中创建的一些文件复制到 FTP 但奇怪的是我随机地低于错误 我无法弄清楚发生了什么 Exception org apache commons net io CopyStreamException

随机推荐

  • 使用Java的File类实现目录拷贝

    目录 准备工作 Case Analysis 代码实现 总结 准备工作 首先 我们来熟悉下File类 在 Java 中 File 类是 java io 包中唯一代表磁盘文件本身的对象 也就是说 如果希望在程序中操作文件和目录 则都可以通过 F
  • otsu算法_OTSU算法详解

    OTSU是阈值分割中一种常用的算法 它可以根据图像自动生成最佳分割阈值 OTSU的核心思想是类间方差最大化 OTSU算法详解 令 表示一幅大小为 像素的数字图像中的 个不同的灰度级 表示灰度级为 的像素数 图像中的像素总数为 像素的灰度级为
  • VS2017序列号

    趁着这两天微软发布了Visual Studio 2017 安装体验了这个史上最强IDE最新版 分享一下自己的安装过程 下载地址点击这里 该版本堪称史上最大IDE 随便勾了几个选项 就要占用几十个GB的安装空间 最后果断选择了最小安装包 只要
  • uniapp中单选按钮的实现

    标签说明 radio group 单项选择器 内部由多个
  • Java核心技术卷 学习Day02

    java学习 复习 本文主要参照 Java核心技术卷 作为学习对象 第四章 对象与类 1 类 面向对象程序设计OOP 类 封装实例字段 方法 类 gt 继承 is a 依赖 uses a 聚合 has a gt 类 2 预定义类 Math
  • JAVA---抽象类和接口基础知识详解(及两者异同点)

    在本篇博客中将介绍JAVA里抽象类和接口的基础知识以及两者的异同点 在有继承和多态的基础知识上学习会更好 目录 抽象类基础知识 抽象类的定义 创建等基础 抽象类的几点说明 一 为何使用抽象方法 抽象类的几点说明 二 接口基础知识 接口的定义
  • 【Zotero高效知识管理】(1)Zotero介绍

    Zotero高效知识管理 专栏其他文章 Zotero文献管理软件的系统性教程 包括安装 全面的配置 基于众多插件的文献导入 管理 引用 笔记方法 Zotero高效知识管理 2 Zotero的安装 百度云存储配置及常用插件安装 Zotero高
  • jsp调用controller方法_SpringMVC五大核心组件及调用过程

    Spring Web MVC 五大核心组件 DispatcherServlet 控制器入口 负责分发请求 HandlerMapping 负责根据请求 找到对应的控制器 Controller 真正处理请求的控制器 ModelAndView 封
  • 用于光栅仿真的非偏振光–实例讨论

    摘要 像光栅这样的光学设备对光的偏振比较敏感 因此 在仿真中适当考虑光的偏振非常重要 在实际中 光栅有时会以非偏振光作为输入 作为两个正交偏振态的平均值 我们为您展示了如何在VirtualLab Fusion中建模这种用于光栅仿真的非偏振光
  • C语言《文件操作》事无巨细,保姆级介绍,通俗易懂

    目录 1 文件名与文件分类 2 文件操作使用 2 1文件的打开与关闭 3 文件操作函数 3 1其他文件函数 1 fseek 2 ftell 3 rewind 4 文件结束的判定 4 1被错误使用的 feof 4 2文件结束的正确判断 5 文
  • negix安装部署

    1 从nginx官网下载Nginx wget http nginx org download nginx 1 8 1 tar gz 2 解压Nginx tar zxvf nginx 1 8 1 tar gz 3 初始化配置 configur
  • OpenCV(3.4.1) Error: Assertion failed (scn == 3

    错误 OpenCV 3 4 1 Error Assertion failed scn 3 scn 4 in cv cvtColor file D Build OpenCV opencv 3 4 1 modules imgproc src c
  • vi的一些操作

    1 u可撤销操作 2 yy复制当前行 3 p粘贴复制的行 4 dd删除一行 5 xxx可以定位xxx所在位置 6 n或ngg或nG可以跳转到第n行 以下设置是临时的 只在当前vi中生效 但可将其添加在配置文件 etc virc中使每次按文件
  • STM32CubeMX的使用教程

    STM32 关于STM32CubeMX的使用 打开Cube 点击File New Project 搜索芯片型号这边选择的是STM32L071CB系列上面菜单Docs Resources可以下载数据手册程序手册等 点击Start Projec
  • DEBUG:only Tensors of floating point dtype can require gradients

    DEBUG only Tensors of floating point dtype can require gradients 解决 x V t arange 2 4 float requires grad True
  • 4_hytrix_信号量_线程池

    文章目录 Hystrix 核心特性和原理 使用 单独使用 整合 Feign 整合RestTemplate 信号量与线程隔离 线程池隔离 信号量隔离 常用配置 hystrix 使用dashboard Hystrix 核心特性和原理 熔断 连续
  • C strtok strtok_s 函数说明 按分隔符分解字符串

    1 说明 1 1 函数签名如下 char strtok char str const char delimiters 1 str 要被分解成一组小字符串的字符串 2 delimiters 包含分隔符的 C 字符串 1 2 返回值 该函数返回
  • DPDK的PMD(uio/igb_uio/vfio-pci/uio_pci_generic)

    目录 linux收包的方式 中断对性能的影响有多大 轮询对性能的提升有多大 PMD 介绍 收包对比 内核收包的弊端 DPDK 收包的优点 uio igb uio uio pci generic vfio pci igb uio IGB UI
  • MATLAB算法实战应用案例精讲-【集成算法】集成学习模型stacking(附Python和R语言代码)

    目录 前言 几个高频面试题目 1 哪种集成技术更优 什么情况下选择哪种集成技术呢
  • Java多线程技术详解(全都是细节!)

    多线程启动 线程有两种启动方式 实现Runnable接口 继承Thread类并重写run 方法 1 Thread 与 Runnable Runnable接口表示线程要执行的任务 当Runnable中的run 方法执行时 表示线程在激活状态