七 内置锁 wait notify notifyall; 显示锁 ReentrantLock
Object中对内置锁进行操作的一些方法:
Java内置锁通过synchronized关键字使用,使用其修饰方法或者代码块,就能保证方法或者代码块以同步方式执行.
内置锁使用起来非常方便,不需要显式的获取和释放,任何一个对象都能作为一把内置锁。使用内置锁能够解决大部分的同步场景。“任何一个对象都能作为一把内置锁”也意味着出现synchronized关键字的地方,都有一个对象与之关联,具体说来:
- 当synchronized作用于普通方法是,锁对象是this;
- 当synchronized作用于静态方法是,锁对象是当前类的Class对象;
- 当synchronized作用于代码块时,锁对象是synchronized(obj)中的这个obj。
wait()系列:
wait()系列方法的作用是:使当前已经获得该对象锁的线程进入等待状态,并且释放该对象的锁。
notify()系列:
notify()系列方法的作用是:唤醒那些正在等待该对象锁的线程,使其继续运行。
基于wait() notify()机制,我们可以实现一个简易的生产者-消费者模型。
大体思路如下,一个生产者线程负责向一个仓库中存放(put)物品,一个消费者线程负责从仓库中取出(get)物品。
public class Warehouse {
    private Queue<Integer> queue;
    private int capacity;
    public Warehouse(int capacity) {
        this.capacity = capacity;
        queue = new LinkedList();
    }
    public synchronized void put(int num) {
        if (queue.size() >= capacity) {
            try {
                System.out.println(Thread.currentThread().getName() + " , put full wait");
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        queue.add(num);
        System.out.println(Thread.currentThread().getName() + " , put : " + num + "  , queue -> " + queue);
        notifyAll();
    }
    public synchronized int get() {
        if (queue.isEmpty()) {
            try {
                System.out.println(Thread.currentThread().getName() + " , get empty wait");
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        int num = queue.poll();
        System.out.println(Thread.currentThread().getName() + " , get : " + num + "  , queue -> " + queue);
        notifyAll();
        return num;
    }
}
public static void main(String[] args) {
        Warehouse warehouse = new Warehouse();
        Random random = new Random();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    warehouse.put(random.nextInt());
                    try {
                        Thread.sleep();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "生产者-01").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    warehouse.get();
                    try {
                        Thread.sleep();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }, "消费者-01").start();
    }
显式锁
内置锁这么好用,为什么还需多出一个显式锁呢?因为有些事情内置锁是做不了的,比如:
- 我们想给锁加个等待时间超时时间,超时还未获得锁就放弃,不至于无限等下去;
- 我们想以可中断的方式获取锁(线程可以被打断),这样外部线程给我们发一个中断信号就能唤起等待锁的线程;
- 我们想为锁维持多个等待队列,比如一个生产者队列,一个消费者队列,一边提高锁的效率。
显式锁(ReentrantLock)正式为了解决这些灵活需求而生。ReentrantLock的字面意思是可重入锁,可重入的意思是线程可以同时多次请求同一把锁,而不会自己导致自己死锁。下面是内置锁和显式锁的区别:
- 可定时: - RenentrantLock.tryLock(long timeout, TimeUnit unit)提供了一种以定时结束等待的方式,如果线程在指定的时间内没有获得锁,该方法就会返回false并结束线程等待。
- 可中断:你一定见过InterruptedException,很多跟多线程相关的方法会抛出该异常,这个异常并不是一个缺陷导致的负担,而是一种必须,或者说是一件好事。可中断性给我们提供了一种让线程提前结束的方式(而不是非得等到线程执行结束),这对于要取消耗时的任务非常有用。对于内置锁,线程拿不到内置锁就会一直等待,除了获取锁没有其他办法能够让其结束等待。 - RenentrantLock.lockInterruptibly()给我们提供了一种以中断结束等待的方式。
- 条件队列(condition queue):线程在获取锁之后,可能会由于等待某个条件发生而进入等待状态(内置锁通过 - Object.wait()方法,显式锁通过- Condition.await()方法),进入等待状态的线程会挂起并自动释放锁,这些线程会被放入到条件队列当中。synchronized对应的只有一个条件队列,而ReentrantLock可以有多个条件队列,多个队列有什么好处呢?请往下看。
- 条件谓词:线程在获取锁之后,有时候还需要等待某个条件满足才能做事情,比如生产者需要等到“缓存不满”才能往队列里放入消息,而消费者需要等到“缓存非空”才能从队列里取出消息。这些条件被称作条件谓词,线程需要先获取锁,然后判断条件谓词是否满足,如果不满足就不往下执行,相应的线程就会放弃执行权并自动释放锁。使用同一把锁的不同的线程可能有不同的条件谓词,如果只有一个条件队列,当某个条件谓词满足时就无法判断该唤醒条件队列里的哪一个线程;但是如果每个条件谓词都有一个单独的条件队列,当某个条件满足时我们就知道应该唤醒对应队列上的线程(内置锁通过 - Object.notify()或者- Object.notifyAll()方法唤醒,显式锁通过- Condition.signal()或者- Condition.signalAll()方法唤醒)。这就是多个条件队列的好处。
使用内置锁时,对象本身既是一把锁又是一个条件队列;使用显式锁时,RenentrantLock的对象是锁,条件队列通过RenentrantLock.newCondition()方法获取,多次调用该方法可以得到多个条件队列。
一个使用显式锁的典型示例如下:
// 显式锁的使用示例
ReentrantLock lock = new ReentrantLock(); // 获取锁,这是跟synchronized关键字对应的用法。
lock.lock();
try{
// your code
}finally{
lock.unlock();
} // 可定时,超过指定时间为得到锁就放弃
try {
lock.tryLock(, TimeUnit.SECONDS);
try {
// your code
}finally {
lock.unlock();
}
} catch (InterruptedException e1) {
// exception handling
} // 可中断,等待获取锁的过程中线程线程可被中断
try {
lock.lockInterruptibly();
try {
// your code
}finally {
lock.unlock();
}
} catch (InterruptedException e) {
// exception handling
} // 多个等待队列,具体参考[ArrayBlockingQueue](https://github.com/CarpenterLee/JCRecipes/blob/master/markdown/ArrayBlockingQueue.md)
/** Condition for waiting takes */
private final Condition notEmpty = lock.newCondition();
/** Condition for waiting puts */
private final Condition notFull = lock.newCondition();
注意,上述代码将unlock()放在finally块里,这么做是必需的。显式锁不像内置锁那样会自动释放,使用显式锁一定要在finally块中手动释放,如果获取锁后由于异常的原因没有释放锁,那么这把锁将永远得不到释放!将unlock()放在finally块中,保证无论发生什么都能够正常释放。
内置锁能够解决大部分需要同步的场景,只有在需要额外灵活性是才需要考虑显式锁,比如可定时、可中断、多等待队列等特性。
显式锁虽然灵活,但是需要显式的申请和释放,并且释放一定要放到finally块中,否则可能会因为异常导致锁永远无法释放!这是显式锁最明显的缺点。
综上,当需要同步时请优先考虑更安全的更易用的隐式锁。
package com.imooc.locks; import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class Task { private final Lock lock = new ReentrantLock(); private final Condition addCondition = lock.newCondition(); private final Condition subCondition = lock.newCondition(); private static int num = ;
private List<String> lists = new LinkedList<String>(); public void add() {
lock.lock(); try {
while(lists.size() == ) {//当集合已满,则"添加"线程等待
addCondition.await();
} num++;
lists.add("add Banana" + num);
System.out.println("The Lists Size is " + lists.size());
System.out.println("The Current Thread is " + Thread.currentThread().getName());
System.out.println("==============================");
this.subCondition.signal(); } catch (InterruptedException e) {
e.printStackTrace();
} finally {//释放锁
lock.unlock();
}
} public void sub() {
lock.lock(); try {
while(lists.size() == ) {//当集合为空时,"减少"线程等待
subCondition.await();
} String str = lists.get();
lists.remove();
System.out.println("The Token Banana is [" + str + "]");
System.out.println("The Current Thread is " + Thread.currentThread().getName());
System.out.println("==============================");
num--;
addCondition.signal(); } catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} }
七 内置锁 wait notify notifyall; 显示锁 ReentrantLock的更多相关文章
- python中的内置函数,递归,递归文件显示(二),二分法
		1.部分内置函数 repr()显示出字符串的官方表示形式,返回一个对象的string形式 # repr 就是原封不动的输出, 引号和转义字符都不起作用 print(repr('大家好,\n \t我叫周 ... 
- PHP限制网页只能在微信内置浏览器中查看并显示
		微信现在算是火了,围绕微信开发的应用也越来越多了,前段时间,自己公司需要,用PHP写了一个微信应用,为了防止自己辛苦写成的PHP应用被盗用,于是 通过PHP做了限制,只能在微信自带的浏览器中才能打开本 ... 
- java多线程14 :wait()和notify()/notifyAll()
		轮询 线程本身是操作系统中独立的个体,但是线程与线程之间不是独立的个体,因为它们彼此之间要相互通信和协作. 想像一个场景,A线程做int型变量i的累加操作,B线程等待i到了10000就打印出i,怎么处 ... 
- 显示锁lock
		一.内置锁sync 和 显示锁lock概念 1.synv锁又叫内置锁,不能中断,拿不到无限等待即阻塞: java自带关键字: 隐式可重入: 重入锁:锁对应对象要多次调用对应方法,如递归 2. lock ... 
- Java多线程8:wait()和notify()/notifyAll()
		轮询 线程本身是操作系统中独立的个体,但是线程与线程之间不是独立的个体,因为它们彼此之间要相互通信和协作. 想像一个场景,A线程做int型变量i的累加操作,B线程等待i到了10000就打印出i,怎么处 ... 
- Java中的显示锁 ReentrantLock 和 ReentrantReadWriteLock
		在Java1.5中引入了两种显示锁,分别是可重入锁ReentrantLock和可重入读写锁ReentrantReadWriteLock.它们分别实现接口Lock和ReadWriteLock.(注意:s ... 
- day12(jsp指令&内置对象&动作标签、JavaBean、EL表达式&函数库)
		day12 JSP指令 JSP指令概述 JSP指令的格式:<%@指令名 attr1="" attr2="" %>,一般都会把JSP指令放到JSP文件 ... 
- JS中的日期内置函数
		用JS中的日期内置函数实现在页面显示:“今天是:2013年9月26日14:32:45”. var date=new Date(Date.parse('9/26/2013 14:32:45')); ... 
- python 内置函数补充 or 递归 or 二分法
		一.内置函数的补充 repr() 显示出字符串的官方表示形式 chr() print(chr(20013)) # 把数字编码转换成字符串 ord() print(ord('中')) # 20013 把 ... 
随机推荐
- IE11 for Windows 7 Enterprise With SP1 故障
			版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/jaminwm/article/details/29592027 这个故障非常诡异,卸载IE11也没实 ... 
- 深度问答之提取语料,导入了yml模块
			根据目录下的yml文件循环创建同名文件夹,并从yml文件读取问答并给每个文件夹写入question和answer文件 #!/usr/bin/env python # coding:utf8 # aut ... 
- HR_ROS 节点信息
			https://stackoverflow.com/questions/24638063/install-node-serialport-module-on-arm-linux https://blo ... 
- LeetCode:用HashMap解决问题
			LeetCode:用HashMap解决问题 Find Anagram Mappings class Solution { public int[] anagramMappings(int[] A, i ... 
- 基本jquery
			<!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8& ... 
- Python decorator @property
			@property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性 下面的链接很好的阐述了@property的概念和应用 http: ... 
- JSP页面EL表达式不解析
			问题是这样:在搭建springMVC环境的时候,笔者写了一个简单的Controller如下: @Controller public class HelloController { @RequestMa ... 
- iOS中成员变量和属性区别
			历史由来: 接触iOS的人都知道,@property声明的属性默认会生成一个_类型的成员变量,同时也会生成setter/getter方法. 但这只是在iOS5之后,苹果推出的一个新机制.看老代码时,经 ... 
- 第三篇、javascript整数和字符串
			一.整数 JavaScript中不区分整数值和浮点数值,JavaScript中所有数字均用浮点数值表示. 转换: parseInt(..) 将某值转换成数字,不成功则NaN parseFloat ... 
- C语言伪随机数的注意事项
			不要将srand(time(NULL))或srand(time(0))放到循环中,因为我们两次调用srand()函数设置随机数种子之间的时间间隔不超过1s,等价于使用了一个固定的随机数种子,会出现相同 ... 
