Java核心知识体系-并发与多线程:线程基础

news2024/12/28 21:05:44

1 先导

image

Java线程基础主要包含如下知识点,相信我们再面试的过程中,经常会遇到类似的提问。

1、线程有哪几种状态? 线程之间如何转变?

2、线程有哪几种实现方式? 各优缺点?

3、线程的基本操作(线程管理机制)有哪些?

4、线程如何中断?

5、线程有几种互斥同步方式? 如何选择?

6、线程之间的协作方式(通信和协调)?

下面我们 一 一 解读。

2 线程的状态和流转

image

2.1 新建(New)

如上图,创建完线程,但尚未启动。

2.2 可运行(Runnable)

如上图,处于可运行阶段,正在运行,或者正在等待 CPU 时间片。包含了 Running 和 Ready 两种线程状态。

2.3 阻塞(Blocking)

如上图,正被Lock住,等待获取一个排它锁,如果其他的线程释放了锁,该状态就会结束。

2.4 无限期等待(Waiting)

如上图,处在无限期等待阶段,等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。主要有两种方式进行释放:

  • 调用方的线程执行完成

  • 使用 Object.notify() / Object.notifyAll()进行显性唤醒

2.5 限期等待(Timed Waiting)

如上图,因为有时间控制,所以无需等待其它线程显式地唤醒,一定时间之后,系统会自动唤醒。所以他有三种方式进行释放:主要有两种方式进行释放:

  • 调用方的线程执行完成

  • 使用 Object.notify() / Object.notifyAll()进行显性唤醒

  • 时间到结束Thread.sleep()Object.wait() 方法,带Timeout参数Thread.join() 方法,带Timeout参数

2.6 死亡(Terminated)

  • 线程结束任务之后结束

  • 产生了异常并结束

3 线程实现方式

在Java中,线程的实现方式主要有两种:继承Thread类和实现Runnable接口。此外,Java 5开始,引入了java.util.concurrent包,提供了更多的并发工具,如Callable接口与Future接口,它们主要用于任务执行。

3.1 继承Thread类

通过继承Thread类来创建线程是最基本的方式。你需要创建一个扩展自Thread类的子类,并重写其run()方法。然后,可以创建该子类的实例来创建新的线程。

class MyThread extends Thread {
    public void run() {
        System.out.println("线程运行中");
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start(); // 调用start()方法来启动线程
    }
}

3.2 实现Runnable接口

另一种方式是让你的类实现Runnable接口,并实现run()方法。然后,你可以创建Thread类的实例,将实现了Runnable接口的类的实例作为构造参数传递给它。

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("线程运行中");
    }
}

public class RunnableDemo {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start(); // 调用start()方法来启动线程
    }
}

3.3 使用Callable和Future

虽然CallableFuture不是直接用于创建线程的,但它们提供了一种更灵活的方式来处理线程执行的结果。Callable类似于Runnable,但它可以返回一个结果,并且可以抛出异常。Future用于获取Callable执行的结果。

import java.util.concurrent.*;

class MyCallable implements Callable<String> {
    public String call() throws Exception {
        return "任务完成";
    }
}

public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = executor.submit(new MyCallable());
        System.out.println(future.get()); // 阻塞等待获取结果
        executor.shutdown();
    }
}

3.4 优缺点解读

  • 继承Thread类:简单直观,但Java不支持多重继承,如果类已经继承了其他类,则不能再用这种方式。另外继承整个 Thread 类开销过大,太重了。

  • 实现Runnable接口:更加灵活,推荐的方式。

  • Callable和Future:提供了更为强大的功能,例如返回执行结果和抛出异常,但通常用于与ExecutorService等高级并发工具一起使用。

4 线程管理机制

Java 中的线程管理机制非常强大,涵盖了从简单的线程创建到复杂的线程池管理等多个方面。

4.1 Executor 框架

Executor 框架是 Java 并发包(java.util.concurrent)中的一个关键组件,它提供了一种更高级别的抽象来管理线程池。通过使用 Executor,你可以更容易地控制线程的创建、执行、调度、生命周期等。它主要有三种类型:

  1. CachedThreadPool: 一个任务创建一个线程

  2. FixedThreadPool: 所有任务只能使用固定大小的线程

  3. SingleThreadExecutor: 单个线程,相当于大小为 1 的 FixedThreadPool。

  • 优点:提高程序性能和响应速度,通过复用线程来减少线程创建和销毁的开销,简化并发编程。

  • 使用示例

ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
    Runnable worker = new WorkerThread("" + i);
    executor.execute(worker);
}
executor.shutdown();

4.2 守护线程(Daemon Threads)

守护线程是一种特殊的线程,它主要用于程序中“后台”任务的支持。守护线程与普通线程的区别在于,当程序中所有非守护线程结束时,JVM 会自动退出,即使还有守护线程在运行。守护线程常用于垃圾回收、JVM 内部的监控等任务。设置守护线程:通过调用线程对象的 setDaemon(true) 方法,在启动线程之前将其设置为守护线程。

 Thread thread = new Thread(new MyRunnable());
 thread.setDaemon(true);

4.3 sleep() 方法

sleep() 方法是 Thread 类的一个静态方法,用于让当前正在执行的线程暂停执行指定的时间(毫秒),以毫秒为单位。在指定的时间过去后,线程将回到可运行状态,等待CPU的调度。

  • 用途:常用于线程间的简单同步。

  • 注意sleep() 方法不会释放锁(如果当前线程持有锁的话)。

  • 示例

 try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

4.4 yield() 方法

yield() 方法也是 Thread 类的一个静态方法,它告诉调度器当前线程愿意放弃当前处理器的使用,但这并不意味着线程会立即停止执行或进入等待/阻塞状态。调度器可以忽略这个提示,继续让当前线程运行。

  • 用途:提示调度器让出CPU时间,但具体是否让出取决于调度器的实现。

  • 注意yield() 方法不会使线程进入阻塞状态,也不会释放锁(如果持有的话),类似仅建议。

  • 示例

Thread.yield();

5 线程中断方式

在Java中,线程中断是一种重要的线程间通信机制,用于通知线程应该停止当前正在执行的任务。线程中断的方式主要有以下几种:

5.1 使用interrupt()方法

interrupt()方法是Java推荐的线程中断方式。它并不会直接停止线程,而是设置线程的中断状态为true。线程需要定期检查这个中断状态(通过isInterrupted()方法),并根据需要自行决定如何响应中断请求,比如退出循环、释放资源等。

  • 优点:安全、灵活,符合Java的并发编程理念。

  • 示例

Thread thread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 执行任务
    }
    // 线程中断后的清理工作
});
thread.start();
// 稍后中断线程
thread.interrupt()

5.2 使用Executor的中断操作

  1. 调用 Executor 的 shutdown() 方法,会等待线程都执行完毕之后再关闭

  2. 调用 Executor 的 shutdownNow() 方法,则相当于直接调用具体线程的 interrupt() 方法

6 线程互斥同步方式

Java中的线程互斥同步是并发编程中的一个重要概念,用于保证多个线程在访问共享资源时的互斥性,即同一时间只有一个线程能够访问某个资源。Java提供了多种机制来实现线程的互斥同步,主要包括以下几种方式:

6.1 synchronized关键字

1. 基本概念

synchronized是Java中最基本的同步机制,它可以用来修饰方法或代码块。当一个线程访问一个被synchronized修饰的方法或代码块时,其他试图访问该方法或代码块的线程将被阻塞,直到当前线程执行完毕释放锁。

2. 使用方法

  • 修饰方法:直接在方法声明上加上synchronized关键字,例如public synchronized void method() {...}

  • 修饰代码块:将需要同步的代码放在synchronized(对象) {...}中,这里的对象就是锁对象,例如synchronized(this) {...}synchronized(某个对象) {...}

3. 特性

  • 可见性:synchronized不仅保证了互斥性,还保证了变量的可见性。当一个线程释放锁时,会将锁变量的值刷新到主存储器中,从而使其他线程可以看到最新的变量值。

  • 可重入性:synchronized支持可重入性,即同一个线程可以多次获取同一个锁,而不会导致死锁。

4. 示例

public class Counter {  
    private int count = 0;  
  
    // synchronized修饰方法  
    public synchronized void increment() {  
        count++;  
    }  
  
    public synchronized int getCount() {  
        return count;  
    }  
}  
  
public class TestSynchronized {  
    public static void main(String[] args) throws InterruptedException {  
        Counter counter = new Counter();  
  
        Thread t1 = new Thread(() -> {  
            for (int i = 0; i < 10; i++) {  
                counter.increment();  
            }  
        });  
  
        Thread t2 = new Thread(() -> {  
            for (int i = 0; i < 10; i++) {  
                counter.increment();  
            }  
        });  
  
        t1.start();  
        t2.start();  
  
        t1.join();  
        t2.join();  
  
        System.out.println("Final count: " + counter.getCount());  
    }  
}

6.2 ReentrantLock类

  • 基本概念:ReentrantLock是java.util.concurrent.locks包中的一个可重入锁,它提供了比synchronized更灵活的锁定机制。

  • 使用方法:创建锁对象:ReentrantLock lock = new ReentrantLock();加锁:lock.lock();释放锁:通常将释放锁的代码放在finally块中,以确保锁一定会被释放,例如try {...} finally { lock.unlock(); }

  • 特性:支持公平锁和非公平锁:通过构造器参数可以指定使用哪种锁,默认是非公平锁。支持尝试获取锁:提供了tryLock()等方法,尝试获取锁,如果获取不到则不会阻塞线程。支持中断锁定的线程:与synchronized不同,ReentrantLock的锁可以被中断。

import java.util.concurrent.locks.ReentrantLock;  
  
public class CounterWithLock {  
    private int count = 0;  
    private final ReentrantLock lock = new ReentrantLock(); // 创建ReentrantLock对象  
  
    public void increment() {  
        lock.lock(); // 加锁  
        try {  
            count++;  
        } finally {  
            lock.unlock(); // 释放锁,放在finally块中确保一定会被释放  
        }  
    }  
  
    public int getCount() {  
        lock.lock(); // 加锁  
        try {  
            return count;  
        } finally {  
            lock.unlock(); // 释放锁  
        }  
    }  
}  
  
public class TestReentrantLock {  
    public static void main(String[] args) throws InterruptedException {  
        CounterWithLock counter = new CounterWithLock();  
  
        Thread t1 = new Thread(() -> {  
            for (int i = 0; i < 10000; i++) {  
                counter.increment();  
            }  
        });  
  
        Thread t2 = new Thread(() -> {  
            for (int i = 0; i < 10000; i++) {  
                counter.increment();  
            }  
        });  
  
        t1.start();  
        t2.start();  
  
        t1.join();  
        t2.join();  
  
        System.out.println("Final count: " + counter.getCount());  
    }  
}

6.3 对比

对于大多数简单场景,synchronized关键字是最直接、最简单的选择;而对于需要更灵活控制锁的场景,则可以考虑使用ReentrantLock等高级同步机制。

7 线程协作(通信)方案

Java中线程之间的协作主要可以通过多种机制实现,其中等待/通知机制(wait/notify/notifyAll)和join方法是两种常用的方式。下面我将分别给出这两种方式的简单代码示例。

7.1 等待/通知机制(wait/notify/notifyAll)

等待/通知机制依赖于Java中的Object类,因为wait()notify(), 和 notifyAll() 方法都定义在Object类中。这些方法必须在同步块或同步方法中被调用,因为它们是用来控制对某个对象的访问的。

示例代码

public class WaitNotifyExample {
    private final Object lock = new Object();
    private boolean ready = false;

    public void doWait() {
        synchronized (lock) {
            while (!ready) {
                try {
                    lock.wait(); // 当前线程等待
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // 保持中断状态
                }
            }
            // 当ready为true时,继续执行
        }
    }

    public void doNotify() {
        synchronized (lock) {
            ready = true;
            lock.notify(); // 唤醒在此对象监视器上等待的单个线程
            // 或者使用 lock.notifyAll(); 唤醒所有等待的线程
        }
    }

    public static void main(String[] args) {
        WaitNotifyExample example = new WaitNotifyExample();

        Thread t1 = new Thread(() -> {
            System.out.println("Thread 1 is waiting");
            example.doWait();
            System.out.println("Thread 1 is proceeding");
        });

        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(1000); // 假设t2需要一些时间来完成准备工作
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("Thread 2 is notifying");
            example.doNotify();
        });

        t1.start();
        t2.start();
    }
}

在这个例子中,t1线程在doWait()方法中等待,直到t2线程调用doNotify()方法并设置readytruet2线程模拟了一些准备工作,并在之后唤醒t1

7.2 Join 方法

join方法是Thread类的一个方法,用于让当前线程等待另一个线程完成其执行。

示例代码

public class JoinExample {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000); // 假设t1执行需要一些时间
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            System.out.println("Thread 1 completed");
        });

        t1.start();

        try {
            t1.join(); // 当前线程(main线程)等待t1完成
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        System.out.println("Thread 1 has joined, continuing main thread");
    }
}

在这个例子中,main线程启动了一个新线程t1,并通过调用t1.join()等待t1完成。t1线程在完成后会打印一条消息,而main线程会在t1完成后继续执行并打印另一条消息。

文章转载自:Hello-Brand

原文链接:https://www.cnblogs.com/wzh2010/p/15886701.html

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

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

相关文章

数据资产入表元年,企业如何抓住数据资产增值的机遇?

近年来&#xff0c;政府将数据要素纳入了经济发展的重要指示性文件当中&#xff0c;希望利用数据驱动。《全国数据资源调查报告&#xff08;2023年&#xff09;》显示&#xff0c;2024年以来不少地方纷纷成立“数据集团”&#xff0c;加快盘活数据资产。作为数字经济时代的首要…

Java | Leetcode Java题解之第389题找不同

题目&#xff1a; 题解&#xff1a; class Solution {public char findTheDifference(String s, String t) {int ret 0;for (int i 0; i < s.length(); i) {ret ^ s.charAt(i);}for (int i 0; i < t.length(); i) {ret ^ t.charAt(i);}return (char) ret;} }

编写Dockerfile第二版

目标 更快的构建速度 更小的Docker镜像大小 更少的Docker镜像层 充分利用镜像缓存 增加Dockerfile可读性 让Docker容器使用起来更简单 总结 编写.dockerignore文件 容器只运行单个应用 将多个RUN指令合并为一个 基础镜像的标签不要用latest 每个RUN指令后删除多余文…

leetcode172. 阶乘后的零,遍历每个因数中5的个数

leetcode172. 阶乘后的零 给定一个整数 n &#xff0c;返回 n! 结果中尾随零的数量。 提示 n! n * (n - 1) * (n - 2) * … * 3 * 2 * 1 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;0 解释&#xff1a;3! 6 &#xff0c;不含尾随 0 示例 2&#xff1a; 输…

第八届控制工程与先进算法国际论坛(IWCEAA 2024)

重要信息 会议大会官网&#xff1a;www.iwaace.cn&#xff08;点击进一步了解&#xff09; 二轮截稿时间&#xff1a;2024年09月27日 录用通知时间&#xff1a;投稿后7个工作日 收录检索类型&#xff1a;IEEE Xplore, EI, Scopus 大会介绍 第八届控制工程与先进算法国际论…

python 交互模式怎么切换目录

假如要用交互界面调用一个.py文件&#xff1a; &#xff08;1&#xff09;用cmd界面定位到文件位置&#xff0c;如cd Desktop/data/ #进入desktop下data目录。 &#xff08;2&#xff09;接着打开python&#xff08;输入python&#xff09; 调用os &#xff08;1&#xff0…

3.js - modelPosition.z = sin((modelPosition.x+uTime)*10.0)*0.05;

哈 1、完整的动态波纹效果吧 main.js import * as THREE from three import { OrbitControls } from three/examples/jsm/controls/OrbitControls// 顶点着色器 import basicVertexShader from ./shader/11-01/raw/vertex.glsl?raw // 片元着色器 import basicFragmentShad…

python OpenGL绘制正八面体代码

学习OpenGL过程中&#xff0c;先从绘制三角形开始逐渐对Opengl有一些了解&#xff0c;到现在进阶到绘制立方体。通过借鉴网友绘制六面体得到灵感&#xff0c;改了一下他的代码成功绘制出正八面体。不管是绘制三角形还是绘制立方体&#xff0c;对于空间坐标系要相当熟悉&#xf…

苍穹外卖项目前端DAY03

前端DAY03 1、套餐管理 1.1、套餐分页查询 1.1.1、需求分析和接口设计 产品原型&#xff1a; 业务规则&#xff1a; 根据页码展示套餐信息每页展示10条数据分页查询时可以根据需要&#xff0c;输入套餐名称、套餐分类、售卖状态进行查询 接口设计&#xff1a; 套餐分页查…

如何使用电商API接口?(淘宝|京东商品详情数据接口)

一、了解电商API接口&#xff1a; 如今&#xff0c;在电商市场中&#xff0c;电商API接口的广泛应用极大地提高了电商行业的工作效率&#xff0c;使得商家能够灵活集成多种服务&#xff0c;高效优化业务流程。 当前&#xff0c;电商平台中的多种业务都可以通过使用API接口来做…

Tomato靶场渗透测试

1.扫描靶机地址 可以使用nmap进行扫描 由于我这已经知道靶机地址 这里就不扫描了 2.打开网站 3.进行目录扫描 dirb http&#xff1a;//172.16.1.113 发现有一个antibot_image目录 4.访问这个目录 可以看到有一个info.php 5.查看页面源代码 可以发现可以进行get传参 6.…

脉脉高聘:大模型算法岗平均月薪近7万元,位居高薪榜第一

9月5日&#xff0c;脉脉高聘人才智库数据显示&#xff0c;大模型领域整体供需比1.76&#xff0c;整体供大于求。同时&#xff0c;高技术岗位人才稀缺&#xff0c;云计算人才供需比仅为0.33&#xff0c;相当于3个岗位争夺1个人才。大模型算法岗位平均月薪最高&#xff0c;超过6.…

【STM32+HAL库】---- 驱动MAX30102心率血氧传感器

硬件开发板&#xff1a;STM32F407VET6 软件平台&#xff1a;cubemaxkeilVScode1 MAX30102心率血氧传感器工作原理 MAX30102传感器是一种集成了红外光源、光电检测器和信号处理电路的高度集成传感器&#xff0c;主要用于心率和血氧饱和度的测量。以下是MAX30102传感器的主要特点…

草料二维码功能上新!可以跨分区移动或复制内容了!

支持将 分区 下的内容移动或复制到其他分区。适用于将原初始分区下的内容按业务划分&#xff0c;移动到其他分区。或者当物品流转或业务变更时&#xff0c;可以及时将码及数据移动到对应分区&#xff0c;移动后&#xff0c;二维码图案不会发生变化。 目前仅表单、活码、批量模…

在修改文件 /ect/hosts时无法保存 can‘t open file for writing

输入&#xff1a;q! 即可 情境&#xff1a; 在Master节点中执行如下命令打开并修改Master节点中的“/etc/hosts”文件&#xff1a; sudo vim /etc/hosts 可以在hosts文件中增加如下两条IP和主机名映射关系&#xff1a; 192.168.1.121 Master 192.168.1.122 Slave1

解决App推广痛点:一键获取下载数据的秘诀

在App推广的过程中&#xff0c;获取准确的下载数据一直是一个令人头疼的问题。你知道吗&#xff1f;无法精确追踪用户来源和下载量&#xff0c;就像是在黑暗中摸索&#xff0c;让推广效果大打折扣。今天&#xff0c;我们就来揭秘如何轻松获取App下载数据&#xff0c;优化你的推…

BaseThreadStart代码分析

BaseThreadStart代码分析 第一部分&#xff1a; ​​​​​​​ 在调用CreateThead创建线程的时候&#xff0c;操作系统会为新线程创建线程内核对想象&#xff0c; 线程内核对象包含了线程的上下文&#xff08;是一个C O N T E X T结构&#xff09;以及一些其他属性和统计信息&…

计算机毕业设计 | SSM停车场管理系统(附源码)

1&#xff0c; 概述 1.1 课题背景 随着社会的快速发展&#xff0c;计算机的影响是全面且深入的。人们的生活水平不断提高&#xff0c;日常生活中用户对停车场管理系统方面的要求也在不断提高&#xff0c;需要的人数更是不断增加&#xff0c;使得停车场管理系统的开发成为必需…

【HarmonyOS】安装包报错,code:9568282 error: install releaseType target not same.

【HarmonyOS】安装包报错&#xff0c;code:9568282 error: install releaseType target not same. 报错信息 Install Failed: error: failed to install bundle. code:9568282 error: install releaseType target not same. You can also uninstall and reinstall the module…

使用Python读取Excel数据

目录 使用Python读取Excel数据 安装必要的库 读取Excel文件 基本步骤 代码案例 解释 其他常用操作 选择特定列 筛选数据 数据清洗 总结 使用Python读取Excel数据 在日常的数据处理工作中&#xff0c;Excel文件是非常常见的一种数据格式。Python提供了多种库来读取和…