每日10题 2020-12-29

本文介绍了Java中线程的join方法和interrupt方法,包括它们如何影响线程的执行和中断。此外,还讨论了守护线程的概念,即当所有非守护线程结束后,即使守护线程未完成,程序也会结束。线程的五种状态(初始、可运行、运行、阻塞、终止)和六种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)也被详细阐述。文章通过实例展示了共享资源可能导致的问题,如竞态条件,并提出使用synchronized关键字作为解决并发问题的方案。

join 方法

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

interrupt 方法

打断 sleep,wait,join 的线程,这几个方法都会让线程进入阻塞状态,打断 sleep 的线程, 会清空打断状态

守护线程

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

class ThreadTest01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread t1 = new Thread(() ->
        {
            System.out.println("thread begin");
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread end");
        },"deamon");
        t1.setDaemon(true);//不是isDeamon()
        t1.start();
        System.out.println("main thread begin");
    }
}

五种状态

在这里插入图片描述

【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行,可运行状态在start()方法之后产生
【运行状态】指获取了 CPU 时间片运行中的状态当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
【阻塞状态】如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

六种状态

在这里插入图片描述
NEW 线程刚被创建,但是还没有调用 start() 方法
RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分
TERMINATED 当线程代码运行结束

共享带来的问题

在这里插入图片描述

class ThreadTest01 {
    static int counter = 0;
    public static void main(String[] args) throws ExecutionException, InterruptedException {
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < 5000; i++) {
                    counter++;
                }
            }, "t1");
            Thread t2 = new Thread(() -> {
                for (int i = 0; i < 5000; i++) {
                    counter--;
                }
            }, "t2");
            t1.start();
            t2.start();
            t1.join();
            t2.join();
        System.out.println(counter);
	}
}

临界区 Critical Section

一个程序运行多个线程本身是没有问题的,问题出在多个线程访问共享资源
多个线程读共享资源其实也没有问题,在多个线程对共享资源读写操作时发生指令交错,就会出现问题
一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

竞态条件 Race Condition

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

synchronized 解决方案

阻塞式的解决方案:synchronized,来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换
在这里插入图片描述

synchronized(对象) // 线程1, 线程2(blocked)
{
临界区
}

两个临界区都必须加上对象锁

class ThreadTest01 {
    static int counter = 0;
    static Object lock = new Object();//静态成员变量
    public static void main(String[] args) throws ExecutionException, InterruptedException {
            Thread t1 = new Thread(() -> {
                for (int i = 0; i < 5000; i++) {
                    synchronized (lock) {
                        counter++;//临界区
                    }
                }
            }, "t1");
            Thread t2 = new Thread(() -> {
                for (int i = 0; i < 5000; i++) {
                    synchronized (lock) {
                        counter--;
                    }
                }
            }, "t2");
            t1.start();
            t2.start();
            t1.join();
            t2.join();
        System.out.println(counter);
   }
}

方法上的 synchronized

在这里插入图片描述

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值