JUC并发编程系列(一):Java线程

news2024/11/27 0:24:40

前言

        JUC并发编程是Java程序猿必备的知识技能,只有深入理解并发过程中的一些原则、概念以及相应源码原理才能更好的理解软件开发的流程。在这篇文章中荔枝会梳理并发编程的基础,整理有关Java线程以及线程死锁的知识,希望能够帮助到有需要的小伙伴~~~


文章目录

前言

一、基本概念

1.1 什么是线程

1.2 常见的三种创建线程的方式

1.3 共享变量的wait、notify、notifyAll

1.4 线程的join()、sleep()和yeild()

1.5 线程中断

二、线程死锁

2.1 线程死锁产生的四种条件

2.2 避免死锁的有效措施

2.3 守护线程和用户线程

三、ThreadLocal

3.1 threadLocals

3.2 ThreadLocal不可继承问题的解决 —— InheritableThreadLocal类

总结


一、基本概念

1.1 什么是线程

        线程是运行在进程上的单元,我们知道操作系统是将资源分配到进程上的,而线程是CPU资源分配的基本单位,或者说是占用CPU执行的基本单位更为贴切,线程栈中存放的是该线程的局部变量。进程中除了线程之外还提供了一块共享区域:堆和方法区。堆是进程中最大的一块内存,堆中存放的是所有的被new操作创建的对象示例,而方法区中则存放所有被JVM加载的类、常量和静态变量。

1.2 常见的三种创建线程的方式

常见的开启线程的方式主要有三种:继承Thread类并重写run方法、实现Runnable接口并实现run方法、实现Callable接口并实现call()方法

继承Thread类并重写run方法

    public static class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println("overwite the thread");
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }

实现Runnable接口

    public static class MyThread implements Runnable{
        @Override
        public void run() {
            System.out.println("overwrite Runnable");
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        new Thread(myThread).start();
    }

实现Callable接口

    public static class MyThread implements Callable {
        @Override
        public String call() throws Exception {
            System.out.println("overwrite callable");
            return "thread";
        }
    }

    public static void main(String[] args) throws InterruptedException{
        //创建异步任务
        FutureTask<String> futureTask = new FutureTask<>(new MyThread());
        //启动线程
        new Thread(futureTask).start();
        try{
            String result = futureTask.get();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

三种线程创建方式的区别:
通过继承的方式优点:方便传参,在需要获取当前线程时使用this,而无需使用Thread.currentThread()。

通过实现接口方式创建线程优点:由于Java语言不支持多继承,所以直接通过继承的方式还存在一定的局限性; 

        三种线程创建方式唯一一个有返回值的就是实现Callable接口并使用FutureTask创建异步任务,Callable接口中的call方法是一个有返回值的方法,可以通过FutureTask对象的get方法来获得线程返回结果。

1.3 共享变量的wait、notify、notifyAll

wait()

        当一个获得共享变量的监视器锁的线程调用变量的wait(),该线程会被阻塞挂起,同时会释放当前共享变量的监视器锁,想要结束阻塞挂起状态一般可以通过其他线程在调用wait()的线程挂起后调用该共享变量地notify() | notifyAll()方法来完成线程唤醒,也可以直接中断该线程异常返回。当然了,wait()方法中还可以提供了一个超时参数,没有在指定时间被唤醒就会返回。

    public final void wait(long timeout, int nanos) throws InterruptedException {
        if (timeout < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0) {
            timeout++;
        }

        wait(timeout);
    }

 wait(long timeout, int nanos)方法的底层也是调用一个wait(long timeout),只有nanos>0才能自增timeout。

虚假唤醒

        线程的虚假唤醒指的是一个线程在没有被其它线程notify() | notifyAll() | 被中断 | 等待超时,就直接从挂起状态变为可运行状态(被唤醒)。避免虚假唤醒的方式仅需要使用一个循环不断地去获取是否达到了可唤醒线程地条件。

notify()

        前面已经提到了,线程调用共享变量的notify()方法就可以将调用变量wait()方法阻塞的线程唤醒,需要注意的是即使唤醒该线程也不一定能直接进入执行状态,而是要与其它的线程一起竞争该共享变量的锁资源。

notifyAll()

 区别于notify()只能唤醒一个线程,notifyAll可以直接唤醒该共享变量上由于调用wait方法挂起的所有线程。

1.4 线程的join()、sleep()和yeild()

sleep

阻塞挂起指定时长,结束后会退化成一个线程就绪状态,等待下一次的CPU调度。

package Thread;

import jdk.nashorn.internal.runtime.regexp.joni.exception.InternalException;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ThreadTest {
    //创建一个资源独占锁
    private static final Lock lock = new ReentrantLock();
    //线程竞争锁的测试
    public static void main(String[] args) throws InterruptedException{
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                //给线程的资源上锁
                lock.lock();
                try{
                    System.out.println("线程被激活:"+Thread.currentThread());
                    System.out.println("threadA is sleep");
                    Thread.sleep(3000);
                    System.out.println("threadA is awake");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        });
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                //给线程的资源上锁
                lock.lock();
                try{
                    System.out.println("线程被激活:"+Thread.currentThread());
                    System.out.println("threadB is sleep");
                    Thread.sleep(3000);
                    System.out.println("threadB is awake");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        });
        //启动线程
        threadA.start();
        threadB.start();
    }
}

运行结果

 这里我们测试一下,如果给B不加锁资源的话是否B还需要等待A释放锁,结果自然是否定的。因为此时二者竞争的就不是一个锁资源辽!

需要注意的是:线程调用sleep后是不会让出锁资源的,而仅仅是在指定时间不参与线程的调度!

join()

join()是由Thread类提供的一个无参返回方法,主要功能是阻塞主线程,等待调用线程执行完成一起将结果返回。

yeild()

        正常情况下,一个线程只有将分配给自己的时间片使用完后,线程调度器才会进行下一轮的线程调度。但是如果一个线程调用yeild()方法,就相当于该线程告诉线程调度器他将让出CPU的执行权,即使按照时间片轮转机制还剩余一部分时间片。此时该线程就会由执行状态变为就绪状态,线程调度器会重新从线程就绪队列里面获取一个线程优先级最高的线程。

调用yeild方法的时候,线程并不会被阻塞挂起,而是让出时间片后处于一种就绪状态

1.5 线程中断

Java中的线程中断是一种线程间的协作模式,通过设置线程中断的方式并不能直接终止线程,而是被中端的线程根据中断状态自行决定。

interrupt()

中断线程,这里需要注意的是调用该方法并不是实际意义上的中断线程,而是通过修改中断标志来告诉线程我现在尝试中断,但具体的决定权属于被中断的线程。

isInterrupted()

判断当前调用方法实例对象的线程是否被中断,不清除线程中断的标志

Interrupted()

判断当前执行线程是否被中断,清除中断标志。需要注意的是,interrupted()内部调用的是获取当前线程和的中断标志,而不是调用interrupted()的实例对象的中断标志。

线程中断的应用场景

        在某些场景下,线程为了等待一些特定条件到来时,一般会调用sleep、wait系例函数、或者join来阻塞挂起当前线程。但如果此时线程在挂起的时候已经满足执行条件,此时我们可以直接中断线程强制抛出一个InterruptedException异常并使其处于激活状态,线程恢复并继续在新一轮CPU调度中继续向下执行。

线程的上下文切换

        线程的上下文切换指的是:CPU的执行从一个线程调度到另一个线程的过程。一个典型的场景就是:当前线程使用完时间片后,就会处于就绪状态并让出CPU资源给其它的线程占用。


二、线程死锁

2.1 线程死锁产生的四种条件

资源互斥条件

线程堆已经获取到的资源进行排他性使用,即该资源同时只有一个线程占用。只有等到占用资源的线程释放出资源后,其它的资源请求者才能获取。

请求并持有条件

线程A已经持有至少一个资源,但A又提出了新的资源请求,那么A就会一直保持着原来持有资源的占用权。如果请求的资源已被其它的资源所占用,A就会一直阻塞着,但是不会释放当前持有的资源。

不可剥夺条件

线程获取到的资源在自己使用完主动释放前,其它的线程不能抢占。

环路等待原则

形成死锁的线程必定存在着一个线程-资源的环形链。

2.2 避免死锁的有效措施

        在操作系统的学习中我们清除,形成死锁的四个条件中只有请求并持有和环路等待条件是可以被破坏的。形成死锁其实与不同线程间资源申请的顺序有关系,要避免死锁的出现我们必须保证资源申请的有序性原则。

什么是资源申请的有序性呢?

比如A和B线程同时申请诸如1,2,3,4的资源,那么我们要保证A,B线程在申请资源的时候是按照同一个顺序来申请资源的。不管A还是B先开始申请资源,其余的线程都会阻塞直至开始申请资源的线程执行完成释放出自己所拥有的资源。 

2.3 守护线程和用户线程

守护线程和用户线程的概念区分需要基于JVM,具体的区别就是用户线程如果全部结束后JVM会退出,而守护线程是否结束并不影响JVM的退出。

设置线程为守护线程

thread.setDaemon(true);

而在Tomcat中,NIO实现的NioEndpoint中会开启一组接受线程来接受用户的连接请求、一组处理线程来负责具体处理用户请求,而这些线程其实都是守护线程! 


三、ThreadLocal

        ThreadLocal是一种在线程上维护本地变量的技术,它提供了线程的本地变量,可以将共享变量拷贝一份到线程分配到的内存空间中,虽然它的提出其实并不是用来解决多线程同时访问共享变量的线程安全问题,线程安全问题一般可以通过Java中的锁机制来解决,但是这里通过ThreadLocal提供了另外一种思路。

static ThreadLocal<String> localVariable = new ThreadLocal<>();

        通过ThreadLocal创建一个本地变量localVariable ,当线程设置localVariable中的值,其实设置的是该线程的本地内存中的一个副本。值得注意的是,每个线程的本地变量并不是放在ThreadLocal实例中的,而是放在调用线程的threadLocals变量中。

3.1 threadLocals

每个线程内部都有的成员变量,类型为HashMap,其中key是我们定义的ThreadLocal变量的this引用,value通过set方法设置的值。每个线程的本地变量都存放在自己的内存变量threadLocals中!

本地变量的生命周期

只要调用线程不终止,该本地变量就会一直存放在threadLocals变量中,这就会带来内存溢出的隐患

调用线程清除本地变量

可以通过调用ThreadLocal变量里面的remove()方法。

3.2 ThreadLocal不可继承问题的解决 —— InheritableThreadLocal类

        要想父线程本地变量可以被子线程继承使用,我们就需要借助InheritableThreadLocal类。InheritableThreadLocal继承自ThreadLocal类并重写了createMap()、getMap()、childValue()三个方法。具体的使用声明倒没有什么区别:

static InheritableThreadLocal<String> localVariable= new InheritableThreadLocal<>();

        通过查看Thread源码,我们知道它的底层逻辑实现通过重写ThreadLocal类的creatMap和getMap方法让本地变量保存到了具体线程的inheritableThreadLocals成员变量中,线程在通过InheritableThreadLocal实例的set和get的时候就会创建当前线程的inheritableThreadLocals,并在创建子线程的构造函数中将父线程的inheritableThreadLocals成员变量复制一份到子线程中。下面是该类重写的三个方法:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

        从Thread类中的源码中我们可以看到,Thread类在构造方法的时候就会调用init方法,而在init方法中,我们不仅获取当前父线程,通过判断inheritableThreadLocals非空,在createInheritedMap中传入父线程的inheritableThreadLocals,复制到一个ThreadLocalMap对象中


总结

        在这篇文章中,荔枝主要梳理了有关Java线程的相关知识,包括线程创建、几种线程的状态、线程死锁产生和避免、线程中断等等。也从源码角度深入地探究了ThreadLocal的原理及其应用场景,在后续的文章中荔枝也会持续输出,深入学习JUC并发编程并持续输出高质量的知识博文,希望大家能喜欢哈哈哈哈哈。

今朝已然成为过去,明日依然向往未来!我是荔枝,在技术成长之路上与您相伴~~~

如果博文对您有帮助的话,可以给荔枝一键三连嘿,您的支持和鼓励是荔枝最大的动力!

如果博文内容有误,也欢迎各位大佬在下方评论区批评指正!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1175631.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

30、JAVA进阶——Socket编程

✅作者简介:热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:乐趣国学的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏:JAVA开发者成长之路 ✨特色专栏:国学周更-心性养成之路 🥭本文内容:JAVA进阶——Socket编程 更多内容点击👇 …

idea2023 PoJie以后无法修改内存无效

1. 打开电脑环境变量 2. 找到对应pojie文件 vmoptions目录 3. 修改这个文件 添加或者修改配置 -Xms128m -Xmx8192m4. 重启idea 修改成功

flutter开发报错The instance member ‘widget‘ can‘t be accessed in an initializer

文章目录 问题描述问题原因解决方法 问题描述 The instance member ‘widget’ can’t be accessed in an initializer. 问题原因 “The instance member ‘widget’ can’t be accessed in an initializer” 错误是因为在初始化器列表中&#xff08;constructor initializer…

JavaScript使用正则表达式

正则表达式(RegExp)也称规则表达式(regular expression)&#xff0c;是非常强大的字符串操作工具&#xff0c;语法格式为一组特殊字符构成的匹配模式&#xff0c;用来匹配字符串。ECMAScript 3以Perl为基础规范JavaScript正则表达式&#xff0c;实现Perl 5正则表达式的子集。Ja…

Linux常用命令——cdrecord命令

在线Linux命令查询工具 cdrecord Linux系统下光盘刻录功能命令 补充说明 cdrecord命令用于Linux系统下光盘刻录&#xff0c;它支持cd和DVD格式。linux下一般都带有cdrecord软件。 语法 cdrecord(选项)(参数)选项 -v&#xff1a;显示刻录光盘的详细过程&#xff1b; -eje…

基于社交网络算法的无人机航迹规划-附代码

基于社交网络算法的无人机航迹规划 文章目录 基于社交网络算法的无人机航迹规划1.社交网络搜索算法2.无人机飞行环境建模3.无人机航迹规划建模4.实验结果4.1地图创建4.2 航迹规划 5.参考文献6.Matlab代码 摘要&#xff1a;本文主要介绍利用社交网络算法来优化无人机航迹规划。 …

UseGalaxy.cn生信云|新增热图绘制工具:heatmap2

2023-11-05&#xff0c;Galaxy生信云平台 UseGalaxy.cn 新增绘制热图工具。 Graph/Display Data heatmap2 (Galaxy Version 3.1.3galaxy0) 使用方法 进入网址&#xff1a; https://usegalaxy.cn/root?tool_idtoolshed.g2.bx.psu.edu/repos/iuc/ggplot2_heatmap2/ggplot2_heatm…

8.接口与抽象类 深入多态

8.1 不该初始化的class 这个结构有什么不对&#xff1f; 这个class结构不算太差。如此设计已经能够维持最少的重复程序代码&#xff0c;且有需要特地实现的方法也已经被覆盖过。从多态的角度来看&#xff0c;我们也做到了适应性&#xff0c;所以任何Animal的子型&#xff0c;包…

什么是Spring?什么是IOC?什么是DI?IOC和DI的关系? —— 零基础可无压力学习,带源码

&#x1f9f8;欢迎来到dream_ready的博客&#xff0c;&#x1f4dc;相信您对这几篇博客也感兴趣o (ˉ▽ˉ&#xff1b;) &#x1f4dc;什么是SpringMVC&#xff1f;简单好理解&#xff01;什么是应用分层&#xff1f;SpringMVC与应用分层的关系&#xff1f; 什么是三层架构&…

【Unity细节】为什么UI移动了锚点,中心点和位置,运行的时候还是不在设置的位置当中

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 秩沅 原创 &#x1f636;‍&#x1f32b;️收录于专栏&#xff1a;unity细节和bug &#x1f636;‍&#x1f32b;️优质专栏 ⭐【…

从行车记录仪恢复已删除/丢失视频的方法

“我的车里有行车记录仪。几天前&#xff0c;当我下班回家时&#xff0c;一辆卡车不知从哪里冒出来撞向了我。我们的两辆车都损坏了&#xff0c;但幸运的是&#xff0c;没有人受伤。我曾与卡车司机就修理我的汽车进行过会面&#xff0c;但他说我有错。我需要查看我的行车记录仪…

微服务注册中心之安装+实例搭建zookeeper

1.下载安装包并上传到Linux服务器 Apache ZooKeeper 可以使用wget或者curl命令 wget http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.7.1/apache-zookeeper-3.7.1-bin.tar.gz连接失败也可以本地下载之后上传到服务器 scp /本地/文件的/路径 用户名远程服务器IP或主…

(免费领源码)java#SSM#mysql基于响应式的网上书店系统27119-计算机毕业设计项目选题推荐

摘 要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理系统的实施在技术上已逐步成熟。管理系统是一个不断发展的新型学科&#xff0c;本文主要通过对响应式的网上书店系统的功能性需求分析&#xff0c;对系统的安全性和可扩展性进行了非功能性需求分析。在详细的…

【Proteus仿真】【Arduino单片机】OLED液晶显示

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用IIC OLED液晶等。 主要功能&#xff1a; 系统运行后&#xff0c;OLED液晶显示各种图形、字符、图像。 二、软件设计 /* 作者&#xff1a;嗨小…

C++标准模板(STL)- 类型支持 (类型属性,is_bounded_array,is_unbounded_array)

类型特性 类型特性定义一个编译时基于模板的结构&#xff0c;以查询或修改类型的属性。 试图特化定义于 <type_traits> 头文件的模板导致未定义行为&#xff0c;除了 std::common_type 可依照其所描述特化。 定义于<type_traits>头文件的模板可以用不完整类型实例…

时间序列预测模型实战案例(八)(Informer)BestPaper论文模型Informer代码实战讲解

论文地址->Informer论文地址PDF点击即可阅读 代码地址-> 论文官方代码地址点击即可跳转下载GIthub链接 本文介绍 本篇博客带大家看的是Informer模型进行时间序列预测的实战案例&#xff0c;它是在2019年被提出并在ICLR 2020上被评为Best Paper&#xff0c;可以说Inform…

如何再kali中下载iwebsec靶场

这个靶场有三种搭建方法&#xff1a; 第一种是在线靶场&#xff1a;http://www.iwebsec.com:81/ 第二种是虚拟机版本的&#xff0c;直接下载到本地搭建 官网地址下载&#xff1a;http://www.iwebsec.com/ 而第三种就是利用docker搭建这个靶场&#xff0c;我这里是用kali进行…

【CIO人物展】厦门市妇幼保健院CIO李振叶:医院数字化转型是迈向智慧医院新时代的关键...

李振叶 本文由厦门市妇幼保健院CIO李振叶投递并参与《2023中国数智化转型升级优秀CIO》榜单/奖项评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 厦门市妇幼保健院&#xff08;厦门大学附属妇女儿童医院&#xff09;创建于1959年&#xff0c;是集保健、医疗、教学、科研…

代码随想录二刷Day 59

647. 回文子串 这个题的dp定义想不到&#xff0c;递推公式也想不到但是看题解都很容易理解&#xff0c;遍历顺序不太好理解。 class Solution { public:int countSubstrings(string s) {vector<vector<bool>> dp(s.size(), vector<bool>(s.size(), false)…

AI系统ChatGPT程序源码+AI绘画系统源码+支持GPT4.0+Midjourney绘画

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…