您的位置:首页 > 游戏 > 游戏 > Java 之多线程进阶

Java 之多线程进阶

2024/11/17 18:28:32 来源:https://blog.csdn.net/weixin_64178283/article/details/142314152  浏览:    关键词:Java 之多线程进阶

1. 线程同步:守护数据安全的关键

在多线程环境下,多个线程可能会同时访问同一个共享资源(如数据库连接、文件、全局变量等),如果没有同步机制,就会出现数据不一致、程序崩溃等问题。为了确保数据的一致性和完整性,Java 提供了多种同步机制来协调多个线程对共享资源的访问。

2. 买票案例:揭示同步机制的必要性

假设一个火车站售票窗口,有多个售票员同时售卖车票,每个售票员都是一个线程。如果售票系统没有同步机制,就会出现以下问题:

public class TicketSeller {private int tickets = 10; // 初始化车票数量public void sellTicket() {if (tickets > 0) {System.out.println(Thread.currentThread().getName() + " 售出一张票,剩余票数:" + tickets--);} else {System.out.println(Thread.currentThread().getName() + " 票已售罄!");}}public static void main(String[] args) {TicketSeller ticketSeller = new TicketSeller();Thread t1 = new Thread(ticketSeller::sellTicket, "售票员1");Thread t2 = new Thread(ticketSeller::sellTicket, "售票员2");t1.start();t2.start();}
}

运行这段代码,你会发现:

  • 数据不一致: 多个线程同时修改 tickets 变量,导致最终的票数可能出现负数,甚至出现多个售票员同时售出同一张票的情况。

  • 问题产生原因:线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题

3. 同步代码块:细粒度的同步控制

同步代码块使用 synchronized 关键字修饰代码块,保证同一时间只有一个线程可以访问该代码块,从而避免多个线程同时修改共享资源。

public class TicketSeller {private int tickets = 10; // 初始化车票数量public void sellTicket() {synchronized (this) { // 同步代码块if (tickets > 0) {System.out.println(Thread.currentThread().getName() + " 售出一张票,剩余票数:" + tickets--);} else {System.out.println(Thread.currentThread().getName() + " 票已售罄!");}}}public static void main(String[] args) {TicketSeller ticketSeller = new TicketSeller();Thread t1 = new Thread(ticketSeller::sellTicket, "售票员1");Thread t2 = new Thread(ticketSeller::sellTicket, "售票员2");t1.start();t2.start();}
}

解释:

  • synchronized (this) 表示使用当前对象 this 作为锁对象,所有需要访问该代码块的线程都需要先获得这个锁。

  • 当一个线程获得了锁后,其他线程只能等待,直到该线程释放锁。

  • 在 sellTicket 方法中,每次只允许一个线程进入同步代码块,确保对 tickets 的操作是原子性的,从而避免数据不一致的问题。

  • 安全问题出现的条件

    • 是多线程环境

    • 有共享数据

    • 有多条语句操作共享数据

  • 如何解决多线程安全问题?

    • 基本思想:让程序没有安全问题的环境

  • 如何实现?

    • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

    • Java提供了同步代码块的方式来解决

  • 同步代码块格式:

    synchronized(任意对象) { 多条语句操作共享数据的代码 
    }

    synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

  • 同步的好处与弊端

    • 好处:解决了多线程的数据安全问题

    • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

4. 同步方法:更便捷的同步方式

同步方法使用 synchronized 关键字修饰方法,保证同一时间只有一个线程可以访问该方法。

public class TicketSeller {private int tickets = 10; // 初始化车票数量public synchronized void sellTicket() { // 同步方法if (tickets > 0) {System.out.println(Thread.currentThread().getName() + " 售出一张票,剩余票数:" + tickets--);} else {System.out.println(Thread.currentThread().getName() + " 票已售罄!");}}public static void main(String[] args) {TicketSeller ticketSeller = new TicketSeller();Thread t1 = new Thread(ticketSeller::sellTicket, "售票员1");Thread t2 = new Thread(ticketSeller::sellTicket, "售票员2");t1.start();t2.start();}
}

解释:

  • synchronized 关键字修饰 sellTicket 方法,所有线程在访问该方法时都需要获得当前对象的锁。

  • 同步方法等价于在方法体上添加 synchronized (this) 代码块。

  • 同步方法的格式

    同步方法:就是把synchronized关键字加到方法上

    修饰符 synchronized 返回值类型 方法名(方法参数) { 方法体;
    }

    同步方法的锁对象是什么呢?

    this

5. 静态同步方法:锁定类对象

静态同步方法使用 synchronized 关键字修饰静态方法,锁定的是当前类的 Class 对象,所有线程共享同一个锁。

public class TicketSeller {private static int tickets = 10; // 初始化车票数量public static synchronized void sellTicket() { // 静态同步方法if (tickets > 0) {System.out.println(Thread.currentThread().getName() + " 售出一张票,剩余票数:" + tickets--);} else {System.out.println(Thread.currentThread().getName() + " 票已售罄!");}}public static void main(String[] args) {Thread t1 = new Thread(TicketSeller::sellTicket, "售票员1");Thread t2 = new Thread(TicketSeller::sellTicket, "售票员2");t1.start();t2.start();}
}

解释:

  • 静态同步方法锁定的是 TicketSeller.class 对象,所有访问该方法的线程都需要获取该锁。

  • 即使有多个 TicketSeller 对象,它们共享同一个锁,保证对静态变量 tickets 的操作是线程安全的。

  • 静态同步方法

    同步静态方法:就是把synchronized关键字加到静态方法上

    修饰符 static synchronized 返回值类型 方法名(方法参数) { 方法体;
    }

    同步静态方法的锁对象是什么呢?

    类名.class

6. Lock 锁:更灵活的同步控制

Lock 接口是 Java 并发包中提供的一个更灵活的同步机制,相较于 synchronized 关键字,它提供了更多功能和控制:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class TicketSeller {private int tickets = 10; // 初始化车票数量private Lock lock = new ReentrantLock(); // 创建一个可重入锁public void sellTicket() {lock.lock(); // 获取锁try {if (tickets > 0) {System.out.println(Thread.currentThread().getName() + " 售出一张票,剩余票数:" + tickets--);} else {System.out.println(Thread.currentThread().getName() + " 票已售罄!");}} finally {lock.unlock(); // 释放锁}}public static void main(String[] args) {TicketSeller ticketSeller = new TicketSeller();Thread t1 = new Thread(ticketSeller::sellTicket, "售票员1");Thread t2 = new Thread(ticketSeller::sellTicket, "售票员2");t1.start();t2.start();}
}

解释:

  • 使用 ReentrantLock 创建可重入锁,保证同一个线程可以多次获取同一个锁。

  • 调用 lock() 方法获取锁,调用 unlock() 方法释放锁。

  • 使用 try...finally 块确保锁在任何情况下都能被释放。

Lock 锁的优点:

  • 更灵活: 可以实现更复杂的同步控制,比如可重入锁、公平锁等。

  • 性能更高: 在某些情况下,Lock 锁的性能可能比 synchronized 关键字更高。

  • 支持条件变量: 可以使用 Condition 接口实现更复杂的线程间协作。

7. 死锁:线程之间的互相等待

死锁是指两个或多个线程互相等待对方释放资源,导致所有线程都无法继续执行,造成程序阻塞。

死锁产生的条件:

  • 互斥条件: 资源只能被一个线程使用。

  • 持有和等待条件: 一个线程持有至少一个资源,并等待另一个线程持有的资源。

  • 不可抢占条件: 线程不能抢占其他线程持有的资源。

  • 循环等待条件: 存在一个循环等待链,每个线程都在等待链中的下一个线程持有的资源。

死锁案例:

public class Deadlock {private static Object lockA = new Object();private static Object lockB = new Object();public static void main(String[] args) {Thread thread1 = new Thread(() -> {synchronized (lockA) {System.out.println("线程1 获取锁 A");try {Thread.sleep(1000); // 模拟线程1持有锁A一段时间} catch (InterruptedException e) {e.printStackTrace();}synchronized (lockB) {System.out.println("线程1 获取锁 B");}}}, "线程1");Thread thread2 = new Thread(() -> {synchronized (lockB) {System.out.println("线程2 获取锁 B");try {Thread.sleep(1000); // 模拟线程2持有锁B一段时间} catch (InterruptedException e) {e.printStackTrace();}synchronized (lockA) {System.out.println("线程2 获取锁 A");}}}, "线程2");thread1.start();thread2.start();}
}

解释:

  • 线程1 获取锁 A,然后等待锁 B。

  • 线程2 获取锁 B,然后等待锁 A。

  • 两个线程都无法继续执行,造成死锁。

如何避免死锁:

  • 避免循环等待: 使用固定的资源获取顺序,避免出现循环依赖。

  • 减少资源持有时间: 线程尽量不要长时间持有资源,以减少其他线程等待的时间。

  • 使用超时机制: 在获取资源时设置超时时间,如果超时则放弃获取资源,避免长时间等待。

  • 使用 Lock 接口的 tryLock() 方法: 可以尝试获取锁,如果获取不到则返回 false,避免线程长时间阻塞。

总结:

线程同步机制是保障多线程程序安全性和稳定性的重要手段。掌握同步代码块、同步方法、Lock 锁和死锁的概念,可以帮助各位看官编写出高效、可靠的多线程程序。感谢各位看官的观看,下期见,谢谢~

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com