java线程同步

线程同步(队列+锁)

多个线程操作同一资源

线程同步其实就是一种排队等待机制

同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。 *解说:甲乙丙丁排队去卫生间,甲进入卫生间后上锁(加锁确保安全),乙丙丁继续排队(等待),等卫生间门打开甲出来后(释放),乙才能进去。

存在的问题:(安全和性能 不可兼得)

  1. 一个线程持有锁会导致其他所有需要此锁的线程挂起

  2. 在多线程竞争下,加锁,释放锁会导致较多的上下文切换 和 调度延时,引起性能问题

  3. 如果一个优先级高的线程等待一个优先级低的线程,会导致优先级倒置,引起性能问题

*性能倒置,解说:甲乙排队去卫生间,甲需要占用30分钟,而乙只需要占用2分钟,如果先让甲使用,乙就需要多等30分钟。

synchronized 两种使用方式

  1. 同步方法:public synchronized void method(){}

  2. 同步块:synchronized (obj) { }

    • obj称为同步监视器,可以是任何对象,推荐使用共享资源作为同步监视器

    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this对象本身,或者是class(反射)

注意: 使用synchronized时,一定要注意同步监视器指向谁,指向的对象是需要增删改的对象才可生效

// 多线程抢票例子
public class UnSaftBuyTicket {
   public static void main(String[] args) {
       // 在不加安全锁时,可能会出现两人同时抢到同一张票,或出现第0,-1张票。加锁后则不会出现
       BuyTicket buyTicket = new BuyTicket();
       new Thread(buyTicket,"小明").start();
       new Thread(buyTicket,"小红").start();
       new Thread(buyTicket,"小白").start();
  }
}
class BuyTicket implements Runnable{
   private Integer ticketNums = 10;// 票数
   boolean flag = true;// 线程停止标志
   @Override
   public void run() {
       while (flag) {
           try {
               buy();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }
  }
   private void buy() throws InterruptedException { // 3.此时在此方法上添加synchronized即可,同步监视器默认指向this(BuyTicket),main方法中多线程则会排队使用BuyTicket对象,使线程安全
       // *1、这里的同步监视器指向ticketNums会发现,运行结果仍然是线程不安全的
       // 2、到main方法中,可以看到有多个线程同时操作BuyTicket对象,当多个线程同时获取到BuyTicket资源后,会在各自的线程内存中运行,此时ticketNums并不是唯一的
       synchronized (ticketNums) {
           if (ticketNums <= 0) {
               flag = false;
               return;
          }
           Thread.sleep(100);
           System.out.println(Thread.currentThread().getName() + " 买到了第 " + ticketNums-- + " 张票!");
      }
  }
}

死锁

多个线程占着对方需要的资源不放,形成僵持

产生死锁的四个必要条件:(只要避免下面任意一条件,即可避免死锁)

  1. 互斥条件:一个资源每次只能被一个进程使用(一个资源不能被多个进程同时使用)

  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放(占用资源不放,导致其他进程一直等待)

  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺(某一资源被占用时,其他进程无法使用)

  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系(进程所需的资源都被占用,一直处于循环等待状态)

// 死锁,例子:两人化妆 竞争口红和镜子
public class TestDeadLock {
   public static void main(String[] args) {
       // 执行结果会发现,小红一直占用着口红永远无法拿到镜子,而小丽一直占用着镜子永远无法拿到口红,形成了僵持
       Makeup p1 = new Makeup(0,"小红");
       Makeup p2 = new Makeup(1,"小丽");
       p1.start();
       p2.start();
  }
}
// 口红
class LipStick{ }
// 镜子
class Mirror{ }
// 化妆
class Makeup extends Thread{
   // 通过static的方式确保口红和镜子只有一个
   static LipStick lipStick = new LipStick();
   static Mirror mirror = new Mirror();
   int choose; // 选择 0口红,1镜子
   String username; // 用户
   public Makeup(int choose,String username){
       this.choose = choose;
       this.username = username;
  }
   @Override
   public void run() {
       try {
           makeup();
      } catch (InterruptedException e) {
           e.printStackTrace();
      }
  }
   private void makeup() throws InterruptedException {
       if (choose == 0) {
           synchronized (lipStick){
               System.out.println(this.username + "获得了口红资源");
               Thread.sleep(1000);
               synchronized (mirror){
                   System.out.println(this.username + "获得了镜子资源");
              }
          }
      } else {
           synchronized (mirror){
               System.out.println(this.username + "获得了镜子资源");
               Thread.sleep(1000);
               synchronized (lipStick){
                   System.out.println(this.username + "获得了口红资源");
              }
          }
      }
  }
}

Lock锁

Lock锁是JDK5.0提供的更强大的线程同步机制

ReentrantLock类(可重入锁)实现了Lock,它拥有与synchronized相同的并发性和内存语义

//JUC Lock锁
public class TestLock {
   public static void main(String[] args) {
       GetTicket getTicket = new GetTicket();
       // 在不加安全锁时,可能会出现两人同时抢到同一张票,或出现第0,-1张票。加锁后则不会出现
       new Thread(getTicket,"小明").start();
       new Thread(getTicket,"小王").start();
  }
}
class GetTicket implements Runnable{
   int ticketNum = 10; // 票数
   private final ReentrantLock lock = new ReentrantLock();
   @Override
   public void run() {
       while (true) {
           try {
               lock.lock();// *加锁
               if (ticketNum > 0) {
                   System.out.println(Thread.currentThread().getName() + " 抢到了第 " + ticketNum-- + " 张票!");
                   Thread.sleep(100);
              } else {
                   break;
              }
          } catch (InterruptedException e) {
               e.printStackTrace();
          } finally {
               lock.unlock();// *解锁
          }
      }
  }
}

synchronized 与 Lock 的对比

  • Lock是显式锁(需先创建ReentrantLock对象,再手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放

  • Lock只有代码块锁,synchronized有代码块锁和方法锁

  • Lock,jvm花费较少的时间来调度线程,性能更好,且具有更好的扩展性(有更多的子类)

posted @ 2021-12-30 18:22  迷路小孩  阅读(48)  评论(0)    收藏  举报