JUC第一次笔记 —— java线程

1、预热

idea新建maven工程,pom.xml如下:

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>

进程 && 线程

image-20220515011052031

image-20220515011116684

并发 && 并行

image-20220515011159514

image-20220515011212733

image-20220515011245289

image-20220515011307836

ps:RobPike :golang 语言的创造者,go可以写JVM

CPU多核单核与多线程单线程

  1. 单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用cpu ,不至于一个线程总占用 cpu,别的线程没法干活

  2. 多核 cpu 可以并行跑多个线程,但能否提高程序运行效率还是要分情况的。有些任务,经过精心设计,将任务拆分,并行执行,当然可以提高程序的运行效率。但不是所有计算任务都能拆分(参考后文的【阿姆达尔定律】)也不是所有任务都需要拆分,任务的目的如果不同,谈拆分和效率没啥意义

  3. IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】,这时相当于线程虽然不用 cpu,但需要一直等待 IO 结束,没能充分利用线程。所以才有后面的【非阻塞 IO】和【异步 IO】优化

2、java线程

2.1 线程状态

2.1.1 OS层面

image-20220515011756485

2.1.2 java的API层面

image-20220515011841765

image-20220515220243190

package A03;

import lombok.extern.slf4j.Slf4j;

import java.io.IOException;

@Slf4j
public class Main10_thread_state {
    public static void main(String[] args) throws IOException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                while(true) { // runnable

                }
            }
        };
        t2.start();

        Thread t3 = new Thread("t3") {
            @Override
            public void run() {
                log.debug("running...");
            }
        };
        t3.start();

        Thread t4 = new Thread("t4") {
            @Override
            public void run() {
                synchronized (Main10_thread_state.class) {
                    try {
                        Thread.sleep(1000000); // timed_waiting
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t4.start();

        Thread t5 = new Thread("t5") {
            @Override
            public void run() {
                try {
                    t2.join(); // waiting
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t5.start();

        Thread t6 = new Thread("t6") {
            @Override
            public void run() {
                synchronized (Main10_thread_state.class) { // blocked
                    try {
                        Thread.sleep(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t6.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 state {}", t1.getState());
        log.debug("t2 state {}", t2.getState());
        log.debug("t3 state {}", t3.getState());
        log.debug("t4 state {}", t4.getState());
        log.debug("t5 state {}", t5.getState());
        log.debug("t6 state {}", t6.getState());
        System.in.read();
    }
}

2.2 创建线程

2.2.1 创建方式一:Thread直接创建

@Slf4j(topic = "c.03001")
public class Main01 {
    public static void main(String[] args) {
        Thread t1 = new Thread("t1") {
            @Override
            public void run(){
                log.debug("t1--hello");
            }
        };
        t1.start();
        log.debug("main--hello");
    }
}
结果:
    
01:22:16.501 [main] DEBUG c.03001 - main--hello
01:22:16.501 [t1] DEBUG c.03001 - t1--hello

Process finished with exit code 0

2.2.2 创建方拾二:使用 Runnable 配合 Thread

@Slf4j(topic = "03002")
public class Main02 {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                log.debug("Runnable--hello");
            }
        };
        Thread t2 = new Thread(runnable, "t2");
        t2.start();

        log.debug("Main02--hello");
    }
}
结果:
01:23:16.900 [main] DEBUG 03002 - Main02--hello
01:23:16.900 [t2] DEBUG 03002 - Runnable--hello

Process finished with exit code 0

思考:

原理之 Thread Runnable 的关系

分析 Thread 的源码,理清它与 Runnable 的关系

小结

方法1 是把线程和任务合并在了一起,方法2 是把线程和任务分开了

用 Runnable 更容易与线程池等高级 API 配合

用 Runnable 让任务类脱离了 Thread 继承体系,可以更灵活

加入Lambda表达式

@Slf4j(topic = "03003")
public class Main03 {
    public static void main(String[] args) {

        /**
         * 组合优于继承
         */
        Runnable runnable = () -> log.debug("Runnable--hello");
        Thread t2 = new Thread(runnable, "t2");
        t2.start();

        log.debug("Main03--hello");

        Thread t3 = new Thread(() -> {log.debug("t3--hello");}, "t3");
        t3.start();
    }
}

**2.2.3 方式三,**FutureTask 配合 Thread

@Slf4j(topic = "c.03005")
public class Main04 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                log.debug("call -- hello");
                Thread.sleep(2000);
                return 100;
            }
        });
        Thread t1 = new Thread(task, "t1");
        t1.start();
        log.debug("{}",task.get());
    }
}

加入Lambda

@Slf4j(topic = "c.03005")
public class Main05 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> task1 = new FutureTask<>(()-> {
                log.debug("call -- hello");
                Thread.sleep(2000);
                return 100;
        });
        new Thread(task1, "t1").start();
        log.debug("{}",task1.get());
    }
}

2.3 java线程内幕

image-20220515013432845

写一个程序,把debug调到线程模式

image-20220515140249497

可以看到两条线程

image-20220515140344345

代码

image-20220515140446587

2.4 线程常见方法

start()

image-20220515013733424

说明:启动一个新线程,在新的线程运行 run 方法中的代码

注意:start 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException

image-20220515013908136

run()

image-20220515014037775

说明;新线程启动后会调用的方法

注意:如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象,来覆盖默认行为。参考本文 2.2.2

join()

说明:等待线程运行结束

join(long n) :等待线程运行结束,最多等待 n 毫秒

image-20220515014312144

拿来直接用,不要思考

image-20220515014505541

isAlive():线程是否存活(还没有运行完毕)

interrupt()

image-20220515014856072

isInterrupted() 和 interrupted()

ps: interrupted() 是 static方法

image-20220515015312164

currentThread()

static 方法

获取当前正在执行的线程

image-20220515015456167

sleep(long n)和yield()

image-20220515015623351

其他不推荐的方法

image-20220515015719661

image-20220515020014135

2.5 操作一下

2.5.1 run 和 start

run方法启动都在main线程里面

image-20220515142533596

start方法启动是新的线程

image-20220515142607315

结论

直接调用 run 是在主线程中执行了 run,没有启动新的线程

使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

2.5.2 sleep yield

sleep方法让线程进入Timed Waiting 状态(阻塞)

image-20220515144058248

其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException

image-20220515144317727

sleep时间的可读性加强

image-20220515144741523

sleep还可以保护CPU

image-20220515150918269

线程优先级:两个线程代码没加干扰,累加数差距不大

image-20220515150144206

使用yield方法,可以看到拉开差距

image-20220515150353365

我们也可以设置线程的优先级

image-20220515150841453

计算型程序CPU刷的一下就上去了,计算结束就掉下来了

image-20220515151144958

2.5.3 join

没join之前得不到正确的结果,sleep了也没有用

image-20220515172036292

join之后

image-20220515172100453

评价

  • 需要外部共享变量,不符合面向对象封装的思想

  • 必须等待线程结束,不能配合线程池使用

下面看等待多个结果

image-20220515204902593

t2和t1调换一下

image-20220515205008445

有时效的join,等得不耐烦就不等了

image-20220515205556178

子线任务提前完成不会死等,结果来了就结束等待

image-20220515205724682

2.5.4 interrupt 方法

打断 sleep,wait,join 的线程
这几个方法都会让线程进入阻塞状态,但是打断标记会置为假
打断 sleep 的线程, 会清空打断状态,以 sleep 为例

image-20220515210658049

打断正常运行的线程
打断正常运行的线程, 不会清空打断状态

判断打断标记来输出信息,优雅的方式,自己料理后事

image-20220515211023476

终止模式之两阶段终止模式

Two Phase Termination
在一个线程 T1 中如何“优雅”终止线程 T2?这里的【优雅】指的是给 T2 一个料理后事的机会。

  1. 错误思路
    使用线程对象的 stop() 方法停止线程
    stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,
    其它线程将永远无法获取锁
    使用 System.exit(int) 方法停止线程
    目的仅是停止一个线程,但这种做法会让整个程序都停止
  2. 两阶段终止模式

image-20220515211550227

代码实现

    /**
     * 模拟监控进程被打断
     */
    @Test
    public void test3() throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
//        tpt.start();
//        tpt.start();

        Thread.sleep(3500);
        log.debug("停止监控");
        tpt.stop();
    }
    @Test
    public void test4() throws InterruptedException {
        TwoPhaseTermination2 tpt = new TwoPhaseTermination2();
        tpt.start();
        Thread.sleep(3500);
        log.debug("停止监控");
        tpt.stop();
    }

// 利用 isInterrupted
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination {
    // 监控线程
    private Thread monitorThread;
    // 停止标记
    private volatile boolean stop = false;
    // 判断是否执行过 start 方法
    private boolean starting = false;

    // 启动监控线程
    public void start() {
        synchronized (this) {
            if (starting) { // false
                return;
            }
            starting = true;
        }
        monitorThread = new Thread(() -> {
            while (true) {
                Thread current = Thread.currentThread();
                // 是否被打断
                if (stop) {
                    log.debug("料理后事");
                    break;
                }
                try {
                    Thread.sleep(1000);
                    log.debug("执行监控记录");
                } catch (InterruptedException e) {
                }
            }
        }, "monitor");
        monitorThread.start();
    }

    // 停止监控线程
    public void stop() {
        stop = true;
        monitorThread.interrupt();
    }
}

//  利用停止标记
@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination2 {
    // 监控线程
    private Thread monitorThread;
    // 判断是否执行过 start 方法
    private boolean starting = false;

    // 启动监控线程
    public void start() {
        synchronized (this) {
            if (starting) { // false
                return;
            }
            starting = true;
        }
        monitorThread = new Thread(() -> {
            while (true) {
                Thread current = Thread.currentThread();
                // 是否被打断
                if (current.isInterrupted()) {
                    log.debug("料理后事");
                    break;
                }
                try {
                    Thread.sleep(1000);
                    log.debug("执行监控记录");
                } catch (InterruptedException e) {
                    //sleep出现异常之后,会把打断标记置为 false
                    // 这边需要重置打断标记
                    current.interrupt();
                }
            }
        }, "monitor");
        monitorThread.start();
    }

    // 停止监控线程
    public void stop() {
        monitorThread.interrupt();
    }
}

2.5.5 park

执行了park方法就蚌住了,程序不走了

image-20220515214539421

image-20220515215251823

他又行了

image-20220515215319662

3、主线程与守护线程

image-20220515215402678

4、练习

烧水泡茶

image-20220515220422275

办法1最好,但是思考一下image-20220515220516618

代码敲一敲

image-20220515221031256

5、总结

线程创建

  • 线程重要 api,如 start,run,sleep,join,interrupt 等

线程状态

应用方面

  • 异步调用:主线程执行期间,其它线程异步执行耗时操作

  • 提高效率:并行计算,缩短运算时间

  • 同步等待:join

  • 统筹规划:合理使用线程,得到最优效果

原理方面

  • 线程运行流程:栈、栈帧、上下文切换、程序计数器

  • Thread 两种创建方式 的源码

模式方面

[外链图片转存中…(img-SuV9KXcn-1652945436725)]

[外链图片转存中…(img-ajQO7eFb-1652945436725)]

他又行了

[外链图片转存中…(img-jFTYIy7k-1652945436726)]

3、主线程与守护线程

[外链图片转存中…(img-g1gqlBCt-1652945436726)]

4、练习

烧水泡茶

[外链图片转存中…(img-hN2qRKpi-1652945436726)]

办法1最好,但是思考一下[外链图片转存中…(img-FrbNxLdv-1652945436726)]

代码敲一敲

[外链图片转存中…(img-tejvfZBN-1652945436727)]

5、总结

线程创建

  • 线程重要 api,如 start,run,sleep,join,interrupt 等

线程状态

应用方面

  • 异步调用:主线程执行期间,其它线程异步执行耗时操作

  • 提高效率:并行计算,缩短运算时间

  • 同步等待:join

  • 统筹规划:合理使用线程,得到最优效果

原理方面

  • 线程运行流程:栈、栈帧、上下文切换、程序计数器

  • Thread 两种创建方式 的源码

模式方面

  • 终止模式之两阶段终止
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

高高飞起的勇敢麦当

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值