JUC - 多线程锁

JUC - 多线程锁

    • 1、关于 Lock
    • 2、 Lock 和 Synchronized
      • 2.1 Lock 和 Synchronized 区别
    • 3、公平锁和非公平锁
    • 4、Lock 接口的实现类 ReentrantLock
    • 5、ReadWriteLock 接口的实现类 ReentrantReadWriteLock

1、关于 Lock

Lock 是 java.util.concurrent (JUC) 包中的一个接口类。

锁是一种通过多个线程控制对共享资源的访问的工具。通常,一个锁提供对共享资源的独占访问:在一个时间只有一个线程可以获得锁和所有访问共享资源,需要先获得锁。然而,有些锁可以允许并发访问共享资源,如一个ReadWriteLock读锁。

Lock 和 Synchronized 都可以达到锁的目的,不过 Lock 有更灵活的结构,有完全不同的特性,可以支持多个相关的 Condition对象

2、 Lock 和 Synchronized

  • 不加锁
package com.pzf.suo;

import lombok.AllArgsConstructor;
import java.util.concurrent.TimeUnit;

public class UnSync {
    public static void main(String[] args) {
        SaleTickets saleTicket = new SaleTickets(2);
        new Thread(() -> saleTicket.sale(), "A").start();
        new Thread(() -> saleTicket.sale(), "B").start();
        new Thread(() -> saleTicket.sale(), "C").start();
    }
}

@AllArgsConstructor
class SaleTickets {
    private int num;
    public void sale() {
        if (num > 0) {
          try {
             TimeUnit.SECONDS.sleep(2);
             System.out.println(Thread.currentThread().getName()+"-> 当前票数" + (num--)+",剩余票数" + num);
          } catch (Exception e) {
              e.printStackTrace();
          }
        }
    }
}
运行结果:
C-> 当前票数0,剩余票数-1
A-> 当前票数1,剩余票数0
B-> 当前票数2,剩余票数0
  • Synchronized 锁
package com.pzf.suo;

import lombok.AllArgsConstructor;
import java.util.concurrent.TimeUnit;

public class ToSync {
    public static void main(String[] args) {
        SaleTickets1 saleTicket = new SaleTickets1(2);
        new Thread(() -> saleTicket.sale(), "A").start();
        new Thread(() -> saleTicket.sale(), "B").start();
        new Thread(() -> saleTicket.sale(), "C").start();
    }
}

@AllArgsConstructor
class SaleTickets1 {
    private int num;
    // synchronized 修饰非静态方法,是对调用该方法的对象加锁,俗称"对象锁"
    public synchronized void sale() {
        if (num > 0) {
            try {
              TimeUnit.SECONDS.sleep(2);
              System.out.println(Thread.currentThread().getName()+"-> 当前票数" + (num--)+",剩余票数"+num);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
    }
}
运行结果:
A-> 当前票数2,剩余票数1
C-> 当前票数1,剩余票数0
  • Lock 锁
package com.pzf.suo;

import lombok.NoArgsConstructor;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ToLock {

    public static void main(String[] args) {
        SaleTickets2 saleTicket = new SaleTickets2(5);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> saleTicket.sale(), i + "").start();
        }
    }
}

@NoArgsConstructor
class SaleTickets2 {

    private int num;
    private Lock lock = new ReentrantLock();
    public SaleTickets2(int num) {
        this.num = num;
    }

    public void sale() {
        lock.lock();
        try {
            if (num > 0) {
              TimeUnit.SECONDS.sleep(1);
              System.out.println(Thread.currentThread().getName()+"-> 当前票数"+(num--)+",剩余票数" + num);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 手动释放锁
            lock.unlock();
        }


    }
}

运行结果:
0-> 当前票数5,剩余票数4
2-> 当前票数4,剩余票数3
1-> 当前票数3,剩余票数2
3-> 当前票数2,剩余票数1
4-> 当前票数1,剩余票数0

 

2.1 Lock 和 Synchronized 区别

 

  • Lock 是 java.util.concurrent (JUC) 包中的一个类,Synchronized 是一个内置的 java 关键字。
  • Lock 可以判断是否获取到了锁,Synchronized 无法判断获取锁的状态。
  • Lock 必须要手动释放锁,如果不释放锁会产生死锁。Synchronized 会自动释放锁。
  • Lock 是可重入锁,默认为非公平锁,但是可以设置为公平锁。 Synchronized 可重入锁,不可中断的非公平锁。
  • Lock 线程1(获得锁,阻塞)、线程2(不一定等待)。Synchronized 线程2 会等待下去。
  • Lock 适合锁大量的同步代码,Synchronized 适合少量的代码同步问题。

 

3、公平锁和非公平锁

 
公平锁 :每个线程获取锁的顺序是按照线程访问锁的先后顺序获取的,最前面的线程总是最先获取到锁。

非公平锁:每个线程获取锁的顺序是随机的,并不会遵循先来先得的规则,所有线程会竞争获取锁。

两种锁的执行流程

  • 公平锁:
    获取锁时,先将线程自己添加到等待队列的队尾并休眠,当某线程用完锁之后,会去唤醒等待队列中队首的线程尝试去获取锁,锁的使用顺序也就是队列中的先后顺序,在整个过程中,线程会从运行状态切换到休眠状态,再从休眠状态恢复成运行状态,但线程每次休眠和恢复都需要从用户态转换成内核态,而这个状态的转换是比较慢的,所以公平锁的执行速度会比较慢。
     
  • 非公平锁:
    当线程获取锁时,会先通过 CAS 尝试获取锁,如果获取成功就直接拥有锁,如果获取锁失败才会进入等待队列,等待下次尝试获取锁。这样做的好处是,获取锁不用遵循先到先得的规则,从而避免了线程休眠和恢复的操作,这样就加速了程序的执行效率。

4、Lock 接口的实现类 ReentrantLock

 
ReentrantLock 重入锁和 synchronize 关键字一样,是互斥锁。比synchronize关键字更加灵活。
ReentrantLock 默认为非公平锁,构造函数传 true 时,可设置为公平锁。
 

JUC - 多线程锁_第1张图片

ReentrantLock 实现类相关方法说明
 
JUC - 多线程锁_第2张图片
 

5、ReadWriteLock 接口的实现类 ReentrantReadWriteLock

 

  • ReentrantLock虽然可以灵活地实现线程安全,但是他是一种完全互斥锁,即某一时刻永远只允许一个线程访问共享资源,不管是读数据的线程还是写数据的线程。这导致的结果就是,效率低下。ReentrantReadWriteLock 类很好的解决了该问题。
  • ReentrantReadWriteLock 类实现了 ReadWriteLock 接口,而 ReadWriteLock 接口中维护了两个锁:读锁(共享锁)和写锁(排他锁)。
  • ReentrantReadWriteLock 允许线程同时读取共享资源;但是如果有一个线程是写数据,那么其他线程就不能去读写该资源。即会出现三种情况:读读共享,写写互斥,读写互斥。

 

ReentrantReadWriteLock 主要特性 公平性 支持公平锁和非公平锁,默认是非公平锁,可以根据构造方式设置公平锁。
// 构造函数默认是false 非公平锁,由于读线程之间没有锁竞争,所以读操作,没有公平性和非公平性。
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true);

JUC - 多线程锁_第3张图片
 
ReentrantReadWriteLock 实现类相关方法说明
 
JUC - 多线程锁_第4张图片

  • 例子
package com.pzf.lock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 独占锁(写锁)  一次只能被一个线程占有
 * 共享锁(读锁)  多个线程可以同时占有
 * 

* ReadWriteLock 实现类 ReentrantReadWriteLock *

* 读-读 可以共存 * 读-写 不能共存 * 写-写 不能共存 */ public class ReadWriteLockDemo { public static void main(String[] args) { MyCache myCache = new MyCache(); // 普通写 for (int i = 1; i <= 5; i++) { final int temp = i; new Thread(() -> {myCache.put(temp + "", temp + "");}, String.valueOf(i)).start(); } // 普通读 for (int i = 1; i <= 5; i++) { final int temp = i; new Thread(() -> {myCache.get(temp + "");}, String.valueOf(i)).start(); } try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(); MyLockCache myLockCache = new MyLockCache(); // 读写锁 -> 写 for (int i = 6; i <= 10; i++) { final int temp = i; new Thread(() -> {myLockCache.put(temp + "", temp + "");}, String.valueOf(i)).start(); } // 读写锁 -> 读 for (int i = 6; i <= 10; i++) { final int temp = i; new Thread(() -> {myLockCache.get(temp + "");}, String.valueOf(i)).start(); } } } class MyCache { private volatile Map<String, Object> map = new HashMap<>(); public void put(String key, Object value) { System.out.println(Thread.currentThread().getName() + "-> key = " + key + "开始写入"); map.put(key, value); System.out.println(Thread.currentThread().getName() + "-> key = " + key + "写入成功"); } public Object get(String key) { System.out.println(Thread.currentThread().getName() + "-> key = " + key + "开始读"); Object value = map.get(key); System.out.println(Thread.currentThread().getName() + "-> key = " + key + "读成功"); return value; } } class MyLockCache { private volatile Map<String, Object> map = new HashMap<>(); private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public void put(String key, Object value) { readWriteLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + "-> key = " + key + "开始写入"); map.put(key, value); System.out.println(Thread.currentThread().getName() + "-> key = " + key + "写入成功"); } catch (Exception e) { e.printStackTrace(); } finally { readWriteLock.writeLock().unlock(); } } public Object get(String key) { readWriteLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + "-> key = " + key + "开始读"); Object value = map.get(key); System.out.println(Thread.currentThread().getName() + "-> key = " + key + "读成功"); return value; } catch (Exception e) { e.printStackTrace(); } finally { readWriteLock.readLock().unlock(); } return null; } }

  • 执行结果:
2-> key = 2开始写入
2-> key = 2写入成功
1-> key = 1开始写入
5-> key = 5开始写入
5-> key = 5写入成功
3-> key = 3开始写入
3-> key = 3写入成功
4-> key = 4开始写入
4-> key = 4写入成功
1-> key = 1开始读
1-> key = 1读成功
1-> key = 1写入成功
2-> key = 2开始读
2-> key = 2读成功
3-> key = 3开始读
4-> key = 4开始读
4-> key = 4读成功
5-> key = 5开始读
3-> key = 3读成功
5-> key = 5读成功



7-> key = 7开始写入
7-> key = 7写入成功
10-> key = 10开始写入
10-> key = 10写入成功
6-> key = 6开始写入
6-> key = 6写入成功
8-> key = 8开始写入
8-> key = 8写入成功
9-> key = 9开始写入
9-> key = 9写入成功
6-> key = 6开始读
6-> key = 6读成功
10-> key = 10开始读
8-> key = 8开始读
7-> key = 7开始读
8-> key = 8读成功
9-> key = 9开始读
9-> key = 9读成功
10-> key = 10读成功
7-> key = 7读成功

你可能感兴趣的:(线程系列,JUC,ReadWriteLock,Lock,公平锁和非公平锁)