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,然后尝试获取lock2
        Thread 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,然后尝试获取lock1
        Thread 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.mfbz.cn/a/496820.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

缓存雪崩问题及解决思路

实战篇Redis 2.7 缓存雪崩问题及解决思路 缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机&#xff0c;导致大量请求到达数据库&#xff0c;带来巨大压力。 解决方案&#xff1a; 给不同的Key的TTL添加随机值利用Redis集群提高服务的可用性给缓存业务添加降…

Requests教程-20-sign签名认证

领取资料&#xff0c;咨询答疑&#xff0c;请➕wei: June__Go 上一小节中&#xff0c;我们学习了requests的token认证方法&#xff0c;本小节我们讲解一下requests的sign签名认证。 在进行接口调用时&#xff0c;一般会要求进行签名操作&#xff0c;以确保接口的安全性和完整…

基于springboot实现课程作业管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现课程作业管理系统演示 摘要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;课程作业管理系统当然也不能排除在外。课程作业管理系统是以实际运用为开发背景…

项目实战:电影评论情感分析系统

目录 1.引言 2.数据获取与预处理 3.构建文本分类模型&#xff08;使用LSTM&#xff09; 4.结果评估与模型优化 4.2.结果评估 4.2.模型优化 5.总结 1.引言 在本篇文章中&#xff0c;将通过一个完整的项目实战来演示如何使用Python构建一个电影评论情感分析系统。涵盖从数…

OSCP靶场--pyLoader

OSCP靶场–pyLoader 考点(信息收集CVE-2023-0297) 1.nmap扫描 ┌──(root㉿kali)-[~/Desktop] └─# nmap -Pn -sC -sV 192.168.178.26 --min-rate 2500 Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-28 09:14 EDT Nmap scan report for 192.168.178.26 Host is up…

二分图

数据结构、算法总述&#xff1a;数据结构/算法 C/C-CSDN博客 二分图&#xff1a;节点由两个集合组成&#xff0c;且两个集合内部没有边的图。换言之&#xff0c;存在一种方案&#xff0c;将节点划分成满足以上性质的两个集合。 染色法 目的&#xff1a;验证给定的二分图是否可…

房间采光不好怎么改造?这里有6个实用的解决方案!福州中宅装饰,福州装修

当装修中遇到房间采光不好的问题&#xff0c;可以从以下几个方面来解决&#xff1a; ①引入自然光源 尽可能减少光线阻碍物&#xff0c;例如可以考虑打通一些非承重墙&#xff0c;扩大窗户的面积&#xff0c;让阳光直接穿过阳台照射到室内。同时&#xff0c;也可以考虑在某些没…

YOLOV8逐步分解(2)_DetectionTrainer类初始化过程

接上篇文章yolov8逐步分解(1)--默认参数&超参配置文件加载继续讲解。 1. 默认配置文件加载完成后&#xff0c;创建对象trainer时&#xff0c;需要从默认配置中获取类DetectionTrainer初始化所需的参数args&#xff0c;如下所示 def train(cfgDEFAULT_CFG, use_pythonFalse…

17.注释和关键字

文章目录 一、 注释二、关键字class关键字 我们之前写的HelloWorld案例写的比较简单&#xff0c;但随着课程渐渐深入&#xff0c;当我们写一些比较难的代码时&#xff0c;在刚开始写完时&#xff0c;你知道这段代码是什么意思&#xff0c;但是等过了几天&#xff0c;再次看这段…

图片标注编辑平台搭建系列教程(3)——画布拖拽、缩放实现

简介 标注平台很关键的一点&#xff0c;对于整个图片为底图的画布&#xff0c;需要支持缩放、拖拽&#xff0c;并且无论画布位置在哪里&#xff0c;大小如何&#xff0c;所有绘制的点、线、面的坐标都是相对于图片左上角的&#xff0c;并且&#xff0c;拖拽、缩放&#xff0c;…

从零开始学习在VUE3中使用canvas(六):线条样式(线条宽度lineWidth,线条端点样式lineCap)

一、线条宽度lineWidth 1.1简介 值为一个数字 const ctx canvas.getContext("2d"); ctx.lineWidth 6; 1.2效果展示 1.3全部代码 <template><div class"canvasPage"><!-- 写一个canvas标签 --><canvas class"main" r…

图像处理与视觉感知---期末复习重点(5)

文章目录 一、膨胀与腐蚀1.1 膨胀1.2 腐蚀 二、开操作与闭操作 一、膨胀与腐蚀 1.1 膨胀 1. 集合 A A A 被集合 B B B 膨胀&#xff0c;定义式如下。其中集合 B B B 也称为结构元素&#xff1b; ( B ^ ) z (\hat{B})z (B^)z 表示 B B B 的反射平移 z z z 后得到的新集合。…

冥想打坐睡觉功法

睡觉把手机放远一点&#xff0c;有电磁辐射&#xff0c;我把睡觉功法交给你&#xff0c;这样就可以睡好了。

55、Qt/事件机制相关学习20240326

一、代码实现设置闹钟&#xff0c;到时间后语音提醒用户。示意图如下&#xff1a; 代码&#xff1a; #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget), speecher(new QTextToSpeech(t…

C++超市商品管理系统

一、简要介绍 1.本项目为面向对象程序设计的大作业&#xff0c;基于Qt creator进行开发&#xff0c;Qt框架版本6.4.1&#xff0c;编译环境MINGW 11.2.0。 2.项目结构简介&#xff1a;关于系统逻辑部分的代码的头文件在head文件夹中&#xff0c;源文件在s文件夹中。与图形界面…

权限提升-Win系统权限提升篇AD内网域控NetLogonADCSPACKDCCVE漏洞

知识点 1、WIN-域内用户到AD域控-CVE-2014-6324 2、WIN-域内用户到AD域控-CVE-2020-1472 3、WIN-域内用户到AD域控-CVE-2021-42287 4、WIN-域内用户到AD域控-CVE-2022-26923 章节点&#xff1a; 1、Web权限提升及转移 2、系统权限提升及转移 3、宿主权限提升及转移 4、域控权…

Git命令上传本地项目至github

记录如何创建个人仓库并上传已有代码至github in MacOS环境 0. 首先下载git 方法很多 这里就不介绍了 1. Github Create a new repository 先在github上创建一个空仓库&#xff0c;用于一会儿链接项目文件&#xff0c;按照自己的需求设置name和是否private 2.push an exis…

指针数组的有趣程序【C语言】

文章目录 指针数组的有趣程序指针数组是什么&#xff1f;指针数组的魅力指针数组的应用示例&#xff1a;命令行计算器有趣的颜色打印 结语 指针数组的有趣程序 在C语言的世界里&#xff0c;指针是一种强大的工具&#xff0c;它不仅能够指向变量&#xff0c;还能指向数组&#…

如何利用OpenCV4.9离散傅里叶变换

返回&#xff1a;OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇:如何利用OpenCV4.9 更改图像的对比度和亮度 下一篇:OpenCV 如何使用 XML 和 YAML 文件的文件输入和输出 目标 我们将寻求以下问题的答案&#xff1a; 什么是傅里叶变换&#xff0c;为什…

《数据结构学习笔记---第五篇》---链表OJ练习下

step1:思路分析 1.实现复制&#xff0c;且是两个独立的复制&#xff0c;我们必须要理清指针之间的逻辑&#xff0c;注意random的新指针要链接到复制体的后面。 2.我们先完成对于结点的复制&#xff0c;并将复制后的结点放在原节点的后面&#xff0c;并链接。 3.完成random结点…
最新文章