【JavaSE】多线程篇(四)线程的同步机制、互斥锁、线程死锁与释放锁

简介: 文章目录1 走进Synchronized1.1 线程同步机制1.2 同步的具体方法--synchronized1.3 使用线程同步解决售票问题2 互斥锁2.1 基本介绍2.2 使用互斥锁解决售票问题3 线程死锁3.1 基本介绍3.2 案例演示4 释放锁4.1 释放锁的情况4.2 不会释放锁的情况

1 走进Synchronized

1.1 线程同步机制

 在前面的多线程篇的学习中,我们可以尝试对售票活动进行模拟,将每个售票窗口看成一个进程。但是,前面由于没有学习过线程的同步,有可能会出现超卖的问题。比如只剩最后一张票,但是,两个窗口此时都在同时卖,就会由于数据更新不及时,导致多卖出票。


🐱何为线程同步机制?


在多线程编程中,一些敏感数据不允许被多个线程同时访问, 此时就需要使用同步访问技术,保证数据在任何时刻,最多有一个线程被访问,以保证数据的完整性。

线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作。

1.2 同步的具体方法–synchronized

1️⃣ 同步代码块:

synchronized(对象){//得到对象的锁,才能操作同步代码
  //需要被同步的代码
}

2️⃣ 同步方法:

public synchronized void method(参数列表){
  //需要被同步的代码
}

1.3 使用线程同步解决售票问题

 在下面的案例中,我们模拟售票问题,通过synchronized解决超卖问题。

package syn;
/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 售票问题
 */
public class SellTicket {
    public static void main(String[] args) {
        SellTicketToCustomer sellTicketToCustomer = new SellTicketToCustomer();
        //模拟三个售票窗口
        new Thread(sellTicketToCustomer).start();
        new Thread(sellTicketToCustomer).start();
        new Thread(sellTicketToCustomer).start();
    }
}
class SellTicketToCustomer implements Runnable{
    /**
     * 票数
     */
    private int ticketNum = 100;
    /**
     * 控制run中的while,用于以通知的形式终止线程
     */
    private boolean loop = true;
    @Override
    public void run() {
        while (loop){
            sellTicket();
        }
    }
    /**
     * 售票的方法
     */
    public synchronized void sellTicket(){ //同步方法, 在统一时刻, 只能有一个线程来执行sellTicket方法
        if (ticketNum <= 0){
            System.out.println("售票结束...");
            loop = false;
            return;
        }
        try {
            Thread.sleep(50);
        }catch (InterruptedException e){
            e.getStackTrace();
        }
        System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票, "
                + "剩余票数 = " + (--ticketNum));
    }
}

🐰 说明:

在上述例子中,sellTicket方法被设置为同步方法,在同一时刻,只允许一个线程进入,解决了超卖的情况。运行结果如下:


2 互斥锁

2.1 基本介绍

Java语言引入了对象互斥锁的概念,来保证共享数据操作的完整性;

每个对象都对应于一个可称为“互斥锁”的标记,这个标记 用来保证在任一时刻,只能有一个线程访问该对象;

关键字synchronized用来与对象的互斥锁联系。当某个对象用synchronized修饰时,表示该对象在任一时刻只能由一个线程访问;

同步具有局限性,会导致程序的执行效率降低;

非静态同步方法的锁可以是this,也可以是其他对象(要求为同一对象);

静态的同步方法的锁为当前类本身,类名.class。

2.2 使用互斥锁解决售票问题

 售票问题我们既可以使用同步方法,也可以使用对象锁,给代码块上锁。在之前的案例中,我们演示的就是使用了同步方法:


public synchronized void sellTicket()就是一个同步方法,这时锁在 this 对象上


这里,我们尝试 使用对象锁解决, 解决代码如下:


其实也不一定必须使用this,只要对象为同一个就行,比如下面的代码中,在该线程类中,初始化了一个对象,加锁代码块用的也是同一对象,因此,结果一致:

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 售票问题
 */
public class SellTicket {
    public static void main(String[] args) {
        SellTicketToCustomer sellTicketToCustomer = new SellTicketToCustomer();
        //模拟三个售票窗口
        new Thread(sellTicketToCustomer).start();
        new Thread(sellTicketToCustomer).start();
        new Thread(sellTicketToCustomer).start();
    }
}
class SellTicketToCustomer implements Runnable{
    /**
     * 票数
     */
    private int ticketNum = 100;
    /**
     * 控制run中的while,用于以通知的形式终止线程
     */
    private boolean loop = true;
    /**
     * 用于加锁
     */
    Object obj = new Object();
    @Override
    public void run() {
        while (loop){
            sellTicket();
        }
    }
    /**
     * 售票的方法
     */
    public void sellTicket(){
        synchronized (obj) {
            if (ticketNum <= 0) {
                System.out.println("售票结束...");
                loop = false;
                return;
            }
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.getStackTrace();
            }
            System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票, "
                    + "剩余票数 = " + (--ticketNum));
        }
    }
}

互斥锁小结一下:


同步方法如果没有static修饰,默认锁对象为this;

如果同步方法有static修饰,默认锁对象为 当前类.class;

注意多个线程的锁对象为同一个即可。

3 线程死锁

3.1 基本介绍

 多个线程都占用了对方的锁资源,但是不肯相让,就导致了死锁。 在实际编程开发中,一定要避免死锁的发生。


🐦 举个例子:


话说,在某一药店门口… …

👮 你好,请您佩戴口罩!

👦我没有口罩!

👮 你好,没有口罩是不能入内的哦~

👦我就是没有口罩才来买口罩啊!

👮 没有佩戴口罩是不能进入的呢…

👦… …

👮 … …


 在上述例子中,我们可以简单的把口罩看作锁,把保安放人与客户通过分别看成一个线程。此时,保安和客户都占用口罩这一锁资源,互不相让,就导致了死锁的情况。


3.2 案例演示

/**
 * @author 兴趣使然黄小黄
 * @version 1.0
 * 模拟线程死锁
 */
public class DeadLockTest {
    public static void main(String[] args) {
        DeadDemo deadDemo1 = new DeadDemo(true);
        DeadDemo deadDemo2 = new DeadDemo(false);
        Thread thread1 = new Thread(deadDemo1);
        Thread thread2 = new Thread(deadDemo2);
        thread1.start();
        thread2.start();
    }
}
/**
 * 线程
 */
class DeadDemo implements Runnable{
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean flag;
    public DeadDemo(boolean flag){
        this.flag = flag;
    }
    @Override
    public void run() {
        if (flag){
            synchronized (o1){
                System.out.println(Thread.currentThread().getName() + "进入1");
                synchronized (o2){
                    System.out.println(Thread.currentThread().getName() + "进入2");
                }
            }
        }else {
            synchronized (o2){
                System.out.println(Thread.currentThread().getName() + "进入3");
                synchronized (o1){
                    System.out.println(Thread.currentThread().getName() + "进入4");
                }
            }
        }
    }
}



🐰 说明:


 thread0与thread1两个线程运行时,thread0首先拿到o1锁,而后请求o2锁,这时,thread1已经拿到了o2锁,请求o1锁,由于各自请求的锁都被其他线程占用,就导致了死锁的情况, 运行结果就像卡住了一样,不再动弹。


4 释放锁

4.1 释放锁的情况

当前线程的同步方法、同步代码块执行结束;

当前线程在同步代码块、同步方法中遇到break、return;

当前线程在同步代码块、同步方法中出现了未处理的Error或者Exception,导致异常结束;

当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

4.2 不会释放锁的情况

线程执行同步代码块或者同步方法时,程序调用了Thread.sleep()、Thread.yield()方法,暂停了当前线程的执行,不会释放锁;

线程执行同步代码块时,其他线程调用了该线程的suspend()方法将线程挂起,该线程不会释放锁。


相关文章
|
8月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
356 0
|
5月前
|
设计模式 消息中间件 安全
【JUC】(3)常见的设计模式概念分析与多把锁使用场景!!理解线程状态转换条件!带你深入JUC!!文章全程笔记干货!!
JUC专栏第三篇,带你继续深入JUC! 本篇文章涵盖内容:保护性暂停、生产者与消费者、Park&unPark、线程转换条件、多把锁情况分析、可重入锁、顺序控制 笔记共享!!文章全程干货!
395 1
|
8月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。
|
9月前
|
Java 数据挖掘 调度
Java 多线程创建零基础入门新手指南:从零开始全面学习多线程创建方法
本文从零基础角度出发,深入浅出地讲解Java多线程的创建方式。内容涵盖继承`Thread`类、实现`Runnable`接口、使用`Callable`和`Future`接口以及线程池的创建与管理等核心知识点。通过代码示例与应用场景分析,帮助读者理解每种方式的特点及适用场景,理论结合实践,轻松掌握Java多线程编程 essentials。
641 5
|
并行计算 安全 Java
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
在Python开发中,GIL(全局解释器锁)一直备受关注。本文基于CPython解释器,探讨GIL的技术本质及其对程序性能的影响。GIL确保同一时刻只有一个线程执行代码,以保护内存管理的安全性,但也限制了多线程并行计算的效率。文章分析了GIL的必要性、局限性,并介绍了多进程、异步编程等替代方案。尽管Python 3.13计划移除GIL,但该特性至少要到2028年才会默认禁用,因此理解GIL仍至关重要。
1134 16
Python GIL(全局解释器锁)机制对多线程性能影响的深度分析
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
487 20
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
284 1
|
供应链 安全 NoSQL
PHP 互斥锁:如何确保代码的线程安全?
在多线程和高并发环境中,确保代码段互斥执行至关重要。本文介绍了 PHP 互斥锁库 `wise-locksmith`,它提供多种锁机制(如文件锁、分布式锁等),有效解决线程安全问题,特别适用于电商平台库存管理等场景。通过 Composer 安装后,开发者可以利用该库确保在高并发下数据的一致性和安全性。
241 6
|
Java 关系型数据库 MySQL
【JavaEE“多线程进阶”】——各种“锁”大总结
乐/悲观锁,轻/重量级锁,自旋锁,挂起等待锁,普通互斥锁,读写锁,公不公平锁,可不可重入锁,synchronized加锁三阶段过程,锁消除,锁粗化

热门文章

最新文章