java并发-显式锁Lock

174 阅读6分钟

Lock接口以及核心方法

  • lock
  • tryLock
  • unlock

使用范式如下

public class LockDemo {

    private Lock lcok = new ReentrantLock();

    private int count = 0;

    private void increament() {
        lcok.lock();
        try {
            count++;
        } finally {
            lcok.unlock();
        }
    }

    public synchronized void incr2() {
        count++;
        incr2();
    }

    public synchronized void test3() {
        incr2();
    }
    
}

Lock与synchronized关键字比较

  • synchronized
    • 代码简洁,非必须使用Lock场景建议使用
  • Lock
    • 获取锁时可用中断
    • 可以超时获取锁
    • 可以尝试获取锁
  • ReadWriteLock
    • 读多些少的场景

公平锁于非公平锁

  • ReentrantLock是可重入锁,即当线程释放锁之后,该线程仍然可以获取到刚刚释放的锁,常用在递归

  • 公平锁于非公平锁:在时间上,先对锁进行获取请求的一定先获得锁,一般非公平锁的效率要高一些

  • ReentrantLock有两个构造方法如下,默认为公平锁。

     public ReentrantLock() {sync = new NonfairSync();}
     public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync();}
    

ReadWriteLock 接口和读写锁ReentrantReadWriteLock

ReentrantLock与synchronized 都是排他锁,ReentrantReadWriteLock中的写锁为排他锁,读锁为共享锁。

读写锁:同一时刻允许多个读线程访问,但是在写线程访问的时候,此时所有的读线程与写线程都会被阻塞,使用与读多写少的情况

在ReentrantReadWriteLock本身并没有实现Lock,但是定义了两把锁,分别实现了Lock,如下:

    /** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;

在读多写少的情况下使用读些锁于synchronized比较

先写一个GoodsInfo类

package com.sakura.ch4.rw;

public class GoodsInfo {
    private final String name;

    private int number;
    private double money;

    public GoodsInfo(String name, int number, double money) {
        this.name = name;
        this.number = number;
        this.money = money;
    }

    public int getNumber() {
        return number;
    }

    public double getMoney() {
        return money;
    }

    public void change(int count) {
        this.money += count * 25;
        this.number -= count;
    }

}

定义一个操作接口GoodsService

public interface GoodsService {
    public GoodsInfo getNum();

    public void setNum(int count);
}

使用syhchronized

public class UseSyn implements GoodsService {

    private GoodsInfo goodsInfo;

    public UseSyn(GoodsInfo goodsInfo) {
        this.goodsInfo = goodsInfo;
    }

    @Override
    public synchronized GoodsInfo getNum() {
        SleepTools.ms(5);
        return this.goodsInfo;
    }

    @Override
    public synchronized void setNum(int count) {
        SleepTools.ms(5);
        this.goodsInfo.change(count);
    }
}

使用读些锁

public class UseRW implements GoodsService {
    private GoodsInfo goodsInfo;
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final Lock readLock = readWriteLock.readLock();
    private final Lock writeLock = readWriteLock.writeLock();

    public UseRW(GoodsInfo goodsInfo) {
        this.goodsInfo = goodsInfo;
    }


    @Override
    public GoodsInfo getNum() {
        readLock.lock();
        try {
            SleepTools.ms(5);
            return this.goodsInfo;
        } finally {
            readLock.unlock();
        }
    }

    @Override
    public void setNum(int count) {
        writeLock.lock();
        try {
            SleepTools.ms(5);
            goodsInfo.change(count);
        } finally {
            writeLock.unlock();
        }
    }
}

编写测试类

public class Test {
    static final int ReadWriteRatio = 10;
    static final int minWriteThread = 3;

    static CountDownLatch latch = new CountDownLatch(1);


    private static class ReadThread implements Runnable {

        private GoodsService goodsService;

        public ReadThread(GoodsService goodsService) {
            this.goodsService = goodsService;
        }

        @Override
        public void run() {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) {
                goodsService.getNum();
            }
            System.out.println(Thread.currentThread().getName() + " 获取数据花费" + (System.currentTimeMillis() - start) + "ms");
        }
    }

    private static class WriteThread implements Runnable {

        private GoodsService goodsService;

        public WriteThread(GoodsService goodsService) {
            this.goodsService = goodsService;
        }

        @Override
        public void run() {
            try {
                latch.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long start = System.currentTimeMillis();
            Random r = new Random();
            for (int i = 0; i < 10; i++) {
//                SleepTools.ms(50);
                goodsService.setNum(r.nextInt(10));
            }
            System.out.println(Thread.currentThread().getName() + " 写数据花费" + (System.currentTimeMillis() - start) + "ms");
        }
    }

    public static void main(String[] args) {
//       1⃣️ GoodsInfo sam = new GoodsInfo("sam", 10000, 100000);
//       2⃣️ GoodsService goodsService = new UseSyn(sam);
        GoodsService goodsService = new UseRW(sam);
        for (int i = 0; i < minWriteThread; i++) {
            Thread writeThread = new Thread(new WriteThread(goodsService));
            for (int j = 0; j < ReadWriteRatio; j++) {
                Thread readThread = new Thread(new ReadThread(goodsService));
                readThread.start();
            }
            SleepTools.ms(100);
            writeThread.start();
        }
        latch.countDown();
    }

}

若运行1⃣️,输出

Thread-27 获取数据花费1799ms
Thread-26 获取数据花费2402ms
Thread-25 获取数据花费3007ms
Thread-21 获取数据花费3960ms
Thread-19 获取数据花费4793ms
Thread-18 获取数据花费5406ms
Thread-17 获取数据花费6012ms
Thread-16 获取数据花费6637ms
Thread-14 获取数据花费7490ms
Thread-9 获取数据花费8359ms
Thread-7 获取数据花费8969ms
Thread-6 获取数据花费9818ms
Thread-5 获取数据花费10430ms
Thread-2 获取数据花费11811ms
Thread-3 获取数据花费11961ms
Thread-4 获取数据花费12260ms
Thread-10 获取数据花费12628ms
Thread-8 获取数据花费13218ms
Thread-13 获取数据花费13799ms
Thread-15 获取数据花费14181ms
Thread-20 获取数据花费14558ms
Thread-28 获取数据花费15240ms
Thread-30 获取数据花费15539ms
Thread-31 获取数据花费16090ms
Thread-32 获取数据花费16688ms
Thread-1 获取数据花费16910ms
Thread-24 获取数据花费17328ms
Thread-0 写数据花费17360ms
Thread-11 写数据花费17377ms
Thread-23 获取数据花费17598ms
Thread-12 获取数据花费17992ms
Thread-29 获取数据花费18313ms
Thread-22 写数据花费18321ms

若运行2⃣️输出

Thread-22 写数据花费66ms
Thread-0 写数据花费126ms
Thread-11 写数据花费192ms
Thread-9 获取数据花费769ms
Thread-3 获取数据花费769ms
Thread-15 获取数据花费769ms
Thread-6 获取数据花费769ms
Thread-5 获取数据花费769ms
Thread-21 获取数据花费768ms
Thread-10 获取数据花费769ms
Thread-1 获取数据花费769ms
Thread-8 获取数据花费769ms
Thread-17 获取数据花费768ms
Thread-4 获取数据花费769ms
Thread-2 获取数据花费769ms
Thread-14 获取数据花费769ms
Thread-7 获取数据花费769ms
Thread-13 获取数据花费769ms
Thread-19 获取数据花费774ms
Thread-16 获取数据花费774ms
Thread-32 获取数据花费774ms
Thread-25 获取数据花费774ms
Thread-26 获取数据花费774ms
Thread-28 获取数据花费774ms
Thread-31 获取数据花费774ms
Thread-12 获取数据花费775ms
Thread-29 获取数据花费774ms
Thread-27 获取数据花费774ms
Thread-18 获取数据花费774ms
Thread-30 获取数据花费774ms
Thread-20 获取数据花费774ms
Thread-24 获取数据花费774ms
Thread-23 获取数据花费781ms

Condition 接口

Condition接口主要的方法有

  • await()
  • signal()
  • signalAll()

类似于Object的wait(),notify(),notifyAll(),Objcet尽量使用notifyAll(),Condition尽量使用signal();

Condition的有边界的,例new ReentrantLock.newCondition().signalAll(),他的边界为这个Condition,而notifyAll()的边界在于这个对象,只要是这个阻塞在这个对象上的线程,都会通知。

使用condition改善快递通知

public class TestCond {
    private static Express express = new Express(100, Express.CITY);

    private static class CheckKm implements Runnable {

        @Override
        public void run() {
            express.waitKm();
        }
    }

    private static class CheckSite implements Runnable {
        @Override
        public void run() {
            express.waitSite();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new Thread(new CheckKm()).start();
        }
        for (int j = 0; j < 3; j++) {
            new Thread(new CheckSite()).start();
        }
        Thread.sleep(1000);
        express.changeKm();
    }
}

Express.class

public class Express {

    public static final String CITY = "shanghai";

    private int km;
    private String site;

    private Lock lock = new ReentrantLock();
    private Condition kmCond = lock.newCondition();
    private Condition siteCond = lock.newCondition();


    public Express(int km, String site) {
        this.km = km;
        this.site = site;
    }

    public void changeKm() {
        lock.lock();
        try {
            this.km = 101;
            kmCond.signal();//1⃣️
          //2⃣️	kmCond.signalAll();
        } finally {
            lock.unlock();
        }

    }

    public void changeSite() {
        lock.lock();
        try {

            this.site = "beijing";
            kmCond.signal();//1⃣️
          //2⃣️kmCond.signalAll()
        } finally {
            lock.unlock();
        }
    }

    public void waitSite() {
        lock.lock();
        try {
            while (CITY.equals(this.site)) {
                siteCond.await();
                System.out.println("checkSite  Thread [" + Thread.currentThread().getId() + " ] is be notified");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        System.out.println("the site is " + this.km + ",I will change db");
    }

    public void waitKm() {
        lock.lock();
        try {
            while (this.km <= 100) {
                kmCond.await();
                System.out.println("checkKm  Thread [" + Thread.currentThread().getId() + " ] is be notified");
            }
            kmCond.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        System.out.println("the Km is " + this.km + ",I will change db");
    }


}

运行1⃣️输出

checkKm  Thread [11 ] is be notified

运行2⃣️输出

checkKm  Thread [11 ] is be notified
checkKm  Thread [12 ] is be notified
checkKm  Thread [13 ] is be notified