Java 并发编程(Ⅰ)

news2024/12/25 8:55:20

目录

  • 1. 概念
    • 1. 基本概念
    • 2. 线程的状态
  • 2. 线程的初始化
    • 1. new Thread()
    • 2. new Thread(new Runnable())
    • 3. Thread 和 Runnable 的关系
    • 4. new Thread(new FutureTask(new Callable()))
  • 3. 常用方法
    • 1. start
      • 1. 线程组
      • 2. start 和 run
    • 2. sleep
      • 1. TimeUtil
      • 2. InterruptedException
    • 3. setPriority
    • 4. yield
    • 5. join
    • 6. interrupt
      • 1. 方法简介
      • 2. 中断线程
    • 7. park
    • 8. setDaemon


1. 概念

1. 基本概念

进程和线程:

  • 图示:
  • 文字:
    • 进程:程序的一个实例,一个程序可以有多个实例,如下图

      现在QQ这一个程序就拥有了两个进程
    • 线程:线程就是进程的执行单元,一个进程中有多个线程
  • 概念:
    • 进程:进程是 分配和管理资源 的基本单位,不同进程会竞争计算机系统资源
    • 线程:线程是 最小调度单位(处理器调度的基本单位)
  • 区别:
    • 独立 | 共享
      • 线程之间共享本进程的地址空间、资源(内存、CPU、IO等)
      • 每个进程都有独立的地址空间,资源独立
    • 健壮 ?
      • 一个线程崩溃之后,整个进程都要崩溃
      • 一个进程崩溃之后,在保护模式下不会对其他进程产生影响
    • 开销?
      • 线程依附于进程运行,显然进程开销大(包括启动开销、切换开销)
    • 通信
      • 同一台计算机的进程通信称为 IPC(Inter-process communication),不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
      • 线程因为共享进程资源,所以通信很简单,比如说共享一个静态资源

并发与并行:
单核CPU同一时间只能执行一个线程,即 串行执行 。操作系统中的 任务调度器 会将 CPU 的时间片(时间很短)分给不同的线程使用。因为 CPU 在线程间切换非常快,会给人一种多线程同时运行的错觉。

  • 并发(concurrent): 同一时间应对(dealing with)多件事的能力
  • 并行(parallel): 同一时间做(doing)多件事的能力

同步与异步:

  • 同步: 即串行执行,需要等前面的代码返回之后,才能继续执行后续的代码。

    @Slf4j
    public class ShowSync {
    
        public static void count(int i) {
            log.info("第" + i + "次执行count方法");
        }
    
        public static void main(String[] args) {
            for (int i = 0; i < 3; i++) {
                count(i);
            }
            log.info("线程:" + Thread.currentThread().getName());
        }
    }
    
  • 异步: 不需要等待前面的代码返回,就能执行下面的代码。

    @Slf4j
    public class ShowAsync {
        public static void main(String[] args) {
            for (int i = 0; i < 3; i++) {
                new Thread(() ->
                    log.info("线程:" + Thread.currentThread().getName())
                ).start();
            }
            log.info("线程:" + Thread.currentThread().getName());
        }
    }
    

2. 线程的状态

Thread 类中提供了一个枚举类 State,如下:

public enum State {

        NEW,
        
        RUNNABLE,

        BLOCKED,

        WAITING,

        TIMED_WAITING,

        TERMINATED;
    }

可见,一个Java线程有 六大状态 。可实际上,我们会把 RUNNABLE 分为 ReadyRUNNABLE 两大状态,如下图所示,所以我们一般会说:一个Java线程有 七大状态

  • NEW
    • Thread t1 = new Thread() 进入此状态
  • READY
    • t.start() 进入此状态
    • 此时线程已经可以运行,但还未获取到 CPU 时间片,处于就绪状态
  • RUNNING
    • 就绪的线程获取到了 CPU 时间片,进入运行状态
  • BLOCKED
    • 线程进入 synchronized 修饰的方法或代码块
  • TERMINATED
    • 线程的 run() 跑完或者 main() 跑完
  • WAITING
    • 进入此状态的线程不会被分配 CPU时间片,需要被其他线程唤醒,不然会一直等待下去
  • TIMED_WAITING
    • 进入此状态的线程不会被分配 CPU时间片,不需要被其他线程唤醒,到达一定时间之后会自动苏醒

至于线程中的状态转换及相关方法,我将在后文陆续介绍。

2. 线程的初始化

线程的初始化,也就是 new Thread(),我们有三种方式来实现初始化,分别是:

  • 直接 new Thread() 然后重写 run()
  • new Thread(new Runnable()) ,将 Runnablerun() 组装到 Thread
  • new Thread(new FutureTask(new Callable())) ,将 Callable 中的 call() 先组装到 FutureTask 再组装到 Thread

1. new Thread()

直接 new Thread() 初始化线程,是将线程(Thread)和任务(run())绑在一起,实际开发中不建议使用,代码如下:

@Slf4j
public class JustThread {
    public static void main(String[] args) {
        Thread thread1 = new Thread("线程1") {
            @Override
            public void run() {
                log.info("线程:" + Thread.currentThread().getName() + " 运行中...");
            }
        };
        thread1.start();

        log.info("线程:" + Thread.currentThread().getName() + " 运行中...");

        /*
            lambda表达式
         */
        Thread thread2 = new Thread(() -> {
            log.info("线程:" + Thread.currentThread().getName() + " 运行中...");
        }, "线程2");
        thread2.start();
    }
}

2. new Thread(new Runnable())

将线程(Thread)和任务(Runnable)分开初始化,然后组合在一起。

@Slf4j
public class RunnableAndThread {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                log.info("线程:" + Thread.currentThread().getName() + " 运行中...");
            }
        };
        Thread thread = new Thread(runnable, "线程3");
        thread.start();

        /*
            lambda表达式
         */
        Runnable runnable1 = () -> log.info("线程:" + Thread.currentThread().getName() + " 运行中...");
        Thread thread1 = new Thread(runnable1, "线程4");
        thread1.start();
    }
}

3. Thread 和 Runnable 的关系

先看一下 Runnable 的源码,如下:

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

一个函数式接口,只有一个 run()

然后看一下 Thread 的部分源码:

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null, true);
    }

好的,就看这几段方法,就不在这里做深入分析了。从上面的代码,我们可以很清晰地弄懂 ThreadRunnable 之间的关系:

  • new Thread 的时候,如果没有传入 Runnable,就会重写 Threadrun()
  • new Thread 的时候,如果传入了 Runnable,就会使用 Runnable 中的 run()

所以,Runnable 接口就是为了给 Thread 提供一个方法体 run()

在实际开发中,我们通常会自定义一个类来 implements Runnable,然后将此类传入 Thread 中。案例如下:

  • 定义一个每间隔十秒打印一下当前时间的线程
public class TimePrinter implements Runnable{
    private SimpleDateFormat dateFormat= new SimpleDateFormat("hh:mm:ss");

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println("当前时间为: " + dateFormat.format(new Date()));
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class TaskDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(new TimePrinter(), "测试线程");
        thread.start();
    }
}

4. new Thread(new FutureTask(new Callable()))

@Slf4j
public class FutureTaskAndThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {


        FutureTask<Long> futureTask = new FutureTask<>(new Callable<Long>() {
            @Override
            public Long call() throws Exception {
                long start = System.currentTimeMillis();
                log.info(Thread.currentThread().getName() + " 运行中...");
                TimeUnit.MILLISECONDS.sleep(123);
                long end = System.currentTimeMillis();
                return end - start;
            }
        });
        Thread thread = new Thread(futureTask, "线程5");
        thread.start();
        log.info(thread.getName() + "花费了 " + futureTask.get() + " 毫秒");
    }
}

Runnable与Callable的关系如下图:
可见,相比于 Runnablerun()Callablecall() 多了一个返回值。

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

3. 常用方法

线程状态的切换:

TIMED_WAITING 和 WAITING 的区别:

  • TIMED_WAITING 即使没有外部信号,在等待时间超时后,线程也会恢复
  • WAITING 需要等待外部信号来唤醒

1. start

先来看一下源码:

	// 线程组
	private ThreadGroup group;
	// NEW 状态的线程的 threadStatus = 0
	private volatile int threadStatus = 0;

    public synchronized void start() {

        if (threadStatus != 0)
            throw new IllegalThreadStateException();
		// 将要启动(start)的线程装入线程组中
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                	// 告诉组,这个线程启动(start)失败
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {

            }
        }
    }

	// native方法,调用本地操作系统开启一个新的线程
    private native void start0();

看完源码之后,可以得出以下结论:

  • start 启动一个线程需要经历以下步骤:
    1. 将调用 start 的线程装入线程组 ThreadGroup
    2. 调用native方法,启动线程

1. 线程组

此处参考链接:https://zhuanlan.zhihu.com/p/61331974

线程组(Thread Group): 一个线程集合,可以很方便地管理一个组中的线程。

线程组树:

  • System线程组: 处理JVM的系统任务的线程组,例如对象的销毁等

  • main线程组:包含至少一个线程——main(用来执行main方法)

  • 面线程组的子线程组: 应用程序创建的线程组

        public static void main(String[] args) {
            // 输出当前线程组——main线程组
            System.out.println(Thread.currentThread().getThreadGroup().getName());
            // 输出当前线程组的父线程组——System
            System.out.println(Thread.currentThread().getThreadGroup().getParent().getName());
        }
    

给线程指定线程组:

  • 在初始化线程的时候如果不指定数组,就会默认指定为 main线程组,代码如下:

    private void init(ThreadGroup g, Runnable target, String name,
                          long stackSize, AccessControlContext acc,
                          boolean inheritThreadLocals) {
                          
            /*
    			其余代码省略
    		*/
            Thread parent = currentThread();
            SecurityManager security = System.getSecurityManager();
            if (g == null) {
              
                if (security != null) {
                    g = security.getThreadGroup();
                }
    
                if (g == null) {
                    g = parent.getThreadGroup();
                }
            }
    
            /*
    			其余代码省略
    		*/
    }
    
  • 可以利用以下任意一个构造器来给线程指定一个线程组:

        public Thread(ThreadGroup group, Runnable target)
    
        public Thread(ThreadGroup group, Runnable target) 
    
    	public Thread(ThreadGroup group, Runnable target, String name)
    
        public Thread(ThreadGroup group, Runnable target, String name, long stackSize)
    

    演示一下:

        public static void main(String[] args) {
            ThreadGroup group = new ThreadGroup("我的线程组");
            Thread thread = new Thread(group, "我的线程");
            System.out.println(thread.getName() + " 的线程组是 " + group.getName());
        }
    

线程组方法解析: https://blog.csdn.net/a1064072510/article/details/87455525

小结一下: 将线程装入线程组就是为了方便批量操作线程,比如说我要通知好几个线程要终止了,就可以事先把这几个线程装入一个线程组,然后直接通过线程组批量通知:

// 通知main线程组中的所有线程要终止了
Thread.currentThread().getThreadGroup().interrupt();

2. start 和 run

问:为什么 start 能启动一个线程,而 run 就不行呢?

答:因为 Java 并不能直接作用于操作系统,所以需要调用native方法(比如C、C++等编写的方法)来告诉操作系统开启新的线程。而在 start 方法中调用了native方法 start0 来启动线程。至于 run, 这就是线程中的一个方法,当线程跑起来的时候,就会去执行 run,当 run 方法执行完毕线程就会结束。

2. sleep

先来看一下 Thread.sleep 的源码:

	/**
	*让当前线程睡眠指定毫秒数,线程不会丢失任何monitors的所有权
	*/
    public static native void sleep(long millis) throws InterruptedException;

好的,现在来使用一下:

@Slf4j
public class Sleep_md{
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            long start = System.currentTimeMillis();
            log.info("t1睡眠5s");
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
            }
            long end = System.currentTimeMillis();
            log.info("已过去 {} 毫秒", (end - start));
        });
        t1.start();
    }
}

通过上述代码的运行,可发现 Thread.sleep 可以让当前线程进入 TIMED_WAITING 睡眠一段时间,然后进入 RUNNABLE 。在此期间会让出CPU,给其他线程机会。

1. TimeUtil

除了 Thread.sleepTimeUtil 也有 sleep,可以让线程进入睡眠,两者效果相同。

    public void sleep(long timeout) throws InterruptedException {
        if (timeout > 0) {
            long ms = toMillis(timeout);
            int ns = excessNanos(timeout, ms);
            Thread.sleep(ms, ns);
        }
    }

使用方法:

// 睡眠n纳秒
TimeUtil.NANOSECONDS.sleep(long n)
// 睡眠n微秒
TimeUtil.MICROSECONDS.sleep(long n)
// 睡眠n毫秒
TimeUtil.MILLISECONDS.sleep(long n)
// 睡眠n秒
TimeUtil.SECONDS.sleep(long n)
// 睡眠n分钟
TimeUtil.MINUTES.sleep(long n)
// 睡眠n小时
TimeUtil.HOURS.sleep(long n)
// 睡眠n天
TimeUtil.DAYS.sleep(long n)

2. InterruptedException

当睡眠的线程被打断睡眠,就会抛出 InterruptedException

@Slf4j
public class Sleep_md{
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            log.info("开始睡眠");
            long start = System.currentTimeMillis();
            try {
            	// 睡眠 60s
                Thread.sleep(1000 * 60);
            } catch (InterruptedException e) {
                long end = System.currentTimeMillis();
                log.info("中途停止睡眠");
            }
        });
        thread.start();
        thread.interrupt();
    }
}

肉眼可见, interrupt() 打断了睡眠,抛出了异常,被catch到了。

3. setPriority

setPriority() —— 设置线程优先级,数值由 1 — 10,数值越小,优先级越高。
注意: 更高的优先级表明更有机会争夺到 CPU 时间片,而不是优先级越高就一定越先执行。

  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片
  • cpu 闲时,优先级几乎没作用
@Slf4j
public class Priority_md {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> log.info("t1运行中..."), "t1");
        t1.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        Thread t2 = new Thread(() -> log.info("t2运行中..."), "t2");
        t2.setPriority(Thread.MIN_PRIORITY);
        t2.start();
        log.info("main运行中...");
    }
}
  • 3个常量

        public final static int MIN_PRIORITY = 1;
        public final static int NORM_PRIORITY = 5;
        public final static int MAX_PRIORITY = 10;
    
  • 当设置优先级的时候,值不在 [1,10] 的区间,就会抛出异常 IllegalArgumentException

  • main线程的优先级是5,自定义线程的优先级初始值也是5

    Thread main = Thread.currentThread();
    log.info("main线程的优先级是: " + main.getPriority());
    

4. yield

Thread.yield() —— 提示线程调度器,当前线程愿意让出CPU时间片回到就绪状态,线程调度器可以选择忽视该提示(可能会让出失败)。

注意:

  • 让出时间片并不一定会成功,这都取决于线程调度器
  • 让出的时间片不一定会被高优先级的线程抢到,因为在CPU不紧张的时候,优先级没啥用,就算CPU比较紧张,也只是提高了高优先级线程抢时间片的能力
@Slf4j
public class Yield_md {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runner(), "张三");
        t1.setPriority(Thread.MIN_PRIORITY);
        Thread t2 = new Thread(new Runner(), "李四");
        t2.setPriority(Thread.MIN_PRIORITY);
        Thread t3 = new Thread(new Runner(), "王五");
        t3.setPriority(Thread.MIN_PRIORITY);

        for (int i = 1; i < 10; i++) {
            Thread thread = new Thread(new Runner(), "路人" + i);
            thread.setPriority(Thread.MAX_PRIORITY);
            thread.start();
        }

        t1.start();
        t2.start();
        t3.start();
    }

    private static class Runner implements Runnable {

        @Override
        public void run() {
            for (int i = 1; i < 8; i++) {
                if (i % 4 == 0) {
                    log.info(Thread.currentThread().getName() + " 摔了一跤!");
                    Thread.yield();
                }
                if (i == 7) {
                    log.info(Thread.currentThread().getName() + " 完成了比赛!");
                }
            }
        }
    }
}

yield 对比 sleep:

  • yield 成功后直接让线程进入 READYsleep 会进入 WAITING 或者 TIMED_WAITING 时间到了之后才会进入。因此 yield 的线程随时都有可能重新获取到CPU继续跑,而 sleep 需要等待睡眠时间结束

5. join

join() 将指定的线程A加入到当前正在运行的线程B,线程B会进入等待状态,并等待A运行完毕之后再执行B。

@Slf4j
public class Join_md {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                for (int i = 0; i < 3; i++) {
                    log.info("t1----->" + i);
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            try {
                t1.join();
                for (int i = 0; i < 3; i++) {
                    log.info("t2----->" + i);
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "t2");
        long start = System.currentTimeMillis();
        t1.start();
        t2.start();

        try {
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        long end = System.currentTimeMillis();
        log.debug("t1: {} t2: {} cost: {}", t1, t2, end - start);
    }
}

注意:

  • join 就相当于让两个交替执行的线程顺序执行

  • 当一个线程D中有多个线程(比如A、B、C) join 之后,A、B、C会交替执行,如下:

    @Slf4j
    public class Join_md {
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(() -> {
                try {
                    log.info("t1......");
                    for (int i = 0; i < 5; i++) {
                        log.info("t1: " + i);
                    }
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }, "t1");
            Thread t2 = new Thread(() -> {
                try {
                    log.info("t2......");
                    for (int i = 0; i < 5; i++) {
                        log.info("t2: " + i);
                    }
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }, "t2");
            long start = System.currentTimeMillis();
            t1.start();
            t2.start();
    
            log.info("main...");
    
            try {
                t1.join();
                t2.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            long end = System.currentTimeMillis();
            log.debug("t1: {} t2: {} cost: {}", t1, t2, end - start);
        }
    }
    

join 对比 yield:

  • 从作用来看:join 阻塞当前线程,让其他线程先执行;yield 让当前线程让出位置,让其他线程执行
  • 从状态来看:join 会让当前线程进入 WAITINGyield 让当前线程进入 READY
  • 从效果上看:join 不会失败;yield 有可能让出资源失败

6. interrupt

1. 方法简介

interrupt 仅更改线程的中断状态(Just to set the interrupt flag),并通知那个线程,不会中断一个正在运行的线程。

isInterrupted 判断当前线程的中断状态。

Thread.interrupted 调用的当前线程的isInterrupted,重置当前线程的中断状态为true。
interrupt之后,线程isInterrupted返回为true,Thread.interrupted会将状态重置为false。

@Slf4j
public class Interrupt_md {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            if (Thread.currentThread().isInterrupted()) {
                log.info("t1已经被interrupt......");
                log.info("重置t1中断状态...");
                Thread.interrupted();
                if (!Thread.currentThread().isInterrupted()) {
                    log.info("t1状态恢复...");
                }
            } else {
                log.info("t1还没被interrupt");
            }
        });
        t1.start();
        t1.interrupt();
    }
}

以下几个方法执行中被interrupt中断会throws InterruptedExceptionThread.sleepjoinwait

2. 中断线程

判断标志位,return中断
中断一个线程就是结束它运行的run方法,所以直接return就可以了。

  • 使用 isInterrupted 作为标志位

    @Slf4j
    public class StopThread {
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
               if (Thread.currentThread().isInterrupted()) {
                   return;
               }
               for (;;) {
                   log.info("t1 is running...");
               }
            });
            t1.start();
            t1.interrupt();
        }
    }
    
  • 自定义一个标志位

    @Slf4j
    public class StopThread {
        private volatile static boolean flag = false;
    
        public static void main(String[] args) {
            Thread t1 = new Thread(() -> {
               if (flag) {
                   return;
               }
               for (;;) {
                   log.info("t1 is running...");
               }
            });
            t1.start();
            t1.interrupt();
            flag = true;
        }
    }
    

7. park

LockSupport.park 让线程休眠的方法(进入 WAITING 状态),调用后不会直接进入休眠,会先根据一个 permit 进行判断,如果 permit 存在就直接返回,不存在才会进入休眠状态(Disables the current thread for thread scheduling purposes unless the permit is available. If the permit is available then it is consumed and the call returns immediately; otherwise the current thread becomes disabled for thread scheduling purposes

有三种情况会打破休眠:

  • 其他线程调用了 LockSupport.unpark 方法
  • 调用 interrupt
  • 其他原因

LockSupport.parkNanos 相比于 LockSupport.parkLockSupport.park 需要指定一个时间,然后进入休眠状态(TIMED_WAITING),无需唤醒,时间一到就会自然苏醒。

@Slf4j
public class Park_demo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> LockSupport.park(), "t1");
        Thread t2 = new Thread(() -> LockSupport.parkNanos(1000*1000*3000L), "t2");
        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(1);
        log.info("t1's state: {}, t2's state: {}", t1.getState(), t2.getState());
    }
}

注意:

  • LockSupport.parkNanos 不会抛出异常,所以在休眠中用 interrupt 去打断也不会抛出异常

  • 可用 LockSupport.unpark 去唤醒休眠的线程

    @Slf4j
    public class Park_demo {
        public static void main(String[] args) throws InterruptedException {
            Thread t1 = new Thread(() -> {
                LockSupport.park();
                log.info("t1苏醒...");
            }, "t1");
            t1.start();
            TimeUnit.SECONDS.sleep(1);
            log.info("t1's state: {}, t1's status: {}", t1.getState(), t1.isInterrupted());
            LockSupport.unpark(t1);
            log.info("t1's state: {}, t1's status: {}", t1.getState(), t1.isInterrupted());
        }
    }
    

8. setDaemon

此节参考文章:https://www.cnblogs.com/lixuan1998/p/6937986.html

setDaemon(true) 将线程设置为守护线程。

Java线程分类:

  • 用户线程
  • 守护线程
    在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。最典型的守护线程:垃圾回收线程。
  • 两者区别:守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。

使用守护线程的注意事项:

  • setDaemon(true) 必须用在 start 之前,不然会抛出异常IllegalThreadStateException
  • 守护线程中产生的新的线程也是守护线程
  • 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断
@Slf4j
public class Daemon_md {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> log.info("t1.isDaemon: {}", Thread.currentThread().isDaemon()), "t1");
        Thread t2 = new Thread(()->{
            t1.start();
            Thread t3 = new Thread("t3");
            t3.start();
            log.info("t2.isDaemon: {}", Thread.currentThread().isDaemon());
            log.info("t3.isDaemon: {}", t3.isDaemon());
        }, "t2");
        t2.setDaemon(true);
        t2.start();
        log.info("main.isDaemon: {}", Thread.currentThread().isDaemon());
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/185593.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DCMM数据管理能力成熟度认证详解

第一部分&#xff1a;评估基础篇 First part DCMM定义 DCMM是《数据管理能力成熟度评估模型》GB/T 36073-2018国家标准&#xff0c;英文简称&#xff1a;&#xff08;Data management Capability Maturity Model&#xff09;。是我国首个数据管理领域正式发布的国家标准。旨在帮…

【ZooKeeper】

1.ZooKeeper是什么&#xff1f; 答&#xff1a; ZooKeeper是一个开放源码的分布式协调服务&#xff0c;它是集群的管理者&#xff0c;监视着集群中各个节点的状态根据节点提交的反馈进行下一步合理操作。最终&#xff0c;将简单易用的接口和性能高效、功能稳定的系统提供给用户…

100 家企业软件测试笔试面试题汇总(网友真实面试后征集)

目录 一、中科软 二、AURALOG 笔试面试&#xff08;外企&#xff09; 三、GWebs 公司笔试题 四、北京麒麟网信息技术有限公司笔试题 五、施惠特 六、总结 一、中科软 笔试题 1.谈谈你对测试的理解2.你三年的职业规划3.你对加班的看法&#xff1f;是否可以加班&#xff1f;4.你心…

推荐系统遇上深度学习(一四二)-[微软复旦]CTR预估中的对比学习框架CL4CTR

今天分享的论文为《CL4CTR: A Contrastive Learning Framework for CTR Prediction》&#xff0c;从特征表示角度入手&#xff0c;将多种对比学习损失引入到CTR预估的模型训练中&#xff0c;一起来看一下。1、背景主流的CTR预估模型大致可以分为两类&#xff0c;一类是传统的模…

【软件测试】遇到新产品的测试就懵了?这三部曲带你轻松快速上手新业务......

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 初入一家公司&#…

FreeRTOS互斥量的实验

互斥量又称互斥信号量&#xff08;本质是信号量&#xff09;&#xff0c;是一种特殊的二值信号量&#xff0c;它和 信号量不同的是&#xff0c;它支持互斥量所有权、递归访问以及防止优先级翻转的特性&#xff0c; 用于实现对临界资源的独占式处理。本章要实现的功能是&#xf…

算法训练营 day24 回溯算法 回溯算法理论基础 组合

算法训练营 day24 回溯算法 回溯算法理论基础 组合 回溯算法理论基础 回溯法也可以叫做回溯搜索法&#xff0c;它是一种搜索的方式。 虽然回溯法很难&#xff0c;很不好理解&#xff0c;但是回溯法并不是什么高效的算法。 因为回溯的本质是穷举&#xff0c;穷举所有可能&am…

钉钉微应用 - - - - 如何本地开发调试?

钉钉微应用 - 本地开发调试1. 安装DingTalk-Design-CLI2. 初始化代码模版3. 启动开发、调试功能4. 遇到的问题4.1 对应企业没有xxx域名微应用&#xff1f;&#xff1f;4.2 history、location的表现异常&#xff1f;&#xff1f;4.3 本地已经存在了H5微应用&#xff0c;也想使用…

软件体系结构与架构技术知识点万字总结

文章目录页面技术一、Spring框架1. 三层体系架构2. Spring的核心3. Spring 的Bean中主要的装配方式&#xff08;1&#xff09;基于XML的装配&#xff1a;&#xff08;2&#xff09;基于Annotation的装配&#xff1a;&#xff08;3&#xff09;自动装配&#xff1a;4. Spring框架…

Linux 中启用 SSH 密码登录

Linux 中启用 SSH 密码登录 文章目录Linux 中启用 SSH 密码登录1、更改配置文件2、设置登录密码3、完成1、更改配置文件 首先使用 管理员 权限打开/etc/ssh/sshd_config文件。 sudo vi /etc/ssh/sshd_config找到 PasswordAuthentication 选项&#xff0c;耐心查找。 当然&am…

23种设计模式之十一种行为型模式

23种设计模式之十一种行为型模式1. 设计模式概述1.1 什么是设计模式1.2 设计模式的好处2. 设计原则分类3. 详解3.1 单一职责原则3.2 开闭原则3.3 里氏代换原则3.4 依赖倒转原则3.5 接口隔离原则3.6 合成复用原则3.7 迪米特法则4. Awakening1. 设计模式概述 我们的软件开发技术也…

车道线检测源码详解

源码链接见文末 1.车道数据与标签下载 数据下载地址:因为数据的规模比较大,源码中只包含了部分数据,能够供运行代码使用 CULane:https://xingangpan.github.io/projects/CULane.html tusimple:https://github.com/TuSimple/tusimple-benchmark/issues/3 2.项目环境配置 …

数据存储:MySQL之多表连接方式

在我们走出新手村&#xff0c;开始编写系统时&#xff0c;总会遇到各种复杂的场景需要多个数据库表的联查&#xff0c;这时我们就需要掌握多表查询有几种方式&#xff0c;以便我们在各种复杂的应用场景使用适宜的连接方式。 用于测试的表&#xff1a; student表 grade表 syst…

车载以太网 - SomeIP测试专栏 - 详细解析 - 02

对于介绍SomeIP协议&#xff0c;我还是想从最基础的协议解析来&#xff0c;所以今天还是先将SomeIP协议详解给大家列举一下&#xff0c;也方便大家在工作中如果不记得哪些信息随时可以查看学习&#xff0c;也算是留给我自己的笔记吧&#xff0c;毕竟确实容易忘记。 SomeIP数据&…

【GD32F427开发板试用】基于移植BP神经网络辨认花的种类

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;卢瑟 前言 很荣幸参与这次GD32的开发板试用活动(白嫖活动)&#xff0c;由于在家条件比较有限&#xff0c;就移植了之前做过的项目。利用神经网…

【Java开发常用软件整理】

该博客整理了一些JAVA程序员常用的软件开发类软件、系统类软件&#xff0c;可以作为JAVA程序员配置Java开发基础环境的参考手册。 目录开发类软件Java8 安装IntelliJ IDEA 安装Maven 安装Git 安装MYSQL 安装系统类软件开发类软件 开发软件安装包下载地址&#xff1a;开发软件安…

ASP.NET Core Web应用程序项目部署流程

目录 一、准备ASP.NET Core应用程序部署文件 二、环境配置 三、测试 ASP.NET Core Web 应用程序 四、部署后访问失败问题 以下部署流程都是基于Windows服务器环境下进行的。 一、准备ASP.NET Core应用程序部署文件 使用 Visual Studio 开发工具创建 ASP.NET Core 的Web应…

盘点最近线程池的几个面试重要考点

有点惊叹最近的面试题&#xff0c;因为从之前的基础的面试题&#xff0c;到之后的一些涉及到分布式和微服务的面试题&#xff0c;再到现在的线程池的一些面试题&#xff0c;反正不同的面试官&#xff0c;就有不同的针对方向&#xff0c;可能现在的面试官比较想考验你的多方面的…

Hive(2):Apache Hive 安装部署

1 元数据相关名词 1.1 Metadata Metadata即元数据。元数据包含用Hive创建的database、table、表的位置、类型、属性&#xff0c;字段顺序类型等元信息。元数据存储在关系型数据库中。如hive内置的Derby、或者第三方如MySQL等。 1.2 Metastore Metastore即元数据服务。Metast…

Python中tqdm进度条的详细介绍(安装程序与耗时的迭代)

平时在做一些测试时候&#xff0c;是没有进度条出现的&#xff0c;这跟大家pip安装程序不一样(有安装进度条)&#xff0c;比如做遍历的时候&#xff1a;for i in range(10):time.sleep(0.5)print(i)只是每过0.5秒就进行打印输出&#xff0c;在这个等待过程是没有任何提示的&…