java多线程高级用法

ReentrantLock类的使用

在java多线程中,可以使用synchronized关键字来实现线程之间的同步互斥,但在JDK1.5中新增了ReentrantLock类也能达到相同的效果,并且在扩展功能上也更加强大,比如具有嗅探锁定,多路分支通知等功能,而且在使用上也比synchronized更加的灵活。
首先我们先通过一个简单的例子来了解ReentrantLock类的使用。
MyService.java的代码如下:

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

public class MyService {

private Lock lock = new ReentrantLock();

public void testMethord(){
lock.lock();
for(int i=0;i<10;i++){
System.out.println("ThreadCurrentName is "+Thread.currentThread().getName()+" and i = "+(i+1));
}
lock.unlock();
}
}

线程类的代码

public class MyThread extends Thread{
private MyService service;
public MyThread(MyService myService){
this.service = myService;
}
public void run(){
service.testMethord();
}
}

运行类的代码

public class runTest {
public static void main(String[] agrs) {

MyService service = new MyService();
MyThread a1 = new MyThread(service);
MyThread a2 = new MyThread(service);
MyThread a3 = new MyThread(service);
MyThread a4 = new MyThread(service);
a1.start();
a2.start();
a3.start();
a4.start();
}
}

运行结果如图:

2017-01-18_ReentrantLock线程类.jpg

从运行的结果来看,当前线程打印完毕之后将锁进行释放,其他的线程才可以继续打印,线程打印的数据是分组打印,因为当前线程已经持有锁,但线程之间打印的顺序是随机的。

使用Condition实现等待/通知

关键字synchronized和wait()和notify()方法相结合可以实现等待,通知模式,类ReentrantLock也可以实现相同的功能,但需要借助Condition对象。Condition类是在JDK5中出现的技术,使用他有更好的灵活性,比如可以实现多路通知功能。
在使用notify方法进行通知时,被通知的线程却是由JVM随机选择的。但是用ReenTranLock结合Condition类可以实现前面介绍过的“选择性通知” ,这个功能是非常重要的,而且在Confition类是默认提供的。
而synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象的身上。线程开始notifyAll时,需要通知所有的WAITNG线程,没有选择权,会出现相当大的效率问题。

Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。
作为一个示例,假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。

class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();

final Object[] items = new Object[100];
int putptr, takeptr, count;

public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}

public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}

下面通过一个例子来学习Condition类的学习:
MyService文件代码如下:

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

public class MyService {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();

public void waitMethord() {

try {
lock.lock();
System.out.println("wait A>>");
condition.await();
System.out.println("wait B>>");
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
System.out.println("lock is releast");
}
}

public void signalMethord(){
try {
lock.lock();
System.out.println("singal A>>==========");
condition.signal();
System.out.println("singal B>>==========");
} catch (Exception e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}
}

线程类:

public class ThreadA extends Thread{
private MyService service;
public ThreadA(MyService servie){
this.service = servie;
}
public void run(){
service.waitMethord();
}
}
//线程B
public class ThreadB extends Thread{
private MyService service;
public ThreadB(MyService servie){
this.service = servie;
}
public void run(){
service.signalMethord();
}
}

运行类:

public class runTest {

public static void main(String[] agrs) {
MyService servicer = new MyService();
ThreadA a = new ThreadA(servicer);
ThreadB b = new ThreadB(servicer);
a.start();
b.start();
}
}

运行结果如图:

2017-01-18_condition实现等待.jpg

通过本程序,成功通过Condition类实现等待/通知模式。

  • Object类中的wait()方法相当于Condition类中的await()方法。
  • Object类中的wait(long)方法相当于Condition类中的await(long,TimeUnit)方法。
  • Object类中的notify()方法相当于Condition类中的signal()方法。
  • Object类中的notifyAll()方法相当于Condition类中的sigalAll()方法。

通过多个Condition实现通知部分线程

不多说,直接上代码:
MyService.java文件类代码

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

public class MyService {
private Lock lock = new ReentrantLock();
public Condition conditionA = lock.newCondition();
public Condition conditionB = lock.newCondition();

public void awaitA() {
try {
lock.lock();
System.out.println("AAAAAAAAAAAA begin await time is "
+ System.currentTimeMillis() + " ThreadName = "
+ Thread.currentThread().getName());
conditionA.await();
System.out.println("AAAAAAAAAAAA end await time is "
+ System.currentTimeMillis() + " ThreadName = "
+ Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}

public void awaitB() {
try {
lock.lock();
System.out.println("BBBBBBBBBBBBBB begin await time is "
+ System.currentTimeMillis() + " ThreadName = "
+ Thread.currentThread().getName());
conditionB.await();
System.out.println("BBBBBBBBBBBBB end await time is "
+ System.currentTimeMillis() + " ThreadName = "
+ Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
lock.unlock();
}
}

public void singalAll_A(){
try{
lock.lock();
System.out.println("singal AAAAAAAAAAAA begin await time is "
+ System.currentTimeMillis() + " ThreadName = "
+ Thread.currentThread().getName());
conditionA.signalAll();
}finally{
lock.unlock();
}
}

public void singalAll_B(){
try{
lock.lock();
System.out.println("singal BBBBBBBBBB begin await time is "
+ System.currentTimeMillis() + " ThreadName = "
+ Thread.currentThread().getName());
conditionB.signalAll();
}finally{
lock.unlock();
}
}
}

线程类

//线程A
public class ThreadA extends Thread{
private MyService myService;

public ThreadA(MyService myService){
this.myService = myService;
}
public void run(){
for(int i=0;i<Integer.MAX_VALUE;i++){
myService.awaitA();
}
}
}
//线程B
public class ThreadB extends Thread{
private MyService myService;

public ThreadB(MyService myService){
this.myService = myService;
}
public void run(){
myService.awaitB();
}
}

运行类:

public class runTest {
public static void main(String [] agrs) throws InterruptedException{
MyService myService = new MyService();
ThreadA a = new ThreadA(myService);
ThreadB b = new ThreadB(myService);
a.setName("ThreadA");
b.setName("ThreadB");
a.start();
b.start();
Thread.sleep(3000);
myService.singalAll_A();
}
}

运行结果如图:

2017-01-18_交替打印.jpg

若在runTest最后添加一句:

myService.singalAll_B();

则最后的结果如图,且运行按键为灰色,代表没有线程处于阻塞状态

2017-01-18_通知部分线程2.jpg

这个程序应该还是比较好理解的,这里就不多做解释说明了。

实现生产者/消费者模式:一对一交替打印

这里也直接上代码

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class MyService {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private boolean hasValue = false;

public void set() {
try {
lock.lock();
while (hasValue == true) {
condition.await();
}
System.out.println("###");
hasValue = true;
condition.signal();
get();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void get() {
try {
lock.lock();
while (hasValue == false) {
condition.await();
}
System.out.println("***");
hasValue = false;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
//线程A
public class ThreadA extends Thread{
private MyService myService;
public ThreadA(MyService myService){
this.myService = myService;
}
public void run(){
for(int i=0;i<Integer.MAX_VALUE;i++){
myService.set();
}
}
}
//线程B
public class ThreadB extends Thread {
private MyService myService;
public ThreadB(MyService myService) {
this.myService = myService;
}
public void run() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
myService.get();
}
}
}
//运行类
public class runTest {
public static void main(String [] args){
MyService myServie = new MyService();
ThreadA a = new ThreadA(myServie);
a.start();
ThreadA b = new ThreadA(myServie);
b.start();
}
}

运行结果如图:两个方法依次执行了。
[图片上传中。。。(5)]

java.util.concurrent.locks 中的相关方法

软件包 java.util.concurrent.locks 的描述
为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。该框架允许更灵活地使用锁和条件,但以更难用的语法为代价。
Lock 接口支持那些语义不同(重入、公平等)的锁规则,可以在非阻塞式结构的上下文(包括 hand-over-hand 和锁重排算法)中使用这些规则。主要的实现是 ReentrantLock。
ReadWriteLock 接口以类似方式定义了一些读取者可以共享而写入者独占的锁。此包只提供了一个实现,即 ReentrantReadWriteLock,因为它适用于大部分的标准用法上下文。但程序员可以创建自己的、适用于非标准要求的实现。
Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
AbstractQueuedSynchronizer 类是一个非常有用的超类,可用来定义锁以及依赖于排队阻塞线程的其他同步器。 AbstractQueuedLongSynchronizer 类提供相同的功能但扩展了对同步状态的 64 位的支持。两者都扩展了类 AbstractOwnableSynchronizer(一个帮助记录当前保持独占同步的线程的简单类)。LockSupport 类提供了更低级别的阻塞和解除阻塞支持,这对那些实现自己的定制锁类的开发人员很有用。
下面就其中比较重要的几个方法和知识点进行介绍。

方法名 作用 备注说明 用法示例
new ReentrantLock(boolean) 创建公平锁与非公平锁 公平锁与非公平锁的含义与区别参见注释1 private ReentrantLock lock = new ReentrantLock(true);
int getHoldCount() 查询当前线程保持此锁定的个数,也就是查询当前调用lock方法的次数 通常只用于测试和调试。 lock.getHoldCount();
int getQueuedThreads() 返回正在等待获取次锁定的线程估计数 此方法用于监视系统状态,不用于同步控制。 lock.getQueueLength();
int getWaitQueueLength(Condition) 返回等待与此锁相关的给定条件的线程估计数。 注意,因为随时可能发生超时和中断,所以只能将估计值作为实际等待线程数的上边界。此方法用于监视系统状态,不用于同步控制。 private Condition condition =lock.newCondition(); lock.getWaitQueueLength(condition);
final boolean hasQueuedThread(Thread) 查询制定的线程是否正待等待获取次锁定 注意,因为随时可能发生取消,所以返回 true 并不保证此线程将获取此锁。此方法主要用于监视系统状态。 lock.hasQueuedThread(thread);
final boolean hasQueuedThreads() 查询是否有些线程正在等待获取此锁。 因为随时可能发生取消,所以返回 true 并不保证有其他线程将获取此锁。此方法主要用于监视系统状态。 lock.hasQueuedThread();
boolean hasWaiters(Condition) 查询是否有线程正待等待与此锁定有关的Condition条件 注意,因为随时可能发生超时和中断,所以返回 true 并不保证将来某个 signal 将唤醒线程。此方法主要用于监视系统状态。 lock.hasWaiters(condition);
final boolean isFair() 判断是不是公平锁 如果此锁的公平设置为 true,则返回 true。 lock.isFair();
boolean isLocked() 查询此锁定是否由任意线程保持。 此方法用于监视系统状态,不用于同步控制。 如果任意线程保持此锁,则返回 true;否则返回 false lock.isLocked();
boolean isHeldByCurrentThread() 查询当前线程是否保持此锁。 如果当前线程保持此锁,则返回 true;否则返回 false lock.isHeldByCurrentThread();
void lockInterruptibly() throws InterruptedException 如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常 在此实现中,因为此方法是一个显式中断点,所以要优先考虑响应中断,而不是响应锁的普通获取或重入获取。 (具体参见JDK api) lock.lockInterruptibly();
boolean tryLock() 仅在调用时锁未被另一个线程保持的情况下,才获取该锁。 如果该锁没有被另一个线程保持,并且立即返回 true 值,则将锁的保持计数设置为 1。即使已将此锁设置为使用公平排序策略,但是调用 tryLock() 仍将 立即获取锁(如果有可用的),而不管其他线程当前是否正在等待该锁。 lock.tryLock();
boolean tryLock(long ,TimeUnit ) throws InterruptedException 如果锁定在给定等待时间内有没有被另一个线程保持,且当前线程未被中断,则获取该锁定。 timeout - 等待锁的时间 unit - timeout 参数的时间单位 lock.tryLock(3,TimeUnit.SECONDS);
void awaitUninterruptibly() 设置当前线程在接到信号之前一直处于等待状态。 假定调用此方法时,当前线程保持了与此 Condition 有关联的锁。这取决于确定是否为这种情况以及不是时,如何对此作出响应的实现。通常,将抛出一个异常(比如 IllegalMonitorStateException)并且该实现必须对此进行记录。 condition.awaitUninterruptibly();
void awaitUntil(Date) throws InterruptedException 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。 假定调用此方法时,当前线程保持了与此 Condition 有关联的锁。这取决于确定是否为这种情况以及不是时,如何对此作出响应的实现。通常,将抛出一个异常(比如 IllegalMonitorStateException)并且该实现必须对此进行记录。 与响应某个信号而返回的普通方法相比,或者与指示是否到达指定最终期限相比,实现可能更喜欢响应某个中断。在任意一种情况下,实现必须确保信号被重定向到另一个等待线程(如果有的话)。 Calendar calendarRef = Calendar.getInstance(); condition.awaitUntil(calendarRef.getTime);

注释1:公平锁与非公平锁:锁Lock分为“公平锁”和“非公平锁”。公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO顺序,而非公平锁就是一种获取锁的抢占机制,是随机获取的,和公平锁不一样的就是先来的不一定先得到锁,这个方式可能造成某些线程一直拿不到锁。Condition某人使用的是非公共锁。

使用ReentrantReadWriterLock类

类ReentrantReadWriterLock类具有完全互斥排他小效果,即同一时间只有一个线程在执行ReetrantLock()方法后面的任务。这样做虽然保证了实例变量的线程安全性,但效率却是非常低下的。所以在JDK中提供可一种读写锁ReentrantReadWriterLock类,使用它可以加快运行效率,在某些不需要操作实例变量的方法中,完全可以使用读写锁ReentranReadWriterLock来提升该方法的代码运行效率。
读写锁表示也有两个锁,一个是读操作相关的锁,也称为共享锁,另一个是写操作相关的锁,也叫排他锁。也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。而进行写入操作的Thread只有获取写锁后才能进行写入操作,即多个Thread可以同时进行读取操作,但是同一时刻只允许一个Thread进行写入操作。
下面以一个实例来介绍如何使用这两种锁。

import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Service {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read() {
try {
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + "获取读锁 时间为:"
+ System.currentTimeMillis());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.readLock().unlock();
}

}
public void write() {
try {
try {
lock.writeLock().lock();
System.out.println(Thread.currentThread().getName()
+ "获取写锁 时间为:" + System.currentTimeMillis());
Thread.sleep(1000);
} finally {
lock.writeLock().unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

线程类:

//线程A
public class ThreadA extends Thread {
private Service service ;
public ThreadA(Service servie){
this.service = servie;
}
public void run(){
service.read();
}
}
//线程B
public class ThreadB extends Thread {
private Service service ;
public ThreadB(Service servier){
this.service = servier;
}
public void run(){
service.read();
}
}
//线程C
public class ThreadC extends Thread {
private Service service ;
public ThreadC(Service service){
this.service = service;
}
public void run(){
service.write();
}
}
//线程D
public class ThreadD extends Thread{
private Service service ;
public ThreadD(Service service){
this.service = service;
}
public void run(){
service.write();
}
}

运行类:

public class runTest {
public static void main(String [] agrs) throws InterruptedException{
Service service = new Service();
ThreadA a = new ThreadA(service);
ThreadB b = new ThreadB(service);
ThreadC c = new ThreadC(service);
ThreadD d = new ThreadD(service);

a.start();
a.setName("a");
b.setName("b");
c.setName("c");
d.setName("d");
b.start();
Thread.sleep(1000);
System.out.println("---------------------");
c.start();
d.start();
}
}

运行效果如图:

2017-01-21_读读写写.jpg

可以明显的看出来,读读是在同一时刻运行的,而写操作一次只能运行一个线程。至于读写之间的互斥,读者可以自行进行验证。这里就不做介绍了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容