Java并发编程显式锁

显式锁 

有了 synchronized 为什么还要 Lock? Java 程序是靠 synchronized 关键字实现锁功能的,使用 synchronized 关键字 将会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先获取再释放。

Synchronized 关键字结合对象的监视器,JVM 为我们提供了一种『内置锁』的语义,这种锁很简便,不需要我们关心加锁和释放锁的过程,我们只需要告诉虚拟机哪些代码块需要加锁即可,其他的细节会由编译器和虚拟机自己实现。

可以将我们的『内置锁』理解为是 JVM 的一种内置特性,它不支持某些高级功能的定制,比如说,我想要这个锁支持公平竞争,我想要根据不同的条件将线程阻塞在不同的队列上,我想要支持定时竞争锁,超时返回,我还想让被阻塞的线程能够响应中断请求等。

这些特殊的需求是『内置锁』满足不了的,所以在 JDK 层面又引入了『显式锁』的概念,不再由 JVM 来负责加锁和释放锁,这两个动作释放给我们程序来做,程序层面难免复杂了些,但锁灵活性提高了,可以支持更多定制功能,但要求你对锁具有更深层次的理解。

Lock的标准用法

在 finally 块中释放锁,目的是保证在获取到锁之后,最终能够被释放。 不要将获取锁的过程写在 try 块中,因为如果在获取锁(自定义锁的实现) 时发生了异常,异常抛出的同时,也会导致锁无故释放。

Lock的常用API

注:

无特殊需求(线程取锁等待中断),推荐使用synchronized关键字,从资源消耗上来讲,Lock是一个类,需要消耗内存,而synchronized关键字是程序的特写,无需new出一个对象,资源相对消耗较小。

ReentrantLock   可重入锁 (Lock的实现类)

锁的可重入

简单地讲就是:“同一个线程对于已经获得到的锁,可以多次继续申请到该 锁的使用权”。而 synchronized 关键字隐式的支持重进入,比如一个 synchronized 修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得 该锁。ReentrantLock 在调用 lock()方法时,已经获取到锁的线程,能够再次调用 lock()方法获取锁而不被阻塞

公平和非公平锁

如果在时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的, 反之,是不公平的。公平的获取锁,也就是等待时间最长的线程最优先获取锁, 也可以说锁获取是顺序的。 ReentrantLock 提供了一个构造函数,能够控制锁是 否是公平的。事实上,公平的锁机制往往没有非公平的效率高。 在激烈竞争的情况下,非公平锁的性能高于公平锁的性能的一个原因是:在恢 复一个被挂起的线程与该线程真正开始运行之间存在着严重的延迟。假设线程 A 持有一个锁,并且线程 B 请求这个锁。由于这个锁已被线程 A 持有,因此 B 将被挂 起。当 A 释放锁时,B 将被唤醒,因此会再次尝试获取锁。与此同时,如果 C 也请求 这个锁,那么 C 很可能会在 B 被完全唤醒之前获得、使用以及释放这个锁。这样 的情况是一种“双赢”的局面:B 获得锁的时刻并没有推迟,C 更早地获得了锁,并 且吞吐量也获得了提高。

注 :ReentrantLock 默认是非公平锁,ReentrantLock 通过构造方法可设置公平锁,synchronized是为非公平锁,非公平锁的性能要比公平锁的高, ReentrantLock 以及 synchronized也叫独占锁

ReentrantLock 代码演示

/**
 * 类说明:使用Lock的范例
 */
public class LockCase {
    private Lock lock = new ReentrantLock();
    private int age = 100000;//初始100000

    private static class TestThread extends Thread {

        private LockCase lockCase;

        public TestThread(LockCase lockCase, String name) {
            super(name);
            this.lockCase = lockCase;
        }

        @Override
        public void run() {
            for (int i = 0; i < 100000; i++) {//递增100000
                lockCase.test();
            }
            System.out.println(Thread.currentThread().getName()
                    + " age =  " + lockCase.getAge());
        }
    }

    public void test() {
        lock.lock();
        try {
            age++;
        } finally {
            lock.unlock();
        }
    }

    public void test2() {
        lock.lock();
        try {
            age--;
        } finally {
            lock.unlock();
        }
    }

    public int getAge() {
        return age;
    }


    public static void main(String[] args) throws InterruptedException {
        LockCase lockCase = new LockCase();
        Thread endThread = new TestThread(lockCase, "endThread");
        endThread.start();
        for (int i = 0; i < 100000; i++) {//递减100000
            lockCase.test2();
        }
        System.out.println(Thread.currentThread().getName()
                + " age =  " + lockCase.getAge());

    }
}

读写锁ReentrantReadWriteLock

之前提到锁(如 Mutex 和 ReentrantLock)基本都是排他锁,这些锁在同一 时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问, 但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对 锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁 有了很大提升。 

除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁能够简化 读写交互场景的编程方式。假设在程序中定义一个共享的用作缓存数据结构,它 大部分时间提供读服务(例如查询和搜索),而写操作占有的时间很少,但是写 操作完成之后的更新需要对后续的读服务可见。 

在没有读写锁支持的(Java5 之前)时候,如果需要完成上述工作就要使用 Java 的等待通知机制,就是当写操作开始时,所有晚于写操作的读操作均会进入 等待状态,只有写操作完成并进行通知之后,所有等待的读操作才能继续执行(写 操作之间依靠 synchronized 关键进行同步),这样做的目的是使读操作能读取到 正确的数据,不会出现脏读。改用读写锁实现上述功能,只需要在读操作时获取 读锁,写操作时获取写锁即可。当写锁被获取到时,后续(非当前写操作线程) 的读写操作都会被阻塞,写锁释放之后,所有操作继续执行,编程方式相对于使 用等待通知机制的实现方式而言,变得简单明了。 

一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。 在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量 

ReentrantReadWriteLock 其实实现的是 ReadWriteLock 接口

废话不多说上代码

实现读写锁的逻辑

/**
 * 类说明:  读写锁
 */
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);
            //TODO 我们平时的查询接口
            return this.goodsInfo;
        }finally {
            getLock.unlock();
        }
    }

    @Override
    public void setNum(int number) {
        //获取写锁
        setLock.lock();
        try{
            SleepTools.ms(5);
            //TODO 我们平时的添加接口
            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);
        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();
        }
    }
}

Condition接口

Condition常 用 方 法

Condition使 用 范 式

Condition使 用

与 等待通知的范式wait()、notify()相似

/**
 *类说明:Condition等待通知演示
 */
public class ExpressCond {
    public final static String CITY = "ShangHai";
    private int km;/*快递运输里程数*/
    private String site;/*快递到达地点*/
    private Lock kmLock = new ReentrantLock();
    private Lock siteLock = new ReentrantLock();
    private Condition kmCond = kmLock.newCondition();
    private Condition siteCond = siteLock.newCondition();

    public ExpressCond() {
    }

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

    /* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/
    public void changeKm(){
        kmLock.lock();
        try{
            this.km = 101;
            kmCond.signal();
            //kmCond.signalAll();
        }finally {
            kmLock.unlock();
        }
        
        
    }

    /* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/
    public  void changeSite(){
    	siteLock.lock();
    	try {
    		this.site = "BeiJing";
    		siteCond.signal();//通知其他在锁上等待的线程
    	}finally {
    		siteLock.unlock();
    	}
    }

    /*当快递的里程数大于100时更新数据库*/
    public void waitKm(){
        kmLock.lock();
        try{
            while(this.km<100){
                try {
                    kmCond.await();
                    System.out.println("Check Site thread["
                            +Thread.currentThread().getId()
                            +"] is be notified");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }finally {
            kmLock.unlock();
        }


        System.out.println("the Km is "+this.km+",I will change db");
    }

    /*当快递到达目的地时通知用户*/
    public void waitSite(){
    	siteLock.lock();
    	try {
        	while(this.site.equals(CITY)) {
        		try {
    				siteCond.await();//当前线程进行等待
    				System.out.println("check Site thread["+Thread.currentThread().getName()
    						+"] is be notify");
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
        	}
    	}finally {
    		siteLock.unlock();
    	}

        System.out.println("the site is "+this.site+",I will call user");
    }
}
/**
 *类说明:单锁的实现 等待通知
 */
public class ExpressCondOneLock {
    public final static 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 ExpressCondOneLock() {
    }

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

    /* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/
    public void changeKm(){
		lock.lock();
    	try {
    		this.km = 101;
    		kmCond.signal();//通知其他在锁上等待的线程
    	}finally {
			lock.unlock();
    	}
        
        
    }

    /* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/
    public  void changeSite(){
		lock.lock();
    	try {
    		this.site = "BeiJing";
    		siteCond.signal();//通知其他在锁上等待的线程
    	}finally {
			lock.unlock();
    	}    	
    }

    /*当快递的里程数大于100时更新数据库*/
    public void waitKm(){
		lock.lock();
    	try {
        	while(this.km<100) {
        		try {
        			kmCond.await();//当前线程进行等待
    				System.out.println("check km thread["+Thread.currentThread().getName()
    						+"] is be notify");
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
        	}    		
    	}finally {
			lock.unlock();
    	}

        System.out.println("the Km is "+this.km+",I will change db");
    }

    /*当快递到达目的地时通知用户*/
    public void waitSite(){
		lock.lock();
    	try {
        	while(this.site.equals(CITY)) {
        		try {
    				siteCond.await();//当前线程进行等待
    				System.out.println("check Site thread["+Thread.currentThread().getName()
    						+"] is be notify");
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}   		
        	}
    	}finally {
			lock.unlock();
    	}      	

        System.out.println("the site is "+this.site+",I will call user");
    }
}

最后为测试类

/**
 * 类说明:测试Lock和Condition实现等待通知
 */
public class TestCond {
    private static ExpressCond express = new ExpressCond(0, ExpressCond.CITY);

    /*检查里程数变化的线程,不满足条件,线程一直等待*/
    private static class CheckKm extends Thread {
        @Override
        public void run() {
            express.waitKm();
        }
    }

    /*检查地点变化的线程,不满足条件,线程一直等待*/
    private static class CheckSite extends Thread {
        @Override
        public void run() {
            express.waitSite();
        }
    }

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

        Thread.sleep(1000);
        express.changeKm();//快递里程变化
    }
}
发布了18 篇原创文章 · 获赞 4 · 访问量 145

猜你喜欢

转载自blog.csdn.net/weixin_42081445/article/details/105000279