Java基础【多线程】

什么是线程

线程(Thread)是计算机科学中的一个重要概念,指的是在单个程序内部同时执行的一条独立的指令序列。简而言之,线程就是在一个进程内部并发执行的一段代码。每个线程都有自己的执行路径,可以独立地执行代码,访问内存和资源。

在操作系统中,一个进程可以包含多个线程,这些线程共享进程的资源,如内存空间、文件句柄等。相比于多个进程之间的通信和同步机制复杂度高,线程之间的通信和同步相对简单,因为它们可以直接访问共享的内存空间。

线程如何使用

创建线程并启动:

public class MyThread extends Thread {public void run() {System.out.println("线程执行中");}public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start(); // 启动线程}
}

线程休眠(sleep):

public class SleepExample {public static void main(String[] args) {System.out.println("开始");try {Thread.sleep(2000); // 休眠2秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("结束");}
}

线程等待:

public class WaitExample {public static void main(String[] args) {final Object lock = new Object();Thread thread1 = new Thread(() -> {synchronized (lock) {System.out.println("线程1开始等待");try {lock.wait(); // 线程1等待} catch (InterruptedException e) {e.printStackTrace();}System.out.println("线程1被唤醒");}});Thread thread2 = new Thread(() -> {synchronized (lock) {System.out.println("线程2开始唤醒");lock.notify(); // 唤醒等待的线程}});thread1.start();thread2.start();}
}

线程中断

public class InterruptExample {public static void main(String[] args) {Thread thread = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("线程执行中");}System.out.println("线程被中断");});thread.start();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}thread.interrupt(); // 中断线程}
}

继承 Thread类 和 实现Runnable接口的区别

继承Thread类:

  1. 当一个类继承Thread类时,该类就成为一个线程类,直接重写run()方法来定义线程执行的逻辑。
  2. 缺点是Java不支持多重继承,如果继承了Thread类,就无法再继承其他类。
  3. 适用于简单的线程逻辑,不需要共享数据。
public class MyThread extends Thread {public void run() {System.out.println("线程执行中");}public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start(); // 启动线程}
}

实现Runnable接口:

  1. 当一个类实现Runnable接口时,可以在这个类中实现线程执行的逻辑,在run()方法中定义线程的行为。
  2. 可以避免Java单继承的限制,因为一个类可以实现多个接口。
  3. 适用于需要共享数据或资源的多线程场
public class MyRunnable implements Runnable {public void run() {System.out.println("线程执行中");}public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable();Thread thread = new Thread(myRunnable);thread.start(); // 启动线程}
}

线程的生命周期

新建(New):当一个线程对象被创建但还没有开始运行时,处于新建状态。

运行(Runnable):通过调用 start() 方法,线程进入可运行状态。此时线程可能正在运行,也可能正在等待系统资源。

阻塞(Blocked):线程在某些条件下会进入阻塞状态,比如等待I/O操作完成或者获取锁资源。

无限期等待(Waiting):线程进入无限期等待状态时,它会一直等待直到其他线程通知或中断它。

限期等待(Timed Waiting):线程在限期等待状态下会等待一定的时间,超时后会自动恢复到可运行状态。

终止(Terminated):线程执行完任务或者出现异常导致线程终止,进入终止状态。

线程的同步

线程同步是指多个线程之间协调它们的操作顺序,以确保数据的一致性和避免竞争条件。在多线程编程中,如果多个线程同时访问共享资源,就会出现竞争条件,可能导致数据不一致或者错误的结果。因此,需要使用同步机制来确保线程安全。

常见的线程同步机制包括:

互斥锁(Mutex):通过对共享资源加锁的方式,保证同一时间只有一个线程能够访问共享资源,其他线程需要等待锁的释放。
信号量(Semaphore):用于控制同时访问共享资源的线程数量,可以设置一个计数器来限制资源的访问。
条件变量(Condition Variable):用于线程间的通信和协调,允许线程等待某个条件的发生。
读写锁(Read-Write Lock):允许多个线程同时读取共享资源,但在有写操作时需要互斥排斥访问。
临界区(Critical Section):用于限制对共享资源的访问,确保在同一时间只有一个线程可以执行临界区代码

线程如何同步

synchronized关键字:通过在方法中使用synchronized关键字或者synchronized代码块,可以确保多个线程对共享资源的访问是同步的。当一个线程获取了对象的锁之后,其他线程必须等待该线程释放锁才能继续执行。

public synchronized void synchronizedMethod() {// 同步方法
}

// 或者

public void someMethod() {synchronized(this) {// 同步代码块}
}

Lock接口: Java中的java.util.concurrent.locks包提供了Lock接口及其实现类,如ReentrantLock。与synchronized不同,Lock接口提供了更灵活的锁定机制,包括可重入锁、公平锁等。

Lock lock = new ReentrantLock();lock.lock();
try {// 同步代码块
} finally {lock.unlock();
}

volatile关键字:在Java中,volatile关键字用于声明变量,保证了变量的可见性,并禁止指令重排序优化。尽管volatile关键字不能取代锁机制,但它可以在特定情况下用来确保共享变量的同步访问。

private volatile boolean flag = false;

Synchronize

使用 synchronized 关键字来实现线程同步,确保多个线程访问共享资源时的安全性。通过在方法上或代码块中添加
synchronized 关键字,可以将方法或代码块标记为同步的,这样只有一个线程能够访问该方法或代码块,其他线程需要等待

public class SynchronizedExample {private int count = 0;public synchronized void increment() {count++;}public static void main(String[] args) {SynchronizedExample syncExample = new SynchronizedExample();// 创建两个线程分别对 count 执行增加操作Thread thread1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {syncExample.increment();}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {syncExample.increment();}});thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Final Count: " + syncExample.getCount());}public synchronized int getCount() {return count;}
}

synchronized 关键字:

特点:synchronized 是 Java 语言提供的内置机制,可以用来实现线程同步,通过对代码块或方法加锁来确保同一时间只有一个线程可以执行该代码块或方法。
优点:简单易用,不需要显式地创建锁对象;可以确保每次只有一个线程访问共享资源。
缺点:灵活性较差,只能实现基本的互斥同步,不能支持尝试获取锁、超时等特性。
ReentrantLock 类:

特点:ReentrantLock 是 Lock 接口的实现类,提供了比 synchronized 更灵活的锁定机制,可以支持可重入锁、公平性设置、尝试获取锁、超时获取锁等功能。
优点:比 synchronized 更灵活,可以支持更多高级特性;可以避免死锁情况。
缺点:使用复杂一些,需要手动释放锁,并且需要注意异常处理。

AtomicInteger 类:

特点:AtomicInteger 是 java.util.concurrent.atomic 包下的原子操作类,提供了一种线程安全的整数类型,支持原子性的增减操作。
优点:适用于简单的原子操作,不需要显式加锁,性能较好。
缺点:只适用于特定类型的原子操作,不适用于复杂的同步场景。

互斥锁

互斥锁(Mutex
Lock)是一种用于确保在多线程环境中不会同时执行特定代码段的同步机制。当一个线程获得了互斥锁时,其他线程就无法再获取该锁,直到持有锁的线程释放它为止。
一般情况下,使用互斥锁的基本流程如下:

当一个线程希望访问共享资源时,它尝试获取互斥锁。
如果互斥锁已经被其他线程持有,则当前线程将被阻塞,直到互斥锁被释放。
一旦获取了互斥锁,线程就可以安全地访问共享资源。
当线程不再需要访问共享资源时,它会释放互斥锁,以便其他线程可以获取并访问共享资源。

当使用synchronized关键字或ReentrantLock类时,都可以实现互斥锁

使用synchronized关键字实现互斥锁:

public class SynchronizedMutexExample {private static int counter = 0;public static void main(String[] args) {Object lock = new Object(); // 创建一个对象作为锁Thread thread1 = new Thread(() -> {synchronized (lock) { // 使用lock对象进行同步for (int i = 0; i < 1000; i++) {counter++;}}});Thread thread2 = new Thread(() -> {synchronized (lock) { // 使用相同的lock对象进行同步for (int i = 0; i < 1000; i++) {counter++;}}});thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Counter: " + counter);}
}

ps: 在使用synchronized关键字时,当线程执行完同步代码块或同步方法后,会自动释放对象锁。在synchronized块或方法结束时,系统会自动释放锁,不需要显式调用解锁的操作,这是synchronized关键字的特性之一。

使用ReentrantLock类实现互斥锁:

import java.util.concurrent.locks.ReentrantLock;public class ReentrantLockMutexExample {private static int counter = 0;private static ReentrantLock lock = new ReentrantLock(); // 创建ReentrantLock对象作为锁public static void main(String[] args) {Thread thread1 = new Thread(() -> {lock.lock(); // 获取锁try {for (int i = 0; i < 1000; i++) {counter++;}} finally {lock.unlock(); // 释放锁}});Thread thread2 = new Thread(() -> {lock.lock(); // 获取锁try {for (int i = 0; i < 1000; i++) {counter++;}} finally {lock.unlock(); // 释放锁}});thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Counter: " + counter);}
}

线程死锁

如果不释放锁,将导致其他线程无法获取该锁,从而造成死锁。当一个线程持有锁并且不释放锁时,其他线程就无法进入同步代码块或方法,这可能会导致所有线程都在等待获取锁而无法继续执行,最终导致程序被阻塞。

死锁是多线程编程中常见的问题,应该尽量避免。因此,一定要确保在合适的时机释放锁,以允许其他线程获得锁并执行同步代码块,从而避免死锁情况的发生。

下面我们模拟一下线程死锁:

public class DeadlockExample {private static Object lock1 = new Object();private static Object lock2 = new Object();public static void main(String[] args) {// 线程1尝试获取lock1,然后尝试获取lock2Thread thread1 = new Thread(() -> {synchronized (lock1) {System.out.println("Thread 1: Holding lock 1...");try {Thread.sleep(100); // 为了让线程2有机会获取lock2} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 1: Waiting for lock 2...");synchronized (lock2) {System.out.println("Thread 1: Holding lock 1 and lock 2...");}}});// 线程2尝试获取lock2,然后尝试获取lock1Thread thread2 = new Thread(() -> {synchronized (lock2) {System.out.println("Thread 2: Holding lock 2...");try {Thread.sleep(100); // 为了让线程1有机会获取lock1} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 2: Waiting for lock 1...");synchronized (lock1) {System.out.println("Thread 2: Holding lock 1 and lock 2...");}}});// 启动两个线程thread1.start();thread2.start();}
}

如何避免线程死锁

避免线程持有多个锁:
线程在持有一个锁的同时,尝试获取另一个锁时容易导致死锁。尽量设计避免一个线程同时持有多个锁的情况。

按固定的顺序获取锁:
多个线程都按照相同的顺序获取共享资源的锁,可以避免循环等待的情况,从而避免死锁的发生。

设置超时时间:
在获取锁的过程中设置超时时间,如果超过一定时间还未获取到锁,就放弃并释放已经获取的锁,避免长时间等待导致死锁。

使用并发工具类:
Java提供了一些并发工具类,如java.util.concurrent包下的工具类,可以更方便地管理线程之间的资源竞争,避免死锁。

避免嵌套锁:
尽量避免在持有一个锁的情况下再去申请另一个锁,这样容易导致死锁的发生。

良好的代码设计:
在编写多线程程序时,要注意良好的代码设计,避免复杂的线程交互关系,减少出现死锁的可能性。

使用同步块代替同步方法:
在对共享资源进行操作时,尽量使用同步块而不是同步方法,这样可以更灵活地控制锁的获取顺序,降低死锁的风险。

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

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

相关文章

小米汽车正式发布:开启智能电动新篇章

随着科技的不断进步&#xff0c;汽车产业正经历着前所未有的变革。智能电动汽车作为这一变革的重要方向&#xff0c;正吸引着越来越多的目光。在这个充满机遇和挑战的时代&#xff0c;小米汽车凭借其卓越的技术实力和深厚的市场底蕴&#xff0c;终于迈出了坚实的一步。今天&…

Exception in thread “main“ com.fasterxml.jackson.databind.JsonMappingException:

问题&#xff1a;jaskson反序列化超出最大长度 Caused by: com.fasterxml.jackson.core.exc.StreamConstraintsException: String length (5043456) exceeds the maximum length (5000000) 场景&#xff1a;前端传递过大base64 原因&#xff1a; jaskon默认已经限制了最大长…

免费|Python|【需求响应】一种新的需求响应机制DR-VCG研究

目录 1 主要内容 2 部分代码 3 程序结果 4 下载链接 1 主要内容 该程序对应文章《Contract Design for Energy Demand Response》&#xff0c;电力系统需求响应&#xff08;DR&#xff09;用来调节用户对电能的需求&#xff0c;即在预测的需求高于电能供应时&#xff0c;希…

chrome 控制台不能粘贴

描述&#xff1a;chrome 控制台不能粘贴 解决方案&#xff1a;setting-Experiments-Filter中输入past&#xff0c;取消勾选

国际伦敦金行情分析中的趋势分析方法

国际伦敦金行情走势复杂多变。近期&#xff0c;金价曾经一度刷新历史的新高点至2222&#xff0c;但就在当天&#xff0c;金价又快速下跌跌超过30美元。不过这么多变的伦敦金行情也为我们的交易创造了空间&#xff0c;有空间就等于有机会&#xff0c;只要我们能够掌握国际伦敦金…

C# 多态 派生类 abstract virtual new

静态多态函数重载运算符重载 动态多态abstract 和 virtual的区别定义与用途&#xff1a;成员实现&#xff1a;继承与重写&#xff1a;与接口的区别&#xff1a; 使用抽象类的好处主要体现在以下几个方面&#xff1a;代码重用&#xff1a;设计灵活性&#xff1a;接口定义&#x…

HCIP作业3

第一步 给PC1配置&#xff1a; 先给PC2配置&#xff1a; 第二部 给R1的接口配置ip [R1]int g0/0/0 [R1-GigabitEthernet0/0/0]ip add 192.168.1.252 24 [R1-GigabitEthernet0/0/0]int g0/0/1 [R1-GigabitEthernet0/0/1]ip ad 100.1.1.1 24 给R2的接口配置IP [R2]int g0/0/…

Java:接口应用(Comparable接口与比较器)

目录 1.引例2.Comparable接口使用3.Comparable接口的局限性4.使用comparaTo实现排序5.比较器&#xff08;Comparator接口&#xff09; 1.引例 class Student{private String name;private int age;public Student(String name, int age) {this.name name;this.age age;} } p…

#孩子学习编程的目的是什么?#

谢谢今日头条编辑的盛情邀请&#xff0c;对此&#xff0c;我发表自己的一点拙见。 首先&#xff0c;最重要的一点&#xff0c;学习编程可以练习打字&#xff0c;加强拼音输入法的掌握。对于孩子的语文成绩有一定的提高帮助&#xff0c;也为将来走上工作熟练使用电脑输入法打下扎…

Linux的学习之路:3、基础指令(2)

一、echo指令 这个指令在上篇文章我也用了但是忘了说了&#xff0c;这个指令的大概用法就是把后面跟的文本等输出在显示器上&#xff0c;如下代码所示打印的“Hello Linux” [rootVM-24-9-centos ~]# echo "Hello Linux" Hello Linux二、输出重定向与输入重定向 着…

vue页面实现左右div宽度,上下div高度分割线手动拖动高度或者宽度自动变化,两个div宽度或者高度拉伸调节,实现左右可拖动改变宽度的div内容显示区

实现左右或者上下div两部分拖动&#xff0c;宽度或者高度自动变化,实现流畅平滑的变化&#xff0c;还可以是实现拖动到一定宽度就不让拖动了&#xff0c;如果你不需要最小宽度&#xff0c;就直接去掉样式就行 这是页面。分左中右三部分&#xff0c;中间我是用来作为拖动的按钮…

Java实现猜数字游戏:编程入门之旅

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

Java研学-SpringBoot(三)

五 Spring Boot 自动配置原理 1 概念 springboot的自动装配就是从spring.factories文件中获取到对应的需要进行自动装配的类&#xff0c;并生成相应的Bean对象&#xff0c;然后将它们交给spring容器来帮我们进行管理。核心注解&#xff1a;SpringBootApplication   调用main…

使用 Yoda 和 ClickHouse 进行实时欺诈检测

背景 Instacart 是北美领先的在线杂货公司,拥有数百万活跃的客户和购物者。在其平台上打击欺诈和滥用行为不仅对于维护一个值得信赖和安全的环境至关重要,也对保持Instacart的财务健康至关重要。在这篇文章中,将介绍了一个欺诈平台——Yoda,解释了为什么我们选择ClickHous…

奖学金NOIP2007 普及组 T1

题目背景 NOIP2007 普及组 T1 题目描述 某小学最近得到了一笔赞助&#xff0c;打算拿出其中一部分为学习成绩优秀的前 55 名学生发奖学金。期末&#xff0c;每个学生都有 33 门课的成绩&#xff1a;语文、数学、英语。先按总分从高到低排序&#xff0c;如果两个同学总分相同…

Linux 反引号、单引号以及双引号的区别

1.单引号—— 单引号中所有的字符包括特殊字符&#xff08;$,,和\&#xff09;都将解释成字符本身而成为普通字符。它不会解析任何变量&#xff0c;元字符&#xff0c;通配符&#xff0c;转义符&#xff0c;只被当作字符串处理。 2.双引号——" 双引号&#xff0c;除了$,…

Android 14.0 SystemUI下拉状态栏增加响铃功能

1.概述 在14.0的系统产品rom定制化开发中,在对systemui的状态栏开发中,对SystemUI下拉状态栏的QuickQSPanel区域有快捷功能键开关,对于增加各种响铃快捷也是常用功能, 有需要增加响铃功能开关功能,接下来就来分析SystemUI下拉状态栏QuickQSPanel快捷功能键开关的相关源码…

API接口开发亚马逊国际获得AMAZON商品详情api采集商品详情页实时数据、实时销量、库存等参数接入演示

要获取亚马逊商品的实时数据、销量和库存等信息&#xff0c;您需要使用亚马逊提供的一些API接口。以下是一些可能的步骤&#xff1a; 注册key账号&#xff1a;首先&#xff0c;您需要注册一个账号。这将允许您访问亚马逊的各种API。 创建一个新的应用&#xff1a;在您的开发者…

AI智能分析网关智慧食安监管系统方案

3.15晚会刚过不久&#xff0c;淀粉肠的“屈辱”终于得以洗清&#xff0c;但某些品牌奶茶、梅菜扣肉、预制菜等等&#xff0c;生产过程仍是触目惊心。如何提升食品安全管理水平&#xff0c;保障食品从生产到消费环节的质量和安全&#xff1f;TSINGSEE青犀智利用智能分析网关V4Ea…

【图像合成】基于DCGAN典型网络的MNIST字符生成(pytorch)

关于 近年来&#xff0c;基于卷积网络&#xff08;CNN&#xff09;的监督学习已经 在计算机视觉应用中得到了广泛的采用。相比之下&#xff0c;无监督 使用 CNN 进行学习受到的关注较少。在这项工作中&#xff0c;我们希望能有所帮助 缩小了 CNN 在监督学习和无监督学习方面的成…