并发编程Thread的常用API有哪些?
弁言在JDK17(或以上版本)中,Thread类提供了一组常用的API,用于管理线程的创建、启动、停息、恢复和销毁等操作。本文从api、源码、编程示例等方面详细说明Thread常用函数的使用和注意事项。
https://img2024.cnblogs.com/blog/1684206/202403/1684206-20240308092448205-1219412479.png
线程 sleep
[*]使当前正在执行的线程停息(挂起)指定的毫秒数。但受系统计时器和调度程序的精度和准确性限制。
[*]线程不会失去任何monitor(监视器)的全部权。
[*]每个线程的休眠互不影响,Thread.sleep 只会导致当火线程进入指定时间的休眠。
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0 && millis < Long.MAX_VALUE) {
millis++;
}
sleep(millis);
}通过测试发现 Thread.sleep 之间互不影响。代码如下:
/**
* 每个线程的休眠互不影响,`Thread.sleep` 只会导致当前线程进入指定时间的休眠。
*/
public class ThreadSleepTest {
public static void main(String[] args) throws Exception{
Thread thread1 = new Thread(()->{
int i = 0;
while(i<10){
System.out.println("thread demo start "+i);
i++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread1.start();
System.out.println("thread main start ");
Thread.sleep(2000);
System.out.println("thread main end ");
}
}线程 yield
Thread.yield()是一个提示,用于告诉调度程序当火线程愿意放弃使用处置惩罚器。调度程序可以选择忽略这个提示。Yield是一种试图改善线程之间相对进程的开导式方法,否则它们会太过利用CPU。它的使用应该与详细的分析和基准测试联合起来,以确保它确实产生了预期的效果。
这种方法适用场景很少。它有助于调试或测试,以资助重现由于竞态条件而引起的错误。在筹划并发控制布局时,例如java.util.concurrent.locks包中的布局,它也大概有效。
调用Thread.yield()函数会将当火线程从RUNNING状态切换到RUNNABLE状态。
thread main start
thread demo start 0
thread demo start 1
thread main end
thread demo start 2
thread demo start 3测试代码如下,在cpu资源不紧张的环境下输出仍旧是乱序的。
package engineer.concurrent.battle.abasic;
import java.util.concurrent.TimeUnit;
/**
* TimeUnit工具类替代Thread.sleep方法。
* @author r0ad
* @since 1.0
*/
public class ThreadSleepTimeUnitTest {
public static void main(String[] args) throws Exception{
System.out.println("thread main start ");
TimeUnit.SECONDS.sleep(1);
TimeUnit.MILLISECONDS.sleep(500);
TimeUnit.MINUTES.sleep(1);
System.out.println("thread main end ");
}
}
/**
* java.util.concurrent.TimeUnit#sleep 源码,底层实现也是Thread.sleep。
* Performs a {@link Thread#sleep(long, int) Thread.sleep} using
* this time unit.
* This is a convenience method that converts time arguments into the
* form required by the {@code Thread.sleep} method.
*
* @param timeout the minimum time to sleep. If less than
* or equal to zero, do not sleep at all.
* @throws InterruptedException if interrupted while sleeping
*/
public void sleep(long timeout) throws InterruptedException {
if (timeout > 0) {
long ms = toMillis(timeout);
int ns = excessNanos(timeout, ms);
Thread.sleep(ms, ns);
}
}输出结果:
public static native void yield();Thread.yield() 和 Thread.sleep() 方法之间的接洽和差异如下:
接洽:
[*]Thread.yield() 和 Thread.sleep() 都会使当火线程停息执行,让出CPU资源给其他线程。
[*]Thread.yield() 和 Thread.sleep() 都不会释放当火线程所占用的锁。
差异:
[*]Thread.yield() 方法只是停息当火线程的执行,让出CPU资源给其他线程,但不会进入壅闭状态。大概导致CPU进行上下文切换。
[*]Thread.sleep() 方法会使当火线程停息指定的时间,并进入壅闭状态,直到休眠时间竣事或者被其他线程打断。
[*]Thread.sleep()具有较高的可靠性,可以确保至少停息指定的时间。Thread.yield()则不能保证停息。
设置线程的优先级
[*]java.lang.Thread#setPriority 修改线程的优先级
[*]java.lang.Thread#getPriority 获取线程的优先级
java.lang.Thread#setPriority 修改线程的优先级实现过程如下:
[*]调用此线程的checkAccess方法,不带任何参数。这大概会导致抛出一个SecurityException非常。
[*]线程的优先级被设置为指定的newPriority和线程所属线程组答应的最大优先级中较小的值。
package engineer.concurrent.battle.abasic;
import java.util.stream.IntStream;
/**
* ThreadYield测试
* @author r0ad
* @since 1.0
*/
public class ThreadYieldTest {
public static void main(String[] args) throws Exception{
System.out.println("thread main start ");
IntStream.range(0, 2).mapToObj(ThreadYieldTest::create).forEach(Thread::start);
System.out.println("thread main end ");
}
private static Thread create(int i) {
return new Thread(() -> {
if(i == 0 ){
Thread.yield();
}
System.out.println("thread " + i + " start ");
});
}
}进程有进程的优先级,线程同样也有优先级,理论上是优先级比较高的线程会获取优先被 CPU 调度的机会,但是事实上设置线程的优先级同样也是一个 hint 操作,具体如下。
[*]对于 root 用户,它会 hint 操作系统你想要设置的优先级别,否则它会被忽略。
[*]如果 CPU 比较忙,设置优先级大概会获得更多的 CPU 时间片,但是闲时优先级的高低几乎不会有任何作用。
所以不要使用线程的优先级进行某些特定业务的绑定,业务执行的次序应该还是要使用同步执行方法来保证。
测试例子如下,线程之间会交替输出:
thread main start
thread main end
thread 0 start
thread 1 start 获取线程ID
返回此线程的标识符。线程ID是一个正的long数字,在创建此线程时生成。线程ID是唯一的,并在其生命周期内保持不变。当一个线程终止时,该线程ID大概会被重新使用。
/**
* `java.lang.Thread#setPriority` 修改线程的优先级源码
*/
public final void setPriority(int newPriority) {
ThreadGroup g;
// 调用此线程的`checkAccess`方法,不带任何参数。这可能会导致抛出一个`SecurityException`异常。
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
// 线程的优先级被设置为指定的`newPriority`和线程所属线程组允许的最大优先级中较小的值。
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
/**
* `java.lang.Thread#getPriority` 获取线程的优先级
*/
public final int getPriority() {
// 返回Thread的priority属性
return priority;
}
/* 原生优先级设置方法 */
private native void setPriority0(int newPriority);获取当火线程
java.lang.Thread#currentThread 方法被大多数框架使用,像是SpringMVC、MyBatis这些。调用该函数会返回当前正在执行的线程对象。
package engineer.concurrent.battle.abasic;
/**
* ThreadPriorityTest测试
* @author r0ad
* @since 1.0
*/
public class ThreadPriorityTest {
public static void main(String[] args) throws Exception{
Thread t1 = ThreadPriorityTest.create("t1");
t1.setPriority(1);
Thread t2 = ThreadPriorityTest.create("t2");
t2.setPriority(10);
t1.start();
t2.start();
}
private static Thread create(String name) {
return new Thread(() -> {
while (true) {
System.out.println("thread " + name );
}
});
}
}测试代码如下:
public long getId() {
return tid;
}设置线程上下文类加载器
[*]java.lang.Thread#getContextClassLoader 返回该线程的上下文ClassLoader。上下文ClassLoader由创建线程的对象提供,用于在此线程中运行的代码在加载类和资源时使用。如果未设置(通过setContextClassLoader()方法),则默以为父线程的ClassLoader上下文。原始线程的上下文ClassLoader通常设置为用于加载应用程序的类加载器。
[*]java.lang.Thread#setContextClassLoader 设置此线程的上下文ClassLoader。上下文ClassLoader可以在创建线程时设置,并答应线程的创建者通过getContextClassLoader方法为在线程中运行的代码提供适当的类加载器,用于加载类和资源。如果存在安全管理器,则会使用其checkPermission方法,传入RuntimePermission的setContextClassLoader权限,以查抄是否答应设置上下文ClassLoader。
@IntrinsicCandidate
public static native Thread currentThread();线程 interrupt
[*]java.lang.Thread#interrupt
停止此线程。除非当火线程自己停止自己,这是始终答应的,否则会调用该线程的checkAccess方法,大概会引发SecurityException非常。
如果此线程在Object类的wait()、wait(long)、wait(long, int)方法的调用中被壅闭,或者在此类的join()、join(long)、join(long, int)、sleep(long)或sleep(long, int)方法的调用中被壅闭,则它的停止状态将被清除,并且它将收到一个InterruptedException非常。
如果此线程在对InterruptibleChannel的I/O操作中被壅闭,则通道将被关闭,线程的停止状态将被设置,并且线程将收到一个ClosedByInterruptException非常。
如果此线程在Selector中被壅闭,则线程的停止状态将被设置,并且它将立即从选择操作中返回,大概带有非零值,就像调用了选择器的wakeup方法一样。
如果以上条件都不满意,则将设置此线程的停止状态。
停止一个未启动的线程大概不会产生任何效果。在JDK参考实现中,停止一个未启动的线程仍旧记录了停止请求的发出,并通过interrupted和isInterrupted()方法陈诉它。
[*]java.lang.Thread#interrupted
测试当火线程是否已被停止。此方法将清除线程的"停止状态"。换句话说,如果连续两次调用此方法,第二次调用将返回false(除非在第一次调用清除了线程的停止状态之后,而第二次调用在查抄之前再次停止了当火线程)。
[*]java.lang.Thread#isInterrupted
测试此线程是否已被停止。此方法不会影响线程的"停止状态"。
package engineer.concurrent.battle.abasic;
/**
* ThreadCurrentTest测试
* @author r0ad
* @since 1.0
*/
public class ThreadCurrentTest {
public static void main(String[] args) throws Exception{
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println(this == Thread.currentThread());
}
};
t1.start();
System.out.println(Thread.currentThread().getName());
}
}测试代码如下:
@CallerSensitive
public ClassLoader getContextClassLoader() {
if (contextClassLoader == null)
return null;
@SuppressWarnings("removal")
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
ClassLoader.checkClassLoaderPermission(contextClassLoader,
Reflection.getCallerClass());
}
return contextClassLoader;
}
public void setContextClassLoader(ClassLoader cl) {
@SuppressWarnings("removal")
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("setContextClassLoader"));
}
contextClassLoader = cl;
}当调用join函数之后主线程和子线程的状态切换如下:
[*]当调用join()方法时,主线程会进入等待状态,直到子线程执行完毕后才会继续执行。此时主线程的状态为WAITING。
[*]如果调用带参数的join()方法,主线程会在等待一段时间后继续执行,而不必一直壅闭。在这种环境下,主线程的状态为TIMED_WAITING。
[*]如果子线程已经执行完毕,但是主线程还没有调用join()方法,则子线程的状态为TERMINATED,而主线程的状态为RUNNABLE。
[*]如果主线程调用join()方法等待子线程完成执行,而子线程抛出了非常,则主线程会收到非常信息并抛出InterruptedException非常。
测试代码如下:
package engineer.concurrent.battle.abasic;import java.util.List;import java.util.concurrent.TimeUnit;import java.util.stream.IntStream;/** * ThreadJoinTest * @author r0ad * @since 1.0 */public class ThreadJoinTest { public static void main(String[] args) throws InterruptedException{ List threadList = IntStream.range(1, 10).mapToObj(ThreadJoinTest::create).toList(); threadList.forEach(Thread::start); for(Thread thread : threadList){ thread.join(); } IntStream.range(1, 10).forEach((i)-> { System.out.println("thread " + Thread.currentThread().getName() + " # "+ i ); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { throw new RuntimeException(e); } }); } /** * 线程创建函数 * @param index * @return */ private static Thread create(int index) { return new Thread(() -> { int i = 0; while (i++
页:
[1]