Java 多线程 -- 从入门到精通

2023-11-01

持续更新中,欢迎收藏,关注,以便查看后续

Java线程与线程的区别

  • 所有与进程相关的资源,都被记录在PCB(进程控制模块)中。
  • 进程是抢占处理机的调度单位。
  • 线程属于某个进程,共享进程的资源。
  • 线程由堆栈寄存器、程序计数器和TCB(线程控制模块)组成。
  • 线程是CPU调度的最小单位,进程是资源分配的最小单位。
  • 线程不能看做独立应用,而进程可看做独立应用
  • 进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径
  • 线程没有独立的地址空间,多进程的程序比多线程程序健壮
  • 进程的切换比线程的切换开销大

多线程的实现方法

  • 继承Thread类,重写run方法
  • 实现Runnable接口,重写run方法
  • 通过Callable和FutureTask创建线程
  • 通过线程池创建线程

Thread中start和run方法的区别

  • run方法只是thread的一个普通方法调用,还是在主线程里执行,是不会开启多线程的

直接调用Run方法,程序中只有主线程这一个线程,执行路径只有一条,还是要顺序执行,需要run方法体执行完毕,才可执行下面的代码。(相当与普通的方法)

  • start方法可启动多线程

start方法启动线程,无需等待run方法体代码执行完毕,可以直接继续执行下面的代码。(真正的实现多线程)

  • 代码示例

创建一个MyThread方法继承Thread

public class MyThread extends Thread {

    private String name;

    public MyThread(String name) {
        this.name = name;
    }

    public void run(){
        for (int i = 0 ; i < 100 ; i++){
            System.out.println(this.name +"--"+ i);
        }
    }

}

使用run方法:结果四个方法按照顺序运行,得出结论(直接使用run方法不是多线程)

public class demo {

    public static void main(String[] args) {
        MyThread my1 = new MyThread("第1个线程");
        MyThread my2 = new MyThread("第2个线程");
        MyThread my3 = new MyThread("第3个线程");
        MyThread my4 = new MyThread("第4个线程");

        my1.run();
        my2.run();
        my3.run();
        my4.run();
    }

}

使用start方法:结果发现四个方法交替输出,得出结论(start真正的实现多线程)

public class demo {

    public static void main(String[] args) {
        MyThread my1 = new MyThread("第1个线程");
        MyThread my2 = new MyThread("第2个线程");
        MyThread my3 = new MyThread("第3个线程");
        MyThread my4 = new MyThread("第4个线程");

        my1.start();
        my2.start();
        my3.start();
        my4.start();
    }

}

Thread和Runnable的关系

  • Thread是实现了Runnable接口的类,是Runnable的具体实现,使得run支持多线程;
  • 因类的单一继承原则,推荐多使用Runnable接口;

创建一个MyThread方法继承Thread

public class MyRunnable implements Runnable {

    private String name;

    public MyRunnable(String name) {
        this.name = name;
    }

    public void run(){
        for (int i = 0 ; i < 100 ; i++){
            System.out.println(this.name +"--"+ i);
        }
    }

}

Runnable的代码实现

public static void main(String[] args) {
        MyRunnable my1 = new MyRunnable("第1个线程");
        MyRunnable my2 = new MyRunnable("第2个线程");
        MyRunnable my3 = new MyRunnable("第3个线程");
        MyRunnable my4 = new MyRunnable("第4个线程");

        Thread t1 = new Thread(my1);
        Thread t2 = new Thread(my2);
        Thread t3 = new Thread(my3);
        Thread t4 = new Thread(my4);
        
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

使用Callable和Future创建线程

获取返回值示例

  1. 创建实现Callable接口的类
public class MyCallable implements Callable {

    public String call() throws Exception {
        return "hello world";
    }

}
  1. 获取多线程返回值
@Test
public void testThread1(){
    // 1.获取FutureTask对象
    MyCallable myCallable = new MyCallable();
    FutureTask futureTask = new FutureTask(myCallable);
    // 2.开启线程
    new Thread(futureTask).start();
    try{
        String s = (String) futureTask.get();
        System.out.println(s);
    }catch (InterruptedException e){
        e.printStackTrace();
    }catch (ExecutionException e){
        e.printStackTrace();
    }
}

线程返回值的处理方法

  • 主线程等待法:循环–检测–睡眠 —》 检测要获取值不为空 停止睡眠
  • 使用Thread类的join()方法:阻塞当前线程以等待子线程处理完毕
  • 实现Callable接口:通过FutureTask或线程池获取

线程池的创建使用

阻塞队列

容量有限
基于数组的先进先出队列
BlockingQueue< Runnable > workQueue = new ArrayBlockingQueue<>(5);

容量无限
基于链表的先进先出队列
弊端:如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了
BlockingQueue< Runnable > workQueue = new LinkedBlockingQueue<>();

拒绝策略

默认
队列满了之后它将抛出 RejectedExecutionException 异常
RejectedExecutionHandler rejected = new ThreadPoolExecutor.AbortPolicy();

队列满了丢任务不异常,但是线程池将丢弃被拒绝的任务。
RejectedExecutionHandler rejected = new ThreadPoolExecutor.DiscardPolicy();

将最早进入队列的任务删除,然后将被拒绝的任务添加到等待队列中。
RejectedExecutionHandler rejected = new ThreadPoolExecutor.DiscardOldestPolicy();

如果添加到线程池失败,那么主线程会自己去执行该任务
RejectedExecutionHandler rejected = new ThreadPoolExecutor.CallerRunsPolicy();

创建多线程

四种构造方法:

/**
*  corePoolSize    核心线程数
*  maximumPoolSize 最大线程数
*  keepAliveTime   idle线程存活时间
*  unit            上个参数的单位
*  workQueue       线程对象的缓冲队列
*  threadFactory   生成线程的工厂
*  handler         达到容量后的回调
*/

//1.
ThreadPoolExecutor(int corePoolSize, 
        int maximumPoolSize, 
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue)

//2.
ThreadPoolExecutor(int corePoolSize, 
        int maximumPoolSize, 
        long keepAliveTime, 
        TimeUnit unit, 
        BlockingQueue<Runnable> workQueue, 
        RejectedExecutionHandler handler)

//3.
ThreadPoolExecutor(int corePoolSize, 
        int maximumPoolSize, 
        long keepAliveTime, 
        TimeUnit unit, 
        BlockingQueue<Runnable> workQueue, 
        ThreadFactory threadFactory)

//4.
ThreadPoolExecutor(int corePoolSize, 
        int maximumPoolSize, 
        long keepAliveTime, 
        TimeUnit unit, 
        BlockingQueue<Runnable> workQueue, 
        ThreadFactory threadFactory, 
        RejectedExecutionHandler handler)

线程不安全

示例代码

public class Demo implements Runnable {

    private static int sum = 0;

    public void run() {
        for (int i = 0;i<50000;i++) {
            System.out.println(Thread.currentThread().getName() + ":" + (sum++));
        }
    }

    public static void main(String[] args) {
        Demo syncThread = new Demo();
        Thread thread1 = new Thread(syncThread,"线程1");
        Thread thread2 = new Thread(syncThread,"线程2");
        Thread thread3 = new Thread(syncThread,"线程3");
        Thread thread4 = new Thread(syncThread,"线程4");
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
    
}

运行截图如下:多次运行结果不一致,这就是线程不安全。正常最后一个值应该为:50000*4-1=199999

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

解决线程不安全(synchronized)

示例代码

public class Demo implements Runnable {

    private static int sum = 0;

    public void run() {
        synchronized (this) {
            for (int i = 0;i<50000;i++) {
                System.out.println(Thread.currentThread().getName() + ":" + (sum++));
            }
        }
    }

    public static void main(String[] args) {
        Demo syncThread = new Demo();
        Thread thread1 = new Thread(syncThread,"线程1");
        Thread thread2 = new Thread(syncThread,"线程2");
        Thread thread3 = new Thread(syncThread,"线程3");
        Thread thread4 = new Thread(syncThread,"线程4");
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

运行截图如下:最后一个值多次测试都为199999

在这里插入图片描述

sleep和wait的区别

  • 基本差别
  1. sleep是thread类的方法,wait是Object类中定义的方法。
  2. sleep()方法可以在任何地方使用。
  3. wait()方法只能在synchronized方法或者synchronized块中使用。
  • 本质差别
  1. Thread.sleep只会让出CPU,不会导致锁行为的改变。
  2. Object.wait不仅仅让出CPU,还会让出已经占有的同步资源锁。

示例代码:

public void run() {
        synchronized (this) {
            try {
                //不仅仅让出CPU,还会让出已经占有的同步资源锁。
                this.wait(1000);
                for (int i = 0;i<50000;i++) {
                    System.out.println(Thread.currentThread().getName() + ":" + (sum++));
                }
                //只会让出CPU,不会导致锁行为的改变。
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

notify与notifyAll的区别

  • notify:只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。(随机)
  • notifyAll:所有等待该锁的所有线程都会被唤醒,所有被唤醒的线程都将争夺锁,如果某一个线程获得了锁,其他线程将会进入线程等待。

生活小案例:
notify:
好比你在上厕所,外面有很多人在等,但是有一个人是厕所管理员。等你出来的时候,由管理员随机找一个人去上厕所。
notifyAll:
好比你还在上厕所,外面依然有很多人在等待,但是没有厕所管理员。等你出来的时候,大家一起去抢厕所的使用权,当有一个人抢到厕所的时候,其他人重新等待空余的厕所。

线程的六个状态

  1. 初始(NEW):
  2. 运行(RUNNABLE):
  3. 阻塞(BLOCKED):
  4. 等待(WAITING):
  5. 超时等待(TIMED_WAITING):
  6. 终止(TERMINATED):shang

第三步到第五步都属于阻塞状态
查看进程状态代码示例如下

public class Demo{
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new MyRunnable());
        System.out.println(thread.getState());//线程状态:初始(NEW)
        thread.start();
        System.out.println(thread.getState());//线程状态:运行(RUNNABLE)
        //为了保证下方方法体执行完毕,让当前主线程休眠0.1s
        Thread.sleep(100);
        System.out.println(thread.getState());//线程状态:终止(terminated)
    }
}
class MyRunnable implements Runnable{
    public void run() {
        for (int i = 0; i < 100; i++) {
        }
        System.out.println("循环完成~");
    }
}

补充:

  • 进入synchronized时,且没有获取到锁,线程状态 ---- blocked
    直到锁被释放。线程状态 ---- runnable
  • 线程调用wait()或join时,线程状态 ---- waiting
    调用notify或notifyAll时,或join的线程执行结束后,线程状态 ---- runnable
  • 线程调用sleep(time),或wait(time)时,线程状态 ---- timed waiting
    当休眠时间结束后,或者调用notify或notifyAll时。线程状态 ---- runnable
  • 程序执行结束,线程状态 ---- terminated

Thread.yield

使用yield线程会把CPU时间让掉,让所有线程在竞争一次,也就是说也就是谁先抢到谁执行。
代码示例如下:

public class Demo1 extends Thread {

    private String name;

    public Demo1(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println(this.name+"-------"+i);
            // 当i为5时,该线程就会把CPU时间让掉,让所有线程在竞争一次,也就是说也就是谁先抢到谁执行。
            if (i == 5) {
                this.yield();
            }
        }
    }

    public static void main(String[] args) {
        Demo1 d1 = new Demo1("张三");
        Demo1 d2 = new Demo1("李四");
        Demo1 d3 = new Demo1("王五");
        d1.start();
        d2.start();
        d3.start();
    }
}

什么是线程安全

在多条线程访问的时候,我们在主程序中不需要去做任何的同步,的程序还能按照我们预期的行为去执行,那么我们就可以说这个类是线程安全的。

我们什么时候需要考虑线程安全呢?:多个线程访问同一个资源
· 如果是多个线程访问同一个资源,那么就需要上锁,才能保证数据的安全性。
· 如果每个线程访问的是各自的资源,那么就不需要考虑线程安全的问题,所以这个时候,我们可以放心使用非线程安全的对象

如何实现线程安全

一、synchronized

采用synchronized关键字给代码块或方法加锁

二、Lock

在java 5之后,java.util.concurrent.locks包下提供了另外一种方式来实现线程同步,就是Lock。

三、synchronized和Lock的区别:

  • Lock是接口,synchronized是关键字
  • Lock可以提高多个线程进行读操作的效率。
  • Lock可以让等待锁的线程响应中断,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
  • 发生异常时:Lock需要在finally块中释放锁,否则很可能造成死锁现象。synchronized会自动释放线程占有的锁,不会导致死锁现象发生。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java 多线程 -- 从入门到精通 的相关文章

  • string.split("(?!^)") 解释

    我正在尝试将字符串的字符拆分为字符串数组 我找到了解决方案here https stackoverflow com questions 5235401 split string into array of character strings
  • 相当于 java PBKDF2WithHmacSHA1 的 Python

    我的任务是构建一个 API 的使用者 该 API 需要带有 UNIX 时间种子值的加密令牌 我看到的示例是使用我不熟悉的 Java 实现的 在阅读文档和其他堆栈文章后一直无法找到解决方案 使用javax crypto SecretKey j
  • Java中如何合并两个数组?

    它不是连接而是合并两个数组 使它们成为名称值对的数组 firstarray a aa aaa secondarray b bb bbb result a b aa bb aaa bbb 最好的方法是什么 in Java public sta
  • 将键与多个值对象关联的有效集合[重复]

    这个问题在这里已经有答案了 有任何有效的集合可以将键与多个值关联起来 例如 new HashMap
  • 从SQLite列中获取所有数字字符串并进行总和计算

    我是 Android 和 SQLite 的新手 我在 SQLite 中有一个只有数字的 AMOUNT 列 我可以在 ListView 中显示它 但我无法找到任何我理解的方法来将它们全部添加并显示在 TextView 中 这是数据库助手 im
  • Java 7u51/7u55 带星号的清单变量

    我正在部署一个小程序 其中包含清单中的下一个变量 Manifest Version 2 0 Ant Version Apache Ant 1 8 2 Trusted Library true Permissions all permissi
  • Eclipse 与 IntelliJ 热部署

    我的应用程序配置 Tomcat 8 Spring Spring MVC Hibernate 在 Eclipse 中 我创建了 Tomcat 服务器 并将我的应用程序添加到资源中 JSP JS CSS 和 JAVA 类热部署的工作原理就是这样
  • 如何在android中使用retrofit访问404错误?

    我正在使用改造 2 访问 REST API 以使用原始正文插入 JSON 数据 我从服务器获得成功响应 但在响应时收到 404 错误 我想访问404错误请帮我解决这个问题 ApiUtil getServiceClass sendFinalC
  • 在 Spring Webflux 中执行阻塞 JDBC 调用

    我使用 Spring Webflux 和 Spring data jpa 使用 PostgreSql 作为后端数据库 我不想在进行数据库调用时阻塞主线程 例如find and save 为了实现同样的目标 我有一个主调度程序Controll
  • Map:为 Integer 和 Double 类型定义方法,但不为 String 类型定义方法

    我正在尝试定义一个方法putIfGreaterThan 为了我的新Map class 给定一个键 仅当新值大于旧值时 它才会用新值替换旧值 我知道我可以通过组合来实现这一点 通过有一个private final Map
  • Java:从 ScriptEngine javascript 返回一个对象

    我正在尝试使用 Java 来评估 javascript脚本引擎 https docs oracle com javase 7 docs api javax script ScriptEngine html班级 这是我正在尝试做的事情的一个简
  • 检查更新时 Maven 无限期挂起

    我正在使用 Maven 构建一个项目 我是新手 并且它挂起 mvn package INFO Scanning for projects INFO INFO Building Presentation Reports INFO task s
  • 如何组合 3 个或更多 CompletionStages?

    如果有 2 个 CompletionStages 我可以将它们与thenCombine method CompletionStage a aCompletionStage getA CompletionStage b bCompletion
  • SwingUtilities.invokeLater

    我的问题与SwingUtilities invokeLater 我应该什么时候使用它 每次需要更新 GUI 组件时都必须使用吗 它到底有什么作用 是否有替代方案 因为它听起来不直观并且添加了看似不必要的代码 Do I have to use
  • @Transactional 注解属于哪里?

    如果您将 Transactional in the DAO类和 或其方法 或者注释使用 DAO 对象调用的服务类是否更好 或者注释两个 层 是否有意义 我认为事务属于服务层 它是了解工作单元和用例的人 如果您将多个 DAO 注入到需要在单个
  • java charAt() 和startsWith() 哪个更快? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 我的问题是 如果我想检查特定索引中字符串的一个字符 仅检查一个字符 哪种方法非常有效charAt or startsWith 我的意思是 据我所
  • 为什么 CompletableFuture 的 thenAccept() 不在主线程上运行

    我在 CompletableFuture 的 SupplyAsync 中处理长时间运行的操作 并将结果放入 thenAccept 中 有时 thenAccept 在主线程上执行 但有时它在工作线程上运行 但我只想在主线程上运行 thenAc
  • Unix 纪元时间转 Java Date 对象

    我有一个包含以下内容的字符串UNIX 纪元时间 https en wikipedia org wiki Unix time 我需要将其转换为 Java Date 对象 String date 1081157732 DateFormat df
  • 如果 @transactional 在类级别应用,如何拦截 @transactional 参数

    我想捕获 transactional 的参数 如果它应用于类级别 例如如果 transactional应用在方法级别 例如 class A transactional readOnly true public void someMethod
  • Zookeeper 未启动,nohup 错误

    我已经下载了zookeeper 3 4 5 tar gz 解压后我将conf zoo cfg写为 tickTime 2000 dataDir var zookeeper clientPort 2181 现在我尝试通过 bin zkServe

随机推荐

  • 搜狐2012年校园招聘会笔试题解析

    一 不定项选择题 1 以下程序的打印结果是 cpp view plain copy include
  • QT笔记- 使窗口不获得焦点,但响应鼠标事件

    HWND wid HWND this gt winId SetWindowLong wid GWL EXSTYLE GetWindowLong wid GWL EXSTYLE WS EX NOACTIVATE WS EX COMPOSITE
  • Polycarp and Div 3【Codeforces Round #496 (Div. 3)【D题】】【贪心】

    应该说是今天凌晨的吧 第一次打Code Forces 懵懵懂懂的 不过感觉还是良好 做了几道签到题 难题还是没有那个水准去做 Polycarp likes numbers that are divisible by 3 He has a h
  • 应用统计学与R语言实现笔记(番外篇四)——bookdown使用与OR值计算

    本期是之前做的应用统计学与R语言实现笔记的番外篇四 本期主要关注两个问题 一个是重新利用R的bookdown包创建新的电子书 另一个是计算公共卫生当中一个比较常见的指标OR值 文章目录 1 bookdown使用 2 公式更正 3 OR值计算
  • linux下网站压力测试工具webbench

    webbench最多可以模拟3万个并发连接去测试网站的负载能力 个人感觉要比Apache自带的ab压力测试工具好 安装使用也特别方便 1 适用系统 Linux 2 编译安装 引用wget http blog s135 com soft li
  • Sentinel 入门使用

    目录 一 Sentinel简介 1 1Sentinel简介 1 2 Sentinel与Hystrix的区别 1 3 名词解释 二 sentinel控制台 2 1 下载启动控制台 2 3 客户端接入控制台 2 4 Rest整合Sentinel
  • python基本概念-关键要素

    1 要素1 数据类型 Python提供了几种内置的数据类型 现在我们只关注其中两种 Python使用int类型表示整数 正整数或负整数 使用str类型表示字符串 Unicode字符序列 如果需要将一个数据项从某种类型转换为另一种类型 可以使
  • DB2数据库连接(jdbc连接)encoding not supported

    在进行db2数据库连接过程中发现了一些问题 报如下错误 com ibm db2 jcc b DisconnectException encoding not supported 该问题的出现是IBM JDK和sun JDK之间相互不支持 解
  • 【滤波器】7. 带通滤波器

    将低通滤波器和高通滤波器串联 如下图所示 就可得到带通滤波器 设低通滤波器的截止频率为 f p 1 f p1 fp1 高通滤波器的截止频率为
  • Spark 的Shuffle过程详解

    一 Shuffle的作用是什么 Shuffle的中文解释为 洗牌操作 可以理解成将集群中所有节点上的数据进行重新整合分类的过程 其思想来源于hadoop的mapReduce Shuffle是连接map阶段和reduce阶段的桥梁 由于分布式
  • if与if else与if else if else之间的用法与区别(C++)

    1 if 满足这个条件 执行语句操作 不满足条件 不操作 结构 if 条件 语句 2 if else 满足这个条件 执行语句1操作 不满足 执行语句2操作 结构 if 条件 语句1 else 语句2 备注 通俗说就是两者取其一 注意 if
  • 简单方法恢复linux以及windows启动引导

    1 恢复linux启动引导 以ubuntu为例 很多小孩喜欢用wubi装linux 确实这种方法比较简单 比较安全 但是这种安装方法是基于windows的 也就是说 如果windows挂了 比如重装了或还原了 那么原来安装的linux也就没
  • cvFindContours函数使用

    CV IMPL intcvFindContours void img CvMemStorage storage CvSeq firstContour int cntHeaderSize int mode int method CvPoint
  • Servlet 实现重定向几种方法

    servlet重定向 在servlet JSP编程中 服务器端重定向可以通过下面两个方法来实现 1 运用javax servlet RequestDispatcher接口的forward方法 2 或者运用javax servlet http
  • 基于FPGA的串口通讯设计与实现

    繁體 基于FPGA的串口通讯设计与实现 日期 2012 03 26 来源 作者 字体 大 中 小 随着多微机系统的应用和微机网络的发展 通信功能越来起重要 串行通信是在一根传输线上一位一位传送信息 这根线既作数据线又作联络线 串行通信作为一
  • uniapp实现支付功能 和 可视化拖拽工具

    1 支付功能 https blog csdn net weixin 37787674 article details 103012041 2 分享一个 uniapp uview ui 可视化 完全自由拖拽 一键生成flex代码网站 http
  • 你必须知道的495个C语言问题整理三

    1 为什么大家都说不要使用gets 跟fgets 不同 gets 不能被告知输入缓冲区的大小 因此不能避免缓冲区的溢出 标准库的fgets 函数对gets 作了很大的改进 尽管它仍然不完善 2 fgetops fsetops 和ftell
  • python入门笔记——函数①

    python入门笔记 函数 def function 定义一个名为function的函数 定义函数时用下面来解释该函数的用处 这个函数是用来重复输出4次 你好 的 return for i in range 1 5 print 你好 pas
  • SSL、TLS、HTTPS的关系

    SSL TLS HTTPS的关系 SSL Secure Sockets Layer 安全套接字协议 TLS Transport Layer Security 传输层安全性协议 TLS是SSL的升级版 两者几乎是一样的 HTTPS Hyper
  • Java 多线程 -- 从入门到精通

    持续更新中 欢迎收藏 关注 以便查看后续 Java 多线程 从入门到精通 Java线程与线程的区别 多线程的实现方法 Thread中start和run方法的区别 Thread和Runnable的关系 使用Callable和Future创建线