前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java锁的各类锁

java锁的各类锁

原创
作者头像
shigen
发布2023-07-31 15:39:44
1820
发布2023-07-31 15:39:44
举报

可重入锁

递归锁,同一个线程,外层函数获得锁,内层的也获得锁。

synchronized
代码语言:java
复制
  public synchronized void sendSMS() {
    System.out.println(Thread.currentThread().getName() + "  sendSMS......");
    sendEmail();
  }

  public synchronized void sendEmail() {
    System.out.println(Thread.currentThread().getName() + "   sendEmail.....");
  }
ReentrantLock

lock和unlock必须成对的出现,不成对出现会使程序不能结束

代码语言:java
复制
  private void get() {
    try {
      lock.lock();
      System.out.println(Thread.currentThread().getName() + " get.....");
      set();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }

  private void set() throws Exception {
    try {
      lock.lock();
      System.out.println(Thread.currentThread().getName() + " set.....");
    } finally {
      lock.unlock();
    }

自旋锁

获得锁的线程不会立即的阻塞,而是采用循环的方式获得锁,减少了线程上下文的切换,会循环消耗CPU资源

代码语言:java
复制
public class SpinLockDemo {

  AtomicReference<Thread> atomicReference = new AtomicReference<>();

  public void myLock() {
    Thread thread = Thread.currentThread();
    System.out.println(thread.getName() + " come in.......");
    while (!atomicReference.compareAndSet(null, thread)) {

    }
  }

  public void myUnLock() {
    Thread thread = Thread.currentThread();
    atomicReference.compareAndSet(thread, null);
    System.out.println(Thread.currentThread().getName() + " myUnlock()....");
  }


  public static void main(String[] args) {
    SpinLockDemo lockDemo = new SpinLockDemo();

    new Thread(() -> {
      lockDemo.myLock();
      try {
        TimeUnit.SECONDS.sleep(5);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      lockDemo.myUnLock();
    }, "AA").start();

    try {
      TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    new Thread(() -> {
      lockDemo.myLock();
      lockDemo.myUnLock();
    }, "BB").start();

  }

}

共享锁和独占锁

代码语言:java
复制
class MyCache {
  // 保证可见性
  private volatile Map<String, Object> map = new HashMap<>();
  // 保证了原子性,但是不能读
  // private Lock lock = new ReentrantLock();
  private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

  public void put(String key, Object value) {
    readWriteLock.writeLock().lock();
    try {
      System.out.println(Thread.currentThread().getName() + "   写入....");
      TimeUnit.SECONDS.sleep(1);
      map.put(key, value);
      System.out.println(Thread.currentThread().getName() + " finished 写入....");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      readWriteLock.writeLock().unlock();
    }
  }

  public Object get(String key) {
    readWriteLock.readLock().lock();
    try {
      System.out.println(Thread.currentThread().getName() + " 读取.....");
      TimeUnit.SECONDS.sleep(1);
      Object o = map.get(key);
      System.out.println(Thread.currentThread().getName() + " finished 读取.....");
      return o;
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      readWriteLock.readLock().unlock();
    }
    return null;
  }

}


public class ReadWriteDemo {

  public static void main(String[] args) {
    MyCache myCache = new MyCache();

    for (int i = 0; i < 5; i++) {
      final int tempInt = i;
      new Thread(() -> {
        myCache.put(tempInt + "", tempInt + "");
      }, String.valueOf(i)).start();
    }

    for (int i = 0; i < 5; i++) {
      final int tempInt = i;
      new Thread(() -> {
        myCache.get(tempInt + "");
      }, String.valueOf(i)).start();
    }
  }

}
代码语言:txt
复制
0   写入....
0 finished 写入....
1   写入....
1 finished 写入....
2   写入....
2 finished 写入....
3   写入....
3 finished 写入....
4   写入....
4 finished 写入....
0 读取.....
1 读取.....
3 读取.....
2 读取.....
4 读取.....
2 finished 读取.....
0 finished 读取.....
1 finished 读取.....
3 finished 读取.....
4 finished 读取.....

写保证顺序,但是读不保证顺序

CountDownLatch

使用原理
  1. 创建CountDownLatch并设置计数器值。
  2. 启动多线程并且调用CountDownLatch实例的countDown()方法。
  3. 主线程调用 await() 方法,这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务,count值为0,停止阻塞,主线程继续执行。
使用场景

一个程序中有N个任务在执行,我们可以创建值为N的CountDownLatch,当每个任务完成后,调用一下countDown()方法进行递减count值,再在主线程中使用await()方法等待任务执行完成,主线程继续执行。

代码语言:java
复制
public class CountDownLatchDemo {

  public static void main(String[] args) throws InterruptedException {
    CountDownLatch countDownLatch = new CountDownLatch(6);
		// 等待所有人离开教室案例
    for (int i = 1; i <= 6; i++) {
      new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + " 离开教室....");
        // * Decrements the count of the latch, releasing all waiting threads if
        // * the count reaches zero.
        countDownLatch.countDown();
      }, CountryEnum.getName(i)).start();
    }
    // Causes the current thread to wait until the latch has counted down to
    // * zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
    countDownLatch.await();
    System.out.println(Thread.currentThread().getName() + " ********关闭");
    // closeDoor();
  }

  public static void closeDoor() throws InterruptedException {
    CountDownLatch countDownLatch = new CountDownLatch(6);

    for (int i = 0; i < 6; i++) {
      new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + " 离开教室....");
        countDownLatch.countDown();
      }, String.valueOf(i)).start();
    }
    countDownLatch.await();
    System.out.println(Thread.currentThread().getName() + " ********关闭");
  }

  enum CountryEnum {
    ONE(1, "齐"), TWO(2, "楚"), THREE(3, "燕"), FOUR(4, "赵"), FIVE(5, "魏"), SIX(6, "韩");


    CountryEnum(Integer code, String msg) {
      this.code = code;
      this.msg = msg;
    }

    private Integer code;
    private String msg;

    public Integer getCode() {
      return code;
    }

    public String getMsg() {
      return msg;
    }

    public static String getName(int index) {
      CountryEnum[] values = CountryEnum.values();
      for (CountryEnum value : values) {
        if (index == value.getCode()) {
          return value.getMsg();
        }
      }
      return null;
    }
  }

}

CyclicBarrier

7个线程是随机的,当运行完毕之后,才会执行cyclicBarrier的回调函数

代码语言:java
复制
  public static void main(String[] args) {
    CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()-> System.out.println("完成收集....."));

    for (int i = 0; i < 7; i++) {
      new Thread(() -> {
        System.out.println(Thread.currentThread().getName() + " 开始.....");

        try {
          cyclicBarrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
          e.printStackTrace();
        }
      }, String.valueOf(i)).start();
    }

  }

CyclicBarrier和CountDownLatch的区别

CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景; CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断; CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。

Semaphore

一个或多个资源的互斥作用,使用时需要先构建一个参数来指定共享资源的数量,Semaphore构造完成后即是获取Semaphore、共享资源使用完毕后释放Semaphore。

代码语言:java
复制
    // 模拟三个停车位
    Semaphore semaphore = new Semaphore(3);
    for (int i = 0; i < 6; i++) {
      new Thread(() -> {
        try {
          semaphore.acquire();
          System.out.println(Thread.currentThread().getName() + "  获得车位");
          TimeUnit.SECONDS.sleep(3);
          System.out.println(Thread.currentThread().getName() + "  离开");
        } catch (Exception e) {
          e.printStackTrace();
        } finally {
          semaphore.release();
        }
      }, String.valueOf(i)).start();
    }

阻塞队列

BlockingQueue

抛出异常

特殊值

阻塞

超时

插入

Add(e)

Offer

put

Offer

移除

Remove()

Pull

Take

Pull

检查

Element()

Peak

SynchronousQueue

单个元素的队列,不消费不生产

代码语言:java
复制
    SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();

    new Thread(() -> {
      try {
        System.out.println(Thread.currentThread().getName() + "put aa");
        synchronousQueue.put("aa");
        System.out.println(Thread.currentThread().getName() + "put bb");
        synchronousQueue.put("bb");
        System.out.println(Thread.currentThread().getName() + "put cc");
        synchronousQueue.put("cc");
      }catch (Exception e) {
        e.printStackTrace();
      }
    }, "aaa").start();

    new Thread(() -> {
      try {
        TimeUnit.SECONDS.sleep(5);
        System.out.println(synchronousQueue.take());
        TimeUnit.SECONDS.sleep(5);
        System.out.println(synchronousQueue.take());
        TimeUnit.SECONDS.sleep(5);
        System.out.println(synchronousQueue.take());
      }catch (Exception e) {
        e.printStackTrace();
      }
    }, "bbb").start();

线程通信生产者消费者

代码语言:java
复制
class Sharedata {

  private int number = 0;
  private Lock lock = new ReentrantLock();
  private Condition condition = lock.newCondition();

  public void increment() {
    lock.lock();
    try {
      // 1.判断
      while (number != 0) {
        // 等待,不生产
        condition.await();
      }
      // 2.干活儿
      number++;
      System.out.println(Thread.currentThread().getName() + " 生产 " + number);
      // 3.唤醒
      condition.signal();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }

  public void decrement() {
    lock.lock();
    try {
      // 1.判断
      while (number == 0) {
        // 等待,不生产
        condition.await();
      }
      // 2.干活儿
      number--;
      System.out.println(Thread.currentThread().getName() + " 消费 " + number);
      // 3.唤醒
      condition.signal();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
}

多线程的判断用while if只适合两个线程的判断

synchronized和lock的区别

参考文章

存在层次上

synchronized: Java的关键字,在jvm层面上

Lock: 是一个接口

锁的释放

synchronized:

1、以获取锁的线程执行完同步代码,释放锁

2、线程执行发生异常,jvm会让线程释放锁

Lock: 在finally中必须释放锁,不然容易造成线程死锁

锁的获取

synchronized: 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待

Lock: 分情况而定,Lock有多个锁获取的方式,大致就是可以尝试获得锁,线程可以不用一直等待(可以通过tryLock判断有没有锁)

锁的释放(死锁产生)

synchronized: 在发生异常时候会自动释放占有的锁,因此不会出现死锁

Lock: 发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生

锁的状态

synchronized: 无法判断

Lock: 可以判断

锁的类型

synchronized: 可重入 不可中断 非公平

Lock: 可重入 可判断 可公平(两者皆可)

性能

synchronized: 少量同步

Lock: 大量同步

  • Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)
  • 在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
  • ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
调度

synchronized: 使用Object对象本身的wait 、notify、notifyAll调度机制

Lock: 可以使用Condition进行线程之间的调度

用法

synchronized: 在需要同步的对象中加入此控制,synchronized可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。

Lock: 一般使用ReentrantLock类做为锁。在加锁和解锁处需要通过lock()和unlock()显示指出。所以一般会在finally块中写unlock()以防死锁。

底层实现

synchronized: 底层使用指令码方式来控制锁的,映射成字节码指令就是增加来两个指令:monitorenter和monitorexit。当线程执行遇到monitorenter指令时会尝试获取内置锁,如果获取锁则锁计数器+1,如果没有获取锁则阻塞;当遇到monitorexit指令时锁计数器-1,如果计数器为0则释放锁。

Lock: 底层是CAS乐观锁,依赖AbstractQueuedSynchronizer类,把所有的请求线程构成一个CLH队列。而对该队列的操作均通过Lock-Free(CAS)操作。

循环打印

a,b,c三个线程的循环调用,每个线程分别打印10次,部分面试会考到

代码语言:java
复制
class Shareresource {

  // a=1,b=2,c=3
  private int number = 1;
  private Lock lock = new ReentrantLock();
  private Condition c1 = lock.newCondition();
  private Condition c2 = lock.newCondition();
  private Condition c3 = lock.newCondition();

  public void print5() {
    try {
      lock.lock();
      // 判断
      while (number != 1) {
        c1.await();
      }
      for (int i = 0; i < 5; i++) {
        System.out.println(Thread.currentThread().getName() + " " + i);
      }
      // 通知
      number = 2;
      c2.signal();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }

  public void print10() {
    try {
      lock.lock();
      while (number != 2) {
        c2.await();
      }
      for (int i = 0; i < 10; i++) {
        System.out.println(Thread.currentThread().getName() + " " + i);
      }
      // 通知
      number = 3;
      c3.signal();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }

  public void print15() {
    try {
      lock.lock();
      while (number != 3) {
        c3.await();
      }
      for (int i = 0; i < 15; i++) {
        System.out.println(Thread.currentThread().getName() + " " + i);
      }
      // 通知
      number = 1;
      c1.signal();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
}

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 可重入锁
    • synchronized
      • ReentrantLock
      • 自旋锁
      • 共享锁和独占锁
      • CountDownLatch
        • 使用原理
          • 使用场景
          • CyclicBarrier
          • Semaphore
          • 阻塞队列
          • SynchronousQueue
          • 线程通信生产者消费者
          • synchronized和lock的区别
            • 存在层次上
              • 锁的释放
                • 锁的获取
                  • 锁的释放(死锁产生)
                    • 锁的状态
                      • 锁的类型
                        • 性能
                          • 调度
                            • 用法
                              • 底层实现
                              • 循环打印
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档