第三章 Java线程基础知识全面详解

JUC并发编程系列文章

http://t.csdn.cn/UgzQi



前言

一、创建和运行线程

1、使用匿名内部类的方式创建线程

在这里插入图片描述

使用匿名内部类的方式创建线程: 创建线程后要重写 run 方法,但是这时任务调度器并不能调度到这个线程,需要调用该线程的 start( ) 方法才行。

在这里插入图片描述

2、使用Runnable配合Thread创建线程

在这里插入图片描述

Runnable还是一个函数式接口,标有 @FunctionalInterface 注解

在这里插入图片描述
在这里插入图片描述

使用Lambda表达式简化Runnable接口创建

在这里插入图片描述
在这里插入图片描述

package tanchishell.JUC;

public class testThread {
    public static void main(String[] args) {


        Runnable runnable = test();
        Thread thread = new Thread(runnable,"nihao");
        thread.start();

        //main方法的线程信息
        long id = Thread.currentThread().getId();
        String name = Thread.currentThread().getName();
        System.out.println(id+"---"+name);


        //thread的线程信息
        long id1 = thread.getId();
        String name1 = thread.getName();
        System.out.println(id1+"---"+name1);

    }


    public static Runnable test(){
        Runnable runnable = () -> System.out.println("线程被调用了");
        return runnable;
    }
}

走进Runnable配合Thread创建线程底层源码

在这里插入图片描述
进入有参构造器
在这里插入图片描述
有参构造获取到Runnable接口和我们传入的线程名字,并调用了 init 初始化方法
在这里插入图片描述
init 方法里面又调用了 init 方法,但是这两个 init 方法的形参列表是不同的。

在这里插入图片描述
会先判断我们传入的线程名字是否为空,为空抛出异常。我们接着找 传入的 Runnabl接口。

在这里插入图片描述
把我们传入的 Runnable接口给了当前对象的成员变量。
在这里插入图片描述
成员变量是一个 Runnable接口。target现在是非空的了。
在这里插入图片描述

在这里插入图片描述

找到run方法,先判断我们是否传入 Runnable 接口,这里条件肯定成立,然后调用 run 方法执行我们重写后的 run 方法。

在这里插入图片描述

3、使用FutureTask 配合Thread创建线程

FutureTask可以获取到线程任务的执行结果,实现了RunnableFuture接口,而RunnableFuture接口又继承了 Runnable接口和Futrue接口,Future接口里面有get( )方法可以获取到线程任务的执行结果,Runnable接口只有一个 run 方法而且没有返回值,所以Runnable接口不能让两个线程之间进行通信,这时只能借助 Future接口来实现线程之间的通信,将某一个线程的结果返回,供其他线程使用。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Future接口可以接收Callable接口类型的参数,Callable接口也是一个函数式接口,和Runnable不同的是它的 call()方法有返回值,并且可以抛出异常。

在这里插入图片描述

代码示例

package tanchishell.JUC;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class testThread02 {
    public static void main(String[] args) throws  Exception{
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                Thread.sleep(10000);
                return 100;
            }
        });

        //启动线程  futureTask
        Thread thread = new Thread(futureTask);
        thread.start();


        //执行到get时会阻塞式等待结果的返回
        Integer integer = futureTask.get();
        System.out.println(integer);

        Thread thread2 = new Thread("nihao"){
            public void run(){
                int i = integer + 1;
                System.out.println("启动第二个线程打印futureTask的结果是"+integer+"加1等于"+i);
            }
        };

        //启动第二个线程 thread2
        thread2.start();

        //主线程打印返回结果
        System.out.println("主线程打印futureTask返回结果是"+integer);
    }
}

二、线程的运行原理

观察多个线程同时运行

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

三、查看进程线程的方法

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

四、线程的运行原理

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

1、多线程的运行原理

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

虽然是两个线程但只是线程的执行顺序不同,但是大致的流程还是和单线程一致

2、线程上下文的切换

在这里插入图片描述
在这里插入图片描述

五、线程的常用方法

在这里插入图片描述
在这里插入图片描述

六、start( ) VS run( )

start( )方法才会正在的调用除主线外的一个线程,如果不通过 start( ) 方法调用线程,直接使用 run 方法,是无法达到多线程异步执行来提高效率的,使用 run 方法会在 mian 方法中进行方法的调用。

在这里插入图片描述

七、sleep与yield

yield方法被调用会让当前线程从 运行状态 Running,进入Runnable就绪状态,也就是让出抢到的cpu时间片,让其他线程去执行业务逻辑。但是要注意当让出cpu的时间片后如果没有其他线程去抢占cpu的时间片,任务调度器还是会把当前线程再次调度起来,也就造成了想让出cpu资源却让不出去的情况。

sleep的yield最大的区别就是,yield方法被调用后线程仍属于就绪状态 Runnable,还是会去再次争夺cpu资源,而sleep就不同了,它会让当前线程进入阻塞状态,只有睡眠的时间到了才会让线程重新进入 Runnable就绪状态再去抢占cpu时间片。sleep会有一段休眠时间,而yield没有任何的参数。

在这里插入图片描述

package tanchishell.JUC;

public class testThread03 {
    public static void main(String[] args)throws Exception {
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        thread1.start();
 
        //打印 thread1 线程状态,这时 thread1还没有 sleep
        System.out.println(thread1.getState());  //RUNNABLE
        //等待500毫秒让 thread1进入睡眠
        Thread.sleep(500);
        //再次打印 thread1线程的状态
        System.out.println(thread1.getState());  //TIMED_WAITING
    }
}

打断线程的睡眠,会抛出InterruptedException中断异常也不会执行sleep后的业务

package tanchishell.JUC;

public class testThread03 {
    public static void main(String[] args)throws Exception {
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                try {
                    //睡眠 10秒之后输出你好
                    Thread.sleep(10000);
                    System.out.println("你好");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        thread1.start();

        //打断  thread1 的睡眠,会抛出InterruptedException中断异常也不会输出  你好
        thread1.interrupt();


        //打印 thread1 线程状态,这时 thread1还没有 sleep
        System.out.println(thread1.getState());  //RUNNABLE
        //等待500毫秒让 thread1进入睡眠
        Thread.sleep(500);
        //再次打印 thread1线程的状态
        System.out.println(thread1.getState());  //TIMED_WAITING
    }
}

线程的优先级

在这里插入图片描述

在这里插入图片描述

package tanchishell.JUC;

/**
 * 使用 yield 让出cpu资源和 使用线程优先级获取 cpu资源的v对比
 */
public class testThread04 {
    public static void main(String[] args) {
        Runnable runnable1 = new Runnable() {
            @Override
            public void run() {
                int count = 0;
                for (int i = 1; i<100;i++){

                    System.out.println("线程1="+count);
                    count++;
                }
            }
        };

        Runnable runnable2 = new Runnable() {
            @Override
            public void run() {

                int count = 0;
                for (int i = 1; i<100;i++){
                    //让线程 2执行时让出 cpu资源
//                    Thread.yield();
                    System.out.println("线程2="+count);
                    count++;
                }
            }
        };

        Thread thread1 = new Thread(runnable1);
        Thread thread2 = new Thread(runnable2);

        //设置线程的优先级
//        thread1.setPriority(Thread.MIN_PRIORITY);
//        thread1.setPriority(Thread.MAX_PRIORITY);

        thread1.start();
        thread2.start();

        /**得出结论:
         *    当线程1的优先级大于线程2,这时线程2调用yield方法让出,线程1会优先执行
         *    当线程1的优先级小于线程2,这时线程2调用yield方法让出,线程2会优先执行
         *
         *    当线程1的优先级等于线程2,这时线程2调用yield方法让出,线程1会优先执行
         *    当线程1的优先级等于线程2,这时线程2没有调用yield方法,两个线程会正常交替执行
         */

    }
}

无论是yield还是线程的优先级,它们都不能决定线程的调度,只是给任务调度器一个提示,让任务调度器来根据这个提示进行各个线程的上下文切换。当cpu空闲时这两个家伙的作用都会失效。

案例:防止CPU占用100%

当我们的一个线程卡在那里一直做 死循环会让cpu的占有率极高,这时可以让这个线程睡眠一会,释放cpu资源给其他线程去使用。

在这里插入图片描述

八、join( )方法详解

1、为什么使用 join( )

join方法:当我们需要等带另一个线程的执行结果来进行业务逻辑的处理,可以让另外一个线程执行 join 方法,那么当前线程就会等待被调用的线程执行结束后再继续运行。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2、join( ) 应用的同步案例

如果调用方需要等待被调用方的结果返回,就是同步。

如果调用方不需要等待被调用方的结果返回,就是异步

在这里插入图片描述

让调用方等待两个被调用方执行完后,拿到结果后再执行

import lombok.extern.slf4j.Slf4j;

/**
 * 让调用方等待两个被调用方执行完后,拿到结果后再执行
 */
@Slf4j(topic = "c.testThread01")
public class testThread01 {
    static  int r = 0;
    static  int r1 = 0;
    static  int r2 = 0;
    public static void main(String[] args) throws Exception{
        log.debug("main方法执行了");
        test2();
        log.debug("main方法结束了");

    }

    public static void test2() throws Exception{
        Runnable runnable1 = ()->{
            log.debug("runnable1开始");
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            r1 = 10;
            log.debug("runnable1结束");
        };

        Runnable runnable2 = ()->{
            log.debug("runnable2开始");
            try {
                Thread.sleep(2000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            r2 = 20;
            log.debug("runnable2结束");
        };

        Thread thread1 = new Thread(runnable1);

        Thread thread2 = new Thread(runnable2);

        thread1.start();
        thread2.start();

        thread1.join();
        thread2.join();

        log.debug("r1的结果是="+r1);
        log.debug("r2的结果是="+r2);
    }
}

在这里插入图片描述

有时效的 join( ) 方法

在这里插入图片描述

九、interrupt 方法详解

interrupt 可以打断处于阻塞状态的线程,也可以用来打断正在运行的线程,join方法的底层还是一个wait方法。每个线程被打断后都会有一个标记,是一个布尔值,但是当 sleep join wait 被打断时,会将这个标记清空为 false,而当线程正常运行时被打断了这个标记就会为 true。

在这里插入图片描述
在这里插入图片描述

正在运行的线程调用 thread.interrupt(); 并不会真正被打断,
只是打了一个 标记 在线程上面,如果被标记的线程需要进行停顿,可以通过这个标记进行判断,
如果该线程的打断标记为 true 就表示需要停下来,先放下手中的工作,而去执行另外的业务逻辑。
总之停与不停有线程自己决定

import lombok.extern.slf4j.Slf4j;

/**
 * 正在运行的线程调用 thread.interrupt(); 并不会真正被打断
 * 只是打了一个 标记 在线程上面,如果被标记的线程需要进行停顿,可以通过这个标记进行判断
 * 如果该线程的打断标记为 true 就表示需要停下来,先放下手中的工作,而去执行另外的业务逻辑
 */
@Slf4j(topic = "c.testThread02")
public class testThread02{
    public static void main(String[] args) throws Exception {
            test();
    }

    //
    public static void test() throws Exception{
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println("nihao");
                        log.debug("线程正在运行");
                    //获取打断标记
                    boolean interrupted = Thread.currentThread().isInterrupted();
                    if(interrupted){
                            log.debug("收到打断标记,线程停止");
                        return;
                    }
                }
            }
    };

        Thread thread = new Thread(runnable);
        thread.start();

        //打断当前正在运行的线程
            Thread.sleep(100);
        log.debug("打断当前正在运行的线程");
        thread.interrupt();

    }

}

1、设计模式:两阶段终止

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

两阶段终止设计模式的代码实现

import lombok.extern.slf4j.Slf4j;

/**
 * 两阶段终止设计模式的代码实现
 */
@Slf4j(topic = "c.testThread03")
public class testThread03 {
    public static void main(String[] args) throws Exception{

        TwoStageTermination tst = new TwoStageTermination();
        tst.start();

        Thread.sleep(5000);

        tst.end();
    }
}

@Slf4j(topic = "c.TwoStageTermination")
class TwoStageTermination{
    private Thread monitor;

    public void start(){
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                while (true){
                    //判断是否被打断
                    boolean interrupted = monitor.currentThread().isInterrupted();
                    if (interrupted){
                        log.debug("线程被打断...料理后事");
                        break;
                    }

                    try {
                        Thread.sleep(1000);
                        log.debug("没有异常执行监控记录");
                    } catch (InterruptedException e) {
                        //睡眠时会将标记清空,再次标记
                        monitor.interrupt();
                        e.printStackTrace();

                    }

                }
            }
        };

        monitor = new Thread(runnable,"monitor");
        monitor.start();
    }

    public void end(){
        //打断监控
        log.debug("打断监控");
        monitor.interrupt();
    }
}

2、打断 park 线程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

十、不推荐使用的方法

在这里插入图片描述

十一、守护线程

在这里插入图片描述

在这里插入图片描述

十二、线程的五种状态(操作系统层面)

在这里插入图片描述

在这里插入图片描述

十三、线程的六种状态(JavaAPI层面)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

十四、本章总结

在这里插入图片描述

附:华罗庚《统筹方法》

统筹方法,是一种安排工作进程的数学方法。它的实用范围极广泛,在企业管理和基本建设中,以及关系复 杂的科研项目的组织与管理中,都可以应用。
怎样应用呢?主要是把工序安排好。
比如,想泡壶茶喝。当时的情况是:开水没有;水壶要洗,茶壶、茶杯要洗;火已生了,茶叶也有了。怎么办?
● 办法甲:洗好水壶,灌上凉水,放在火上;在等待水开的时间里,洗茶壶、洗茶杯、拿茶叶;等水开
了,泡茶喝。
● 办法乙:先做好一些准备工作,洗水壶,洗茶壶茶杯,拿茶叶;一切就绪,灌水烧水;坐待水开了,泡茶喝。
● 办法丙:洗净水壶,灌上凉水,放在火上,坐待水开;水开了之后,急急忙忙找茶叶,洗茶壶茶杯,泡茶喝。
哪一种办法省时间?我们能一眼看出,第一种办法好,后两种办法都窝了工。
这是小事,但这是引子,可以引出生产管理等方面有用的方法来。
水壶不洗,不能烧开水,因而洗水壶是烧开水的前提。没开水、没茶叶、不洗茶壶茶杯,就不能泡茶,因而这些又是泡茶的前提。它们的相互关系,可以用下边的箭头图来表示:
在这里插入图片描述
从这个图上可以一眼看出,办法甲总共要16分钟(而办法乙、丙需要20分钟)。如果要缩短工时、提高工作效率,应当主要抓烧开水这个环节,而不是抓拿茶叶等环节。同时,洗茶壶茶杯、拿茶叶总共不过4分钟,大可利用“等水开”的时间来做。
是的,这好像是废话,卑之无甚高论。有如走路要用两条腿走,吃饭要一口一口吃,这些道理谁都懂得。但稍有变化,临事而迷的情况,常常是存在的。在近代工业的错综复杂的工艺过程中,往往就不是像泡茶喝这么简单了。任务多了,几百几千,甚至有好几万个任务。关系多了,错综复杂,千头万绪,往往出现“万事俱备,只欠东风”的情况。由于一两个零件没完成,耽误了一台复杂机器的出厂时间。或往往因为抓的不是关键,连夜三班,急急忙忙,完成这一环节之后,还得等待旁的环节才能装配。
洗茶壶,洗茶杯,拿茶叶,或先或后,关系不大,而且同是一个人的活儿,因而可以合并成为:

在这里插入图片描述

看来这是“小题大做”,但在工作环节太多的时候,这样做就非常必要了。
这里讲的主要是时间方面的事,但在具体生产实践中,还有其他方面的许多事。这种方法虽然不一定能直接解决所有问题,但是,我们利用这种方法来考虑问题,也是不无裨益的。

代码示例

解法1

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.FutureTask;

/**
 * 多线程模拟《《统筹方法》》
 */
@Slf4j(topic = "c.testThread04")
public class testThread04 {
    private static FutureTask futureTask1;
    private static FutureTask futureTask2;
    public static void main(String[] args) throws Exception {

        // //洗水壶,烧开水
        Thread thread1 = new Thread(() -> {
            log.debug("开始洗水壶");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("开始烧开水");
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"老王");


        //洗茶壶、拿茶杯、拿茶叶
        Thread thread2 = new Thread(() -> {
            log.debug("洗茶壶");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("拿茶杯");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("拿茶叶");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //等待线程 1 将水烧开
            try {
                thread1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("开始泡茶");
        },"小王");

        thread1.start();
        thread2.start();
    }


}




18:26:07 [小王] c.testThread04 - 洗茶壶
18:26:07 [老王] c.testThread04 - 开始洗水壶
18:26:08 [小王] c.testThread04 - 拿茶杯
18:26:08 [老王] c.testThread04 - 开始烧开水
18:26:09 [小王] c.testThread04 - 拿茶叶
18:26:18 [小王] c.testThread04 - 开始泡茶

解法1 的缺陷: ● 上面模拟的是小王等老王的水烧开了,小王泡茶,如果反过来要实现老王等小王的茶叶拿来了,老王泡茶呢?代码最好能适应两种情况 ● 上面的两个线程其实是各执行各的,如果要模拟老王把水壶交给小王泡茶,或模拟小王把茶叶交给老王泡茶呢?
当然这么一个简单的例子,要想做的完美,看来也并非易事。后续会给出完美的解决方案。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值