前言
本文接着学习线程之间的共享与协作,线程基础以及线程之间的共享与协作<一> 主要介绍了线程的基础概念和线程的启动与终止,本文接着讲述线程的一些常用方法与 synchronized 加锁的方法。
关于线程的一些基本方法,下面这张图可以很清晰地表述各个方法得作用与联系。
线程的基本方法
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.
* 给调度程序的一个提示,即当前线程愿意产生其当前使用的处理器。
* 调度程序可以忽略此提示。
*
* <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。
* 它的使用应该与详细的分析和基准测试相结合,以确保它确实具有预期的效果。
*
* <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.
* 使用这种方法很少合适。它可能对调试或测试有用,因为它可能有助于重现由于竞争条件而产生的bug。
* 在设计并发控制结构(如{java.util.concurrent.locks}包
*/
public static native void yield();
yield 方法首先是一个 native 方法,是调用操作系统的方法;yield 会让当前线程让出执行权,然后CPU随机分配时间片给处于就绪态的线程,要注意的是,当前线程让出执行权之后也会转到就绪态,所以说当前线程也有几率被CPU选中。
线程执行 yield 方法让出CPU执行权,并不会让出当前线程所占用的其他资源,比如内存空间、磁盘I/O等。
有人可能会问执行 yield 方法会让当前线程释放锁吗?
答案是不会的。第一点,我们可以想一下,在一个进程中,并不是所有线程都需要加锁的;第二点,在执行 yield 方法的时候,线程并不一定会持有锁。综合以上两点,所有线程执行 yield 方法并不会释放锁。
yield 方法在实际开发中很少使用,主要是对调试和测试有用,在某些情况下它可能由于竞争条件而有助于重现错误。当设计并发控制结构时,例如link java.util.concurrent.locks包中的并发控制结构,它也可能很有用。
举个实际的例子,来简单演示一下 yield 方法的作用。
老王laowang在电影院排队买票,突然看到女神goddess,于是主动献殷勤,让女神插队,后来女神男盆友goddessBoyFriend来了,女神主动让男盆友插队。(有点儿心疼老王。。。)
简单分析:
老王主动让女神插队,相当于让出执行权给女神,老王执行 yield 方法;女神让男盆友插队,女神执行 yield 方法。
代码如下:
public class UseYield {
private static class MarkThread extends Thread {
private Thread thread;
public MarkThread(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
System.out.println("laowang开始排队买票。。。");
if (thread != null) {
// 当前线程主动让出执行权,由运行态转到可运行状态
// 在这里就是 laowang 主动让 goddess 插队
yield();
}
// 休眠4秒
SleepTools.second(4);
System.out.println(Thread.currentThread().getName() + " laowang买票结束。。。");
}
}
private static class GoddessThread extends Thread {
private Thread thread;
public GoddessThread(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
System.out.println("goddess开始排队买票。。。");
if (thread != null) {
// 当前线程主动让出执行权,由运行态转到可运行状态
// 在这里就是 goddess 主动让 goddessBoyFriend 插队
yield();
}
// 休眠两秒
SleepTools.second(2);
System.out.println(Thread.currentThread().getName() + " goddess买票结束。。。");
}
}
private static class GoddessBoyFriendThread extends Thread {
@Override
public void run() {
System.out.println("goddessBoyFriend开始排队买票。。。");
// 休眠两秒
SleepTools.second(2);
System.out.println(Thread.currentThread().getName() + " goddessBoyFriend买票结束。。。");
}
}
public static void main(String[] args) {
GoddessBoyFriendThread goddessBoyFriend = new GoddessBoyFriendThread();
GoddessThread goddess = new GoddessThread(goddessBoyFriend);
MarkThread mark = new MarkThread(goddess);
mark.start();
goddess.start();
goddessBoyFriend.start();
}
}
执行结果:
laowang开始排队买票。。。
goddessBoyFriend开始排队买票。。。
goddess开始排队买票。。。
Thread-0 goddessBoyFriend买票结束。。。
Thread-1 goddess买票结束。。。
Thread-2 laowang买票结束。。。
join 方法
同样的,读源码,分析方法。源码如下:
/**
* Waits for this thread to die.
* 等待该线程死亡。
*
* <p> An invocation of this method behaves in exactly the same
* way as the invocation
* 此方法的调用与调用的行为方式完全相同
*
* <blockquote>
* {@linkplain #join(long) join}{@code (0)}
* </blockquote>
*
* @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.
* 当前线程被中断,中断标志位会变成false,并抛出异常
*/
public final void join() throws InterruptedException {
join(0);
}
执行 join 方法可以把指定的线程加入到当前线程,将两个交替执行的线程合并为顺序执行。
也就是说,例如现在有 thread1 和 thread2,thread1 正在执行,thread2 调用 join() 方法,thread1就被挂起了,那么 thread1 就要让出执行权,thread2 获取执行权,而且 thread1 要等待 thread2 执行结束(线程死亡)之后,才能拿到执行权。
同样是老王去电影院排队买票的案例,用 join 方法的实现方法如下:
public class UseJoin {
static class Goddess implements Runnable {
private Thread thread;
public Goddess(Thread thread) {
this.thread = thread;
}
@Override
public void run() {
System.out.println("Goddess开始排队买票.....");
try {
if (thread != null) {
thread.join();
}
} catch (InterruptedException e) {
}
SleepTools.second(2);//休眠2秒
System.out.println(Thread.currentThread().getName()
+ " Goddess排队买票...");
}
}
static class GoddessBoyfriend implements Runnable {
@Override
public void run() {
SleepTools.second(2);//休眠2秒
System.out.println("GoddessBoyfriend开始排队买票.....");
System.out.println(Thread.currentThread().getName()
+ " GoddessBoyfriend排队买票...");
}
}
public static void main(String[] args) throws Exception {
// 这里用主线程表示老王排队买票
Thread laowang = Thread.currentThread();
GoddessBoyfriend goddessBoyfriend = new GoddessBoyfriend();
Thread gbf = new Thread(goddessBoyfriend);
Goddess goddess = new Goddess(gbf);
//Goddess goddess = new Goddess();
Thread g = new Thread(goddess);
g.start();
gbf.start();
System.out.println("laowang开始排队买票.....");
g.join();
//让主线程休眠2秒
SleepTools.second(2);
System.out.println(Thread.currentThread().getName() + " laowang买票完成.");
}
}
执行完成:
laowang开始排队买票.....
Goddess开始排队买票.....
GoddessBoyfriend开始排队买票.....
Thread-0 GoddessBoyfriend排队买票...
Thread-1 Goddess排队买票...
main laowang买票完成.
线程的优先级
线程的setPriority()方法可以给线程设置优先级,也就是更新线程的优先级,因为线程的默认优先级为 5,线程的优先级是从1-10,优先级越高,CPU给线程分配的时间片可能就越多,优先执行的概率可能就越高。
但是能不能真正的执行,还要看操作系统,假设给线程设置了10的优先级,可能操作系统本身最高的优先级只有5,那也是不起作用的。如果你设置了超出这个范围(1-10)的优先级,就会报错,throw new IllegalArgumentException()。
基本规则:
侧重于休眠、I/O操作的线程优先级可以设置高一点,侧重于计算的线程优先级可以设置低一点
总结:
想要通过设置优先级来指定哪个线程先执行,哪个后执行,这样是不起作用的,能不能先执行还是要看操作系统的。
守护线程
线程的setDaemon()方法,可以让我们的线程变成守护线程。
什么是守护线程?
守护线程是一种支持性线程,因为主要被用作程序中后台调度以及一些支持性工作。包括,内存回收、gc等。
平常我们通过 new Thread() 创建的线程都被称为用户线程(非守护线程)。但是像JDK启动的线程,我们通过参数配置的线程都被称为守护线程。比如:useThread.start(); Thread.sleep(5);
当一个进程的非守护线程(用户线程)都执行结束了,这个进程也就跟着停止了,所有的守护线程也就跟着停止了。想要设置守护的线程的话,只需要 setDaemon(true) 就可以。
守护线程在开发中一般用不到,比如垃圾回收线程用的就是守护线程,还有在netty框架中,一般会创建堆外内存,这个时候就可以用守护线程来管理这些堆外内存。
实例:
当前代码直接执行的话,非守护线程有两个,一个main主线程,一个useThread线程。执行结果是main执行结束了之后,useThread线程一直在运行;代码如下:
public class DaemonThread {
private static class UseThread extends Thread{
@Override
public void run() {
try {
while (!isInterrupted()) {
System.out.println(Thread.currentThread().getName()
+ " I am extends Thread.");
}
System.out.println(Thread.currentThread().getName()
+ " interrupt flag is " + isInterrupted());
} finally {
//守护线程中finally不一定起作用
System.out.println(" .............finally");
}
}
}
public static void main(String[] args)
throws InterruptedException, ExecutionException {
UseThread useThread = new UseThread();
// useThread.setDaemon(true);
useThread.start();
Thread.sleep(5);
// useThread.interrupt();
}
}
执行结果:
可以看到 useThread 用户线程(非守护线程)一直在执行,不会结束。
但是我们把useThread线程设置成守护线程,再去执行,主线程执行结束之后,子线程也执行结束了。代码就是把上一份代码的注释打开,重新执行
useThread.setDaemon(true);
执行结果:
看输出结果,在 main 线程执行完成之后,useThread 守护线程也跟着结束了。
需要注意的是:
daemon守护线程被用作完成支持性工作,在JVM退出的时候,守护线程中的finally块并不一定会被执行。这是取决于CPU是否分配时间片给守护线程,分配了就执行,没分配就不执行。所以在构建守护线程的时候,不能依赖finally中的内容来确保执行关闭或者释放资源的逻辑。