Java多线程与JUC——04控制线程

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 ThreadTest3 {
    public static void main(String[] args) {
        new Thread(new MyThread1(), "子线程").start();
        for (int i = 1; i < 4; i++){
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

class MyThread1 implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(3000); //最佳写的位置是 run 方法里
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 1; i < 4; i++) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

结果:
main:1
main:2
main:3
--------此处睡眠3秒,三秒后运行子线程--------
子线程:1
子线程:2
子线程:3

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

控制线程睡眠的方法还有一种(JUC):

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()方法,可以改变线程的优先级。也可以getPriorty()获取线程的优先级
代码:

public class ThreadTest5 {
    public static void main(String[] args) {
        System.out.println("main线程的默认的优先级别:" + Thread.currentThread().getPriority());
        Thread t1 = new PriorityThread("低级别");
        t1.setPriority(Thread.MIN_PRIORITY);
        t1.start();

        Thread t2 = new PriorityThread("高级别");
        t2.setPriority(Thread.MAX_PRIORITY);
        t2.start();
    }
}

class PriorityThread extends Thread {
    public PriorityThread(String name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 1; i < 51; i++) {
            System.out.println(this.getName() + ":" + this.getPriority() + ":" + i);
        }
    }
}

多次的运行结果都差不多:
在这里插入图片描述在这里插入图片描述
从运行效果来看,优先级高的线程获得更多的执行机会。
注意!:在多线程中线程的执行顺序是依靠哪个线程先获得到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方法的功能有时候不明显,可能看不到效果。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值