Java的线程支持提供了一些工具方法,通过这些方法可以很好地控制线程的执行。
Join线程
Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并发执行变为串行执行。具体看代码:
public class ThreadTest1 {
/**
* join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
* 程序在 main 线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕
* 所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会
*/
public static void main(String[] args) {
//main方法是jvm进程的默认的主线程
System.out.println("主线程的名字:" + Thread.currentThread().getName());
//在main线程中创建一个新的线程,并且启动
Thread t1 = new MyThread("子线程1");
t1.start();
try {
//调用了 t1 的 join() 方法,他会将“主线程”给挂起来,使主线程堵塞,让子线程 t1 继续执行,
//当 t1 执行完或者超过时间再让主线程执行
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行到这里的时候,一定 t1 线程已经结束了!");
}
}
class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 1; i < 1001; i++) {
System.out.println(this.getName() + ":" + i);
}
}
}
运行结果:
为了对比,我将 t1.join() 注释掉,之后我们再来看看运行的结果:
上面注释也大概说明了join方法的作用:在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
注意,这里调用的join方法是没有传参的,join方法其实也可以接收参数的,具体看下面的简单例子:
public class ThreadTest1 {
/**
* join的意思是使得放弃当前线程的执行,并返回对应的线程,例如下面代码的意思就是:
* 程序在main线程中调用t1线程的join方法,则main线程放弃cpu控制权,并返回t1线程继续执行直到线程t1执行完毕
* 所以结果是t1线程执行完后,才到主线程执行,相当于在main线程中同步t1线程,t1执行完了,main线程才有执行的机会
*/
public static void main(String[] args) {
//main方法是jvm进程的默认的主线程
System.out.println("主线程的名字:" + Thread.currentThread().getName());
//在main线程中创建一个新的线程,并且启动
Thread t1 = new MyThread("子线程1");
t1.start();
try {
//调用了 t1 的 join() 方法,他会将“主线程”给挂起来,使主线程堵塞,让子线程 t1 继续执行,
//当 t1 执行完或者超过时间再让主线程执行
t1.join(10);//参数的意义是main线程等待,阻塞的时间10毫秒,10毫秒过去后,
//main线程和t1线程之间执行顺序由串行执行变为普通的并行执行
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("执行到这里的时候,一定 t1 线程已经结束了!");
}
}
class MyThread extends Thread{
public MyThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 1; i < 1001; i++) {
System.out.println(this.getName() + ":" + i);
}
}
}
运行结果:
所以,join方法中如果传入参数,则表示这样的意思:如果A线程中掉用B线程的join(10),则表示A线程会等待B线程执行10毫秒,10毫秒过后,A、B线程并发执行。需要注意的是,jdk规定,join(0)的意思不是A线程等待B线程0秒,而是A线程等待B线程无限时间,直到B线程执行完毕,即join(0)等价于join()。
join与start调用顺序问题
上面的讨论大概知道了join的作用了,那么,如果 join在start前调用,会出现什么后果呢?先看下面的测试结果
运行结果:
从运行结果很容易看出来,这就相当于没有join一样。
所以得到以下结论:join方法必须在线程start方法调用之后调用才有意义。
这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。
后台线程
后台线程,它是在后台运行的,它的任务是为其他线程提供服务,这种线程被称为“后台线程(Daemon Thread)”,又称为“守护线程”或“精灵线程”。JVM的垃圾回收线程就是典型的后台线程。
后台线程的特征:如果所有的前台线程都死亡了,后台线程也会自动死亡。
其余代码不变,调用Thread对象的setDaemon(true)可将制定线程设置为后台线程。所有的前台线程都死亡时,后台线程也死亡,程序就退出了。
注意:设置为后台线程,注意必须在start前掉用,否则会报错
public class ThreadTest2 {
public static void main(String[] args) {
Thread daemonThread = new DaemonThread("后台子线程");
daemonThread.setDaemon(true);//设置daemonThread为后台线程的关键语句
daemonThread.start();
try {
Thread.sleep(5000);//让 main 线程沉睡5s
} catch (InterruptedException e) {
e.printStackTrace();
}
//5s之后,main线程会回来,继续执行主线程里面的任务代码
for (int i = 1; i<101; i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
new ForefroundThread("前台子线程").start();
}
}
/**
* 这是后台子线程
*/
class DaemonThread extends Thread{
public DaemonThread(String name){
super(name);
}
@Override
public void run() {
for(int i = 1; i < 101; i++) {
try {
Thread.sleep(1000);//让该线程沉睡1s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + ":" + i);
}
}
}
/**
* 这是前台子线程
*/
class ForefroundThread extends Thread{
public ForefroundThread(String name){
super(name);
}
@Override
public void run() {
for(int i = 1; i < 101; i++) {
try {
Thread.sleep(500);//让线程沉睡0.5s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + ":" + i);
}
}
}
运行结果:为了方便说明,我就把结果的分析一起写在运行截图里了
结论:如果所有的前台线程都死亡了,后台线程也会自动死亡。反过来,如果后台线程先执行完,前台线程会不停止。
线程睡眠sleep()方法
1、方法介绍
sleep(long millis) 线程睡眠 millis 毫秒
sleep(long millis, int nanos) 线程睡眠 millis 毫秒 + nanos 纳秒
2、如何调用sleep
因为sleep()是静态方法,所以最好的调用方法就是 Thread.sleep()。
3、在哪里写sleep更合理?
线程的sleep方法应该写在线程的run()方法里,就能让对应的线程睡眠。
代码:
public class Thread1 {
public static void main(String[] args) {
Runner1 r1 = new Runner1();
Thread t = new Thread(r1);
t.start();
for (int i = 0; i < 3; i++) {
System.out.println(“main thread :”+i);
}
}
}
class Runner1 implements Runnable{
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 3; i++) {
System.out.println("Runner1 : " + i);
}
}
}
结果:
main thread :0
main thread :1
main thread :2
----------------- 此处睡眠5秒,5秒后出现以下:
Runner1 : 0
Runner1 : 1
Runner1 : 2
4、到底是让哪个线程睡眠?
结论:sleep方法只能让当前线程睡眠。调用某一个线程类的对象t.sleep(),睡眠的不是t,而是当前线程。
代码验证:为了验证,我们通过继承Thread类创建线程。
在Runner1的run()中不写sleep(),在主线程中写Runner1.sleep(5000),
结果不是Runner1睡眠,还是主线程睡眠,请看下面输出结果。
代码:
public class Thread1 {
public static void main(String[] args) {
Runner1 r = new Runner1();
r.start();
try {
Runner1.sleep(5000); //此处是类名.sleep()
System.out.println("当前运行的线程名称: "+ Runner1.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 3; i++) {
System.out.println(“main thread :”+i);
}
}
}
class Runner1 extends Thread{
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println("Runner1 : " + i);
}
}
}
结果:
Runner1 : 0
Runner1 : 1
Runner1 : 2
--------------------------------- 此处睡眠5秒,5秒后出现以下:
当前运行的线程名称: main
main thread :0
main thread :1
main thread :2
控制线程睡眠的方法还有一种:
TimeUnit.SECONDS.sleep(1);
TimeUnit.MINUTES.sleep(1);
TimeUnit.HOURS.sleep(1);
TimeUnit.DAYS.sleep(1);
作用和sleep一样,但是程序可读行更高,在项目中,推荐睡眠后后面这种写法。
改变线程优先级
每个线程在执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较少的执行机会。
每个线程默认的优先级都与创建它的父线程的优先级相同,在默认情况下,main线程具有普通优先级,由main线程创建的子线程也具有普通优先级。
java 中的线程优先级的范围是1~10,1的优先级最低,10的优先级最高。
通过setPriority()方法,可以改变线程的优先级。
代码:
public class PriorityTest extends Thread
{
// 定义一个有参数的构造器,用于创建线程时指定name
public PriorityTest(String name)
{
super(name);
}
public void run()
{
for (int i = 0 ; i < 50 ; i++ )
{
System.out.println(getName() + “,其优先级是:”
+ getPriority() + “,循环变量的值为:” + i);
}
}
public static void main(String[] args)
{
// 改变主线程的优先级
Thread.currentThread().setPriority(6);
for (int i = 0 ; i < 30 ; i++ )
{
if (i == 10)
{
PriorityTest low = new PriorityTest(“低级”);
low.start();
System.out.println(“创建之初的优先级:”
+ low.getPriority());
// 设置该线程为最低优先级
low.setPriority(Thread.MIN_PRIORITY);
}
if (i == 20)
{
PriorityTest high = new PriorityTest(“高级”);
high.start();
System.out.println(“创建之初的优先级:”
+ high.getPriority());
// 设置该线程为最高优先级
high.setPriority(Thread.MAX_PRIORITY);
}
}
}
}
从运行效果来看,优先级高的线程获得更多的执行机会。
注意:在多线程中线程的执行顺序是依靠哪个线程先获得到CUP的执行权谁就先执行,虽然说可以通过线程的优先权进行设置,但是他只是获取CUP执行权的概率高点,但是也不一定必须先执行
线程让步
Thread中有一个线程让步方法yield(),作用就是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!
代码:
class ThreadA extends Thread{
public ThreadA(String name){
super(name);
}
public synchronized void run(){
for(int i=0; i <10; i++){
System.out.printf("%s [%d]:%d\n", this.getName(), this.getPriority(), i);
// i整除4时,调用yield
if (i%4 == 0)
Thread.yield();
}
}
}
public class YieldTest{
public static void main(String[] args){
ThreadA t1 = new ThreadA(“t1”);
ThreadA t2 = new ThreadA(“t2”);
t1.start();
t2.start();
}
}
说明:“线程t1”在能被4整数的时候,并没有切换到“线程t2”。这表明,yield()虽然可以让线程由“运行状态”进入到“就绪状态”;但是,它不一定会让其它线程获取CPU执行权(即,其它线程进入到“运行状态”),即使这个“其它线程”与当前调用yield()的线程具有相同的优先级。在多CPU并行的环境下,yield方法的功能有时候不明显,可能看不到效果。
来源:CSDN
作者:JokerCaesar6
链接:https://blog.csdn.net/qq_41600383/article/details/104675731