JAVA并发编程-4-显式锁Lock

上一章看这里:JAVA并发编程-3-原子操作CAS和原子类

一、Lock接口及其核心方法

JAVA从1.5开始新增了Lock接口,这里不得不提一位java并发大师Doug Lea,大家可以发现JUC包下很多类都是来自这位大神的编码,是当之无愧的JAVA并发大师,他现任纽约州立大学教授。

Lock接口中的方法如下:

public interface Lock {

    void lock();
    /**
     * 尝试加锁
     **/
    boolean tryLock();

	/**
	  * 可中断锁
	  **/
    void lockInterruptibly() throws InterruptedException;
    
    /**
      * 超时尝试加锁
      **/
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException
    
    void unlock();

    Condition newCondition();
}

从上面我们可以看到Lock相比synchronized关键字提供了更加丰富的方法来支持加锁,主要包括:

  1. 可以被中断 lockInterruptibly()
  2. 可以尝试获取 tryLock()
  3. 可以超时获取 tryLock(long time, TimeUnit unit)
  4. 读多写少时可用支持读写锁

上面几条是synchronized所不能完成的,这也是Lock存在的意义,而synchronized的使用是比较简洁的。所以除非我们有以上几点的需求,否则应该优先选用synchronized关键字来加锁。
synchronized关键字的性能也是随着java语言的版本迭代不断被优化,所以普通的加锁场景synchronized的性能或许更好。

二、可重入锁ReentrantLock

java为我们提供了Lock接口的一个重要实现ReentrantLock可重入锁。
所谓可重入锁,就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。换言之就是可以获取锁的多次。

比如,递归获取同一把锁

  public synchronized void change() {
        i = 1;
        change();
    }

或者调用其它方法

    public synchronized void change() {
        i = 1;
        changeSingle();
    }
    public synchronized void changeSingle() {
        i = 1;
    }

synchronized和ReentrantLock都是典型的可重入锁。可重入锁每进入一次锁会有计数器将加锁次数加1,解锁后将锁的次数减1。

对于Lock的使用,请一定要在finally中调用unlock方法,以确保一定会解锁。

     if (lock.tryLock()) {
        try {
          // manipulate protected state
        } finally {
          lock.unlock();
        }

简单的demo:

public class LockDemo {
    private Lock lock = new ReentrantLock();
    private int count;

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

三、锁的公平和非公平

所谓锁的公平和非公平,面试的时候经常会被问到
如果在时间上,先对锁进行获取的请求,一定先被满足,这个锁就是公平的,不满足,就是非公平的。
就是一定会保证先来拿锁的等待线程先获得锁,而非公平锁不会保证这一点。

ReentrantLock的构造方法中提供了是否是公平锁参数:

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

值得一提的是,通常认为非公平锁的效率是要比公平锁高的。假设a线程获取到了锁,b线程在等待,a线程释放锁的时候恰好c线程过来拿锁,那么在非公平锁的情况下就极有可能让c线程拿到锁,让b线程继续等待,因为这样总体的开销最小,效率最高。

四、读写锁ReadWriteLock

锁有排他锁和共享锁之分。

  • 排他锁:无论如何只能有一个线程拿到锁,ReentrantLock,synchronized
  • 共享锁:允许多个相同业务的线程拿到同一把锁,读写锁就是同一时刻允许多个读线程同时访问

读写锁就是同一时刻允许多个读线程同时访问,但是写线程访问时,所有的读和写都被阻塞,最适合读多写少的情况。

读写锁实际上是通过提供读锁和写锁两把锁来实现的。JAVA中提供了ReadWriteLock读写锁接口,并且提供了一个实现类ReentrantReadWriteLock,下图中可看到定义了两把锁ReadLock和WriteLock,两把锁分别实现了Lock接口
在这里插入图片描述
通过一个简单的雷子来看下读写锁和synchronized的性能比较:

定义一个商品信息类:

    public  class GoodsInfo {
        private final String name;
        private double totalMoney;//总销售额
        private int storeNumber;//库存数

        public GoodsInfo(String name, int totalMoney, int storeNumber) {
            this.name = name;
            this.totalMoney = totalMoney;
            this.storeNumber = storeNumber;
        }

        public double getTotalMoney() {
            return totalMoney;
        }
        public int getStoreNumber() {
            return storeNumber;
        }
        public void changeNumber(int sellNumber) {
            this.totalMoney += sellNumber * 25;
            this.storeNumber -= sellNumber;
        }
    }

一个商品的服务的接口:

public interface GoodsService {

    GoodsInfo getNum();//获得商品的信息

    void setNum(int number);//设置商品的数量
}

synchronized的实现:

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 number) {
		SleepTools.ms(5);
		goodsInfo.changeNumber(number);
	}

}

读写锁实现:

public class UseRwLock implements GoodsService {
	
    private GoodsInfo goodsInfo;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock getLock = lock.readLock();//读锁
    private final Lock setLock = lock.writeLock();//写锁

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

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

	@Override
	public void setNum(int number) {
		setLock.lock();
		try {
			SleepTools.ms(5);
			goodsInfo.changeNumber(number);
		}finally {
			setLock.unlock();
		}
	}
}

测试类:

public class BusiApp {
    static final int readWriteRatio = 10;//读写线程的比例
    static final int minthreadCount = 3;//最少线程数

    //读操作
    private static class GetThread implements Runnable {

        private GoodsService goodsService;

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

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) {//操作100次
                goodsService.getNum();
            }
            System.out.println(Thread.currentThread().getName() + "读取商品数据耗时:" + (System.currentTimeMillis() - start) + "ms");

        }
    }

    //写操做
    private static class SetThread implements Runnable {

        private GoodsService goodsService;

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

        @Override
        public void run() {
            long start = System.currentTimeMillis();
            Random r = new Random();
            for (int i = 0; i < 10; i++) {//操作10次
                SleepTools.ms(50);
                goodsService.setNum(r.nextInt(10));
            }
            System.out.println(Thread.currentThread().getName() + "写商品数据耗时:" + (System.currentTimeMillis() - start) + "ms---------");

        }
    }

    public static void main(String[] args) throws InterruptedException {
        GoodsInfo goodsInfo = new GoodsInfo("Cup", 100000, 10000);
        GoodsService goodsService = new UseRwLock(goodsInfo);
//        GoodsService goodsService = new UseSyn(goodsInfo);
        for (int i = 0; i < minthreadCount; i++) {
            Thread setT = new Thread(new SetThread(goodsService));
            for (int j = 0; j < readWriteRatio; j++) {
                Thread getT = new Thread(new GetThread(goodsService));
                getT.start();
            }
            SleepTools.ms(100);
            setT.start();
        }
    }
}

synchronized的执行结果:
在这里插入图片描述
读写锁执行结果:
在这里插入图片描述
由此可见,在读多写少的情况下,读写锁可以大大提高性能

五、等待通知机制Condition

与线程的wait()和notify()和synchronized相配合实现的等待和通知机制相对应,显示锁Lock中也实现了等待通知机制,就是通过Condition接口来实现。
在这里插入图片描述
await() 是为进入等待,对应Object中的wait
signal(),signalAll()对应notify()和notifyAll

而它们的使用同样遵守wait()和notify()的标准范式


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

    public void dowait() {
        lock.lock();
        try {
            //doSomething
            condition.await();
        } catch (Exception e) {

        } finally {
            lock.unlock();
        }
    }

    public void donotify() {
        lock.lock();
        try {
            //doSomething
            condition.signal();
        } catch (Exception e) {

        } finally {
            lock.unlock();
        }
    }

大家可以试着将第一章中的wait和notify的事例用Condition再实现一遍。

下一章看这里:JAVA并发编程-5-AQS的实现原理

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值