• 【Java】详解多线程同步的三种方式


    🌺个人主页:Dawn黎明开始

    🎀系列专栏:Java
    每日一句:等风来,不如追风去

    📢欢迎大家:关注🔍+点赞👍+评论📝+收藏⭐️


     


     

    文章目录

    一.🔐线程安全

    1.1🔓案例引入

    1.1.1🔑问题

    1.1.2🔑实例操作

    1.2🔓说明

    二.🔐同步代码块

    2.1🔓语法格式

    2.2🔓全局锁

    🚩实例练习1

    2.3🔓任意锁

    🚩实例练习2

    2.4🔓局部锁

    🚩实例练习3

    2.5🔓this对象作为锁

    🚩实例练习4

    2.6🔓注意

    三.🔐同步方法

    3.1🔓语法格式

    3.2🔓实例练习

    3.3🔓思考

    四.🔐同步锁(重入锁)

    4.1🔓语法格式

    4.2🔓实例练习


    一.🔐线程安全

    1.1🔓案例引入

    1.1.1🔑问题

           电影院上映一部电影,共有三个窗口,请你设计一个模拟电影院卖票的程序。

    1.1.2🔑实例操作

    代码如下👇🏻 

    1. package Process3;
    2. public class SellTicket implements Runnable {
    3. private int tickets = 20;//总票数
    4. private int ticketId = 1;//票号
    5. @Override
    6. public void run() {
    7. while (tickets>0) {
    8. try {
    9. Thread.sleep(50);
    10. } catch (InterruptedException e) {
    11. e.printStackTrace();
    12. }
    13. System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticketId + "张票");
    14. tickets--; //剩余票数--
    15. ticketId++; //票号++
    16. }
    17. }
    18. }
    1. package Process3;
    2. public class SellTicketDemo {
    3. public static void main(String[] args) {
    4. // 创建资源对象
    5. SellTicket st = new SellTicket();
    6. // 创建三个线程对象
    7. Thread t1 = new Thread(st, "窗口1");
    8. Thread t2 = new Thread(st, "窗口2");
    9. Thread t3 = new Thread(st, "窗口3");
    10. // 启动线程
    11. t1.start();
    12. t2.start();
    13. t3.start();
    14. }
    15. }

    运行结果👇🏻

    总结:该方法不可以,有重票和漏票和多票问题。 

    1.2🔓说明

               线程安全问题其实就是由多个线程同时处理共享资源所导致的。要想解决线程安全问题,必须得保证处理共享资源的代码在任意时刻只能有一个线程访问。为此,Java中提供了线程同步机制。

    二.🔐同步代码块

    2.1🔓语法格式

    原理

          (1).当线程执行同步代码块时,首先会检查lock锁对象的标志位。

          (2).默认情况下标志位为1,此时线程会执行Synchronized同步代码块,同时将锁对象的标志位置为0。

          (3).当一个新的线程执行到这段同步代码块时,由于锁对象的标志位为0,新线程会发生阻塞,等待当前线程执行完同步代码块后。

          (4).锁对象的标志位被置为1,新线程才能进入同步代码块执行其中的代码,这样循环往复,直到共享资源被处理完为止。

     同步代码块:

     * synchronized(对象){

     * 需要同步的代码;

     * }

     *

     * (1).对象是什么呢?

     * 我们可以随便创建一个对象试试。

     * (2).需要同步的代码是哪些呢?

     * 把多条语句操作共享数据的代码的部分给包起来

     *

     * 注意

     * (1).同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。

     * (2).多个线程必须是同一把锁。

    2.2🔓全局锁

    注:以下四种方法的测试类都一样(只有2.2写了测试类,其余省略了)

    🚩实例练习1

    代码如下👇🏻 

    1. package Sell;
    2. public class SellTicket implements Runnable {
    3. private int tickets = 20;//总票数
    4. private int ticketId = 1;//票号
    5. Object o =new Object();//全局锁
    6. @Override
    7. public void run() {
    8. while (tickets>0) {
    9. synchronized (o) {
    10. if(tickets>0) {
    11. try {
    12. Thread.sleep(50);
    13. } catch (InterruptedException e) {
    14. e.printStackTrace();
    15. }
    16. System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticketId + "张票");
    17. tickets--; //剩余票数--
    18. ticketId++; //票号++
    19. }
    20. }
    21. }
    22. }
    23. }
    1. package Sell;
    2. public class SellTicketDemo1 {
    3. public static void main(String[] args) {
    4. // 创建资源对象
    5. SellTicket1 st = new SellTicket1();
    6. // 创建三个线程对象
    7. Thread t1 = new Thread(st, "窗口1");
    8. Thread t2 = new Thread(st, "窗口2");
    9. Thread t3 = new Thread(st, "窗口3");
    10. // 启动线程
    11. t1.start();
    12. t2.start();
    13. t3.start();
    14. }
    15. }

    运行结果👇🏻

    2.3🔓任意锁

    🚩实例练习2

    代码如下👇🏻 

    1. package Sell;
    2. public class SellTicket1 implements Runnable {
    3. private int tickets = 20;//总票数
    4. private int ticketId = 1;//票号
    5. Demo lock =new Demo();//任意锁
    6. @Override
    7. public void run() {
    8. while (tickets>0) {
    9. try {
    10. Thread.sleep(50);
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. synchronized (lock) {
    15. // 为什么有if判断?
    16. // 如果没有if,t1执行结束,tickets=0,这样t2,t3再执行同步代码块之后,票会变成负数,票会多卖
    17. if(tickets>0) {
    18. try {
    19. Thread.sleep(50);
    20. } catch (InterruptedException e) {
    21. e.printStackTrace();
    22. }
    23. System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticketId + "张票");
    24. tickets--; //剩余票数--
    25. ticketId++; //票号++ // 多卖21,22
    26. }
    27. }
    28. }
    29. }
    30. }
    31. class Demo{
    32. //任意类
    33. }

    运行结果👇🏻

    2.4🔓局部锁

    🚩实例练习3

    代码如下👇🏻 

    1. package Sell;
    2. public class SellTicket1 implements Runnable {
    3. private int tickets = 20;//总票数
    4. private int ticketId = 1;//票号
    5. @Override
    6. public void run() {
    7. Object lock =new Object();//局部锁锁不住
    8. while (tickets>0) {
    9. try {
    10. Thread.sleep(50);
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. synchronized (lock) {
    15. // 为什么有if判断?
    16. // 如果没有if,t1执行结束,tickets=0,这样t2,t3再执行同步代码块之后,票会变成负数,票会多卖
    17. if(tickets>0) {
    18. try {
    19. Thread.sleep(50);
    20. } catch (InterruptedException e) {
    21. e.printStackTrace();
    22. }
    23. System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticketId + "张票");
    24. tickets--; //剩余票数--
    25. ticketId++; //票号++ // 多卖21,22
    26. }
    27. }
    28. }
    29. }
    30. }

    运行结果👇🏻

    总结:该方法不可以,有重票和漏票问题。

    2.5🔓this对象作为锁

    🚩实例练习4

    代码如下👇🏻 

    1. package Sell;
    2. public class SellTicket1 implements Runnable {
    3. private int tickets = 20;//总票数
    4. private int ticketId = 1;//票号
    5. @Override
    6. public void run() {
    7. while (tickets>0) {
    8. try {
    9. Thread.sleep(50);
    10. } catch (InterruptedException e) {
    11. e.printStackTrace();
    12. }
    13. synchronized (this) {
    14. // 为什么有if判断?
    15. // 如果没有if,t1执行结束,tickets=0,这样t2,t3再执行同步代码块之后,票会变成负数,票会多卖
    16. if(tickets>0) {
    17. try {
    18. Thread.sleep(50);
    19. } catch (InterruptedException e) {
    20. e.printStackTrace();
    21. }
    22. System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticketId + "张票");
    23. tickets--; //剩余票数--
    24. ticketId++; //票号++ // 多卖21,22
    25. }
    26. }
    27. }
    28. }
    29. }

    运行结果👇🏻

    说明:项目开发中一般使用this关键字作为锁对象。

    2.6🔓注意

    三.🔐同步方法

    3.1🔓语法格式

    3.2🔓实例练习

     代码如下👇🏻 

    1. package Sell;
    2. public class SellTicket1 implements Runnable {
    3. private int tickets = 20;//总票数
    4. private int ticketId = 1;//票号
    5. @Override
    6. public void run() {
    7. while (tickets>0) {
    8. try {
    9. Thread.sleep(50);
    10. } catch (InterruptedException e) {
    11. e.printStackTrace();
    12. }
    13. Selltickets ();
    14. }
    15. }
    16. //方法抽取:
    17. //同步方法
    18. public synchronized void Selltickets () {
    19. if(tickets>0) {
    20. System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticketId + "张票");
    21. tickets--; //剩余票数--
    22. ticketId++; //票号++ // 多卖21,22
    23. }
    24. }
    25. }
    1. package Sell;
    2. public class SellTicketDemo1 {
    3. public static void main(String[] args) {
    4. // 创建资源对象
    5. SellTicket1 st = new SellTicket1();
    6. // 创建三个线程对象
    7. Thread t1 = new Thread(st, "窗口1");
    8. Thread t2 = new Thread(st, "窗口2");
    9. Thread t3 = new Thread(st, "窗口3");
    10. // 启动线程
    11. t1.start();
    12. t2.start();
    13. t3.start();
    14. }
    15. }

    运行结果👇🏻

    3.3🔓思考

         (1).同步方法的格式及锁对象问题?

               把同步关键字加在方法上

         (2).同步代码块的锁对象是谁?

               任意全局对象   一般使用this作为锁对象

         (3).同步方法的锁是谁?

               this,它是隐含的

    四.🔐同步锁(重入锁)

    4.1🔓语法格式

     Lock:

     * void lock(): 获取锁。

     * void unlock():释放锁。  

     * ReentrantLock是Lock的实现类.

    4.2🔓实例练习

    代码如下👇🏻 

    1. package Sell;
    2. import java.util.concurrent.locks.Lock;
    3. import java.util.concurrent.locks.ReentrantLock;
    4. public class SellTicket1 implements Runnable {
    5. private int tickets = 20;//总票数
    6. private int ticketId = 1;//票号
    7. Lock lock =new ReentrantLock();//同步锁(重入锁)
    8. @Override
    9. public void run() {
    10. while (tickets>0) {
    11. try {
    12. Thread.sleep(50);
    13. } catch (InterruptedException e) {
    14. e.printStackTrace();
    15. }
    16. lock.lock();//上锁
    17. if(tickets>0) {
    18. System.out.println(Thread.currentThread().getName() + "正在出售第"+ ticketId + "张票");
    19. tickets--; //剩余票数--
    20. ticketId++; //票号++
    21. }
    22. lock.unlock();//开锁
    23. }
    24. }
    25. }
    1. package Sell;
    2. public class SellTicketDemo1 {
    3. public static void main(String[] args) {
    4. // 创建资源对象
    5. SellTicket1 st = new SellTicket1();
    6. // 创建三个线程对象
    7. Thread t1 = new Thread(st, "窗口1");
    8. Thread t2 = new Thread(st, "窗口2");
    9. Thread t3 = new Thread(st, "窗口3");
    10. // 启动线程
    11. t1.start();
    12. t2.start();
    13. t3.start();
    14. }
    15. }

    运行结果👇🏻


    ​🌺建议大家亲自动手操作,学编程,多实践练习是提升编程技能的必经之路。

    🌺欢迎大家在评论区进行讨论和指正!

  • 相关阅读:
    C++ 99 之 容器存取
    JAVA计算机毕业设计晨光文具店进销存系统设计与开发Mybatis+源码+数据库+lw文档+系统+调试部署
    【Unity开发】VSCode 代码补全缺失问题的各种修复方法
    JDBC-API详解-Statement类
    Linux 本地zabbix结合内网穿透工具实现安全远程访问浏览器
    【Java高级编程】Java多线程学习笔记
    计算机是如何启动的
    关于js_数组的介绍和常用方法的使用
    【最新】生成式人工智能(AIGC)与大语言模型(LLM)学习资源汇总
    15-Hbase深入理解数据读写流程、数据刷写、合并、切分和表设计原则
  • 原文地址:https://blog.csdn.net/2301_80760873/article/details/134426974