关于synchronized

发布时间 2023-09-04 18:14:57作者: upupup-999
  1. 关于synchronized

    synchronized是java中的关键字,可以在需要线程安全的业务场景中进行使用,保证线程安全,它是利用锁机制来实现同步的。

  2. synchronized锁对象和锁类

    1. 对象锁:每个实例都会有一个monitor对象,即Java对象的锁,类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰
    2. 类锁:每个类只有一个Class对象,所以每个类只有一个类锁;类锁是加载类上的,而类信息是存在JVM方法区的,并且整个JVM只有一份,方法区又是所有线程共享的,所以类锁是所有线程共享的
    3. 原理参考:底层原理 https://javaguide.cn/java/concurrent/java-concurrent-questions-02.html#synchronized-%E5%BA%95%E5%B1%82%E5%8E%9F%E7%90%86%E4%BA%86%E8%A7%A3%E5%90%97
  3. 实战使用

    1. 多个线程操作相同对象

      /**
       * @Author wsz
       * @Description 多个线程操作同一个资源, 同时只有一个线程执行成功,使用对象锁或类锁都可以
       */
      public class SellTicket implements Runnable {
          private int ticketNum = 100;
          public boolean loop = true;
      
          //每个对象都有自己的锁
          Object obj = new Object();
      
          public void sell() {
              synchronized (obj) {
                  if (ticketNum <= 0) {
                      loop = false;
                      return;
                  }
                  System.out.println(Thread.currentThread().getName() + " 卖了第" + ticketNum + "张票,还剩" + --ticketNum);
              }
      
          }
      
          @Override
          public void run() {
              while (loop) {
                  sell();
              }
          }
      }
      
      class Test {
          public static void main(String[] args) {
              SellTicket sellTicket = new SellTicket();
              // 多个线程操作同一个对象 使用同一个对象锁obj,同一时刻只能有一个线程执行sell方法内的代码块
              Thread thread1 = new Thread(sellTicket);
              Thread thread2 = new Thread(sellTicket);
              Thread thread3 = new Thread(sellTicket);
              thread1.start();
              thread2.start();
              thread3.start();
          }
      }
      
      1. 每个线程操作不同对象

        package com.pj.project.sys_user_sync.secure;
        
        /**
         * @Author wsz
         * @Description 这个示例中,我们创建了两个不同的BankAccount对象实例 account1 和 account2。每个对象实例都有自己的锁。
         * 当线程thread1和thread2分别对account1和account2调用withdraw()方法时,它们会尝试获取各自对象实例的锁
         * 这意味着每次只有一个线程可以执行withdraw()方法,并且其他线程必须等待锁的释放。通过使用对象级别的锁,
         * 我们可以确保同一时间只有一个线程能够从特定的账户中进行取款操作,避免了数据不一致和竞态条件的问题。
         */
        public class BankAccount {
            private int balance;
        
            public BankAccount(int balance) {
                this.balance = balance;
            }
        
            public void withdraw(int amount) {
                // 不同线程获取各自对象实例的锁
                synchronized (this) {
                    if (balance >= amount) {
                        balance -= amount;
                        System.out.println(Thread.currentThread().getName() + " 完成取款:" + amount+"还剩"+(balance));
                    } else {
                        System.out.println(Thread.currentThread().getName() + " 取款失败,余额不足");
                    }
                }
            }
        }
        
        class Main1 {
            public static void main(String[] args) {
                //每个线程操作不同对象 使用同一个对象锁obj,同一时刻只能有一个线程执行sell方法内的代码块
                //账户1
                BankAccount account1 = new BankAccount(1000);
                //账户2
                BankAccount account2 = new BankAccount(2000);
        
                Thread thread1 = new Thread(() -> {
                    for (int i = 0; i < 5; i++) {
                        //从账户1取款
                        account1.withdraw(200);
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                });
        
                Thread thread2 = new Thread(() -> {
                    for (int i = 0; i < 10; i++) {
                        //从账户2取款
                        account2.withdraw(300);
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                });
        
                Thread thread3 = new Thread(() -> {
                    for (int i = 0; i < 5; i++) {
                        //从账户1取款
                        account1.withdraw(200);
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                });
        
                thread1.start();
                thread2.start();
                thread3.start();
            }
        }
        
  4. 对象锁和类锁使用场景

    1. 对象锁适用于处理对象实例的并发访问,保护对象状态的一致性。每个对象实例都拥有自己的锁,因此可以独立地控制并发访问。不同对象实例之间的锁是相互独立的,互不影响。
    2. 类锁适用于处理类的静态变量或静态方法的并发访问,保护全局状态的一致性。所有类的实例对象共享同一个类锁,只能有一个线程同时获取锁,确保全局资源的同步访问。
    3. 因此,如果存在多个对象实例,每个实例之间需要独立控制并发访问,应该选择对象锁。而如果需要保护全局状态或静态资源,并且所有实例对象共享同一个锁,应该选择类锁。