Day3 - 并发编程核心2、深入理解Thread1
(接上文)
2.3 总结
在本章节并发编程核心中,我们认识了进程和线程、线程的执行方式(串行、并发)和执行时机(同步、异步);了解了如何创建线程、进程的生命周期;通过Thread源码学习到其中使用的两种设计模式:模板方法设计模式和策略模式
三、深入理解Thread
3.1 Thread构造方法
3.1.1 不含ThreadGroup的构造方法
先认识Tread前四种构造方法
这里提一下创建线程时的命名规则,当没有设定线程名称时,线程名会按照Thread-0、Thread-1、Thread-2…累计命名,不会因中间出现的自定义名称的线程而发生编号缺失(除非将默认名称创建后,将该线程名称修改)。原因见下面源码
3.1.2 ThreadGroup
顾名思义,即线程组,用来标识同一组线程。作用此处先不讲。
可以通过以下构造方法创建ThreadGroup:
ThreadGroup threadGroup = new ThreadGroup("myThread"); // 创建名为myThread的线程组
// 还有一种指定父线程组的方式,这里不展示。
Tips:
main线程所属线程组名为main;
当创建线程时若不指定线程组,则会将父线程(创建该线程的线程)的线程组作为该线程的线程组,见源码:(带有ThreadGroup参数的构造方法最终会调用如下init()方法)/** * Initializes a Thread. * * @param g the Thread group * @param target the object whose run() method gets called * @param name the name of the new Thread * @param stackSize the desired stack size for the new thread, or * zero to indicate that this parameter is to be ignored. * @param acc the AccessControlContext to inherit, or * AccessController.getContext() if null * @param inheritThreadLocals if {@code true}, inherit initial values for * inheritable thread-locals from the constructing thread */ 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(); // 当前线程线程组设置为父线程线程组 } }
3.1.2 stackSize
/*
* The requested stack size for this thread, or 0 if the creator did
* not specify a stack size. It is up to the VM to do whatever it
* likes with this number; some VMs will ignore it.
* 设置当前线程的栈容量,不指定则为0。
* 该数字的设置在一个虚拟机中可能被忽略,而由虚拟机自己决定。
*/
private long stackSize;
一般情况下,不会主动设置该参数
3.2 守护线程
JVM进程中,当存在任意一个非守护线程时,JVM不会停止;仅当进程中都是守护线程时,JVM才会停止。
守护线程的例子:
JVM垃圾回收线程。在main线程仍然运行时,垃圾回收线程始终保持后台运行,为main提供垃圾回收服务;当main线程结束(程序执行结束),此时没有了非守护线程,JVM关闭。
3.3 总结
本节主要了解了Thread的多种构造方法、其中TreadGroup和stackSize的含义以及守护线程的概念。
四、ThreadAPI介绍
4.1 sleep()
/**
* Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
* 立即使当前线程暂停执行指定时间,暂停时间取决于系统定时器和调度器的精准度
* 此线程不会失去monitor的持有权(以后介绍)
*
* @param millis
* the length of time to sleep in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public static native void sleep(long millis) throws InterruptedException;
注:
在实际开发和使用场景中,更多地是使用TimeUnit替代Thread.sleep()
使用TimeUnit可使时间设定值可读、更有意义。
4.2 yield()
/**
* A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
* 提醒调度器当前线程可以让出CPU占用权。
* 调度器也可以忽略。
*
* <p> Yield is a heuristic attempt to improve relative progression
* between threads that would otherwise over-utilise a CPU. Its use
* should be combined with detailed profiling and benchmarking to
* ensure that it actually has the desired effect.
* yield方法尝试解决线程过度占用CPU的问题,以提高执行效率。
* 应该结合详细的配置和标准使用yield,以达到预期效果。
*
* <p> It is rarely appropriate to use this method. It may be useful
* for debugging or testing purposes, where it may help to reproduce
* bugs due to race conditions. It may also be useful when designing
* concurrency control constructs such as the ones in the
* {@link java.util.concurrent.locks} package.
* yield不常用。
* yield可用于调试和测试,找出竞争场景下的bug。
* 设计并发控制组件也可用yield。
*/
public static native void yield();
yield和sleep
注:在JDK1.5之前,yield内部实际使用调用了sleep(0)。
- yield 只是对CPU 调度器的一个提示,如果 CPU调度器没有忽略这个提示,它会导致当前线程让出CPU占用权,发生线程上下文的切换;
- sleep 会使线程短暂 block,会在给定的时间内试放 CPU 资源;
- yield 会使 Running状态的 Thread 进入Runnable 状态(如果 CPU 调度器没有忽略这个提示的话);sleep 几乎百分之百的完成了给定时间的休眠,而 yield 的提示并不能一定保证。