`

Java之多线程之Lock与Condition

阅读更多
Java之多线程之Lock

接上文
在多线程环境中,大部分情况下,使用 synchronized 关键字可以满足需求。
但是其也存在不足。于是 java.util.concurrent.locks 包出现了。


第一篇

背景

在Java中实现线程同步的传统方法是使用synchronized关键字。
虽然它提供了一定的基本同步,但synchronized参数在使用时非常死板。
例如,一个线程只能锁一次。 同步块不提供等待队列的任何机制,
并且在一个线程退出后,任何线程都可以获取锁定。
这可能导致很长一段时间内某些其他线程的资源匮乏。

Java 从 1.5 开始,提供了可重入锁,以提供更大的灵活性同步。


一、概念
可重入锁
ReentrantLock

当某一线程在已经获取到该锁时,可再多次请求获取该锁,而该线程不会被阻塞而造成死锁。
它在线程不知是否已经获取到锁资源的情况下使用,非常有用。
相反,如果一个锁是不可重入的,当你已经获取到该锁资源,然后再次尝试获取该锁资源时,
就会自己把自己给锁住,造成线程死锁,一直阻塞在那里。


说明一:实现了Lock接口的类,都是可重入的。
可重入锁是 java.util.concurrent.locks.Lock 接口的实现之一。
另一个实现是 ReentrantReadWriteLock。

说明二:锁线程的方法介绍
- lock(), 拿不到lock就不罢休,不然线程就一直block。 比较无赖的做法。

- tryLock(),马上返回,拿到lock就返回true,不然返回false。 比较潇洒的做法。   
- 带时间限制的tryLock(),拿不到lock,就等一段时间,超时返回false。比较聪明的做法。

- tryInterruptibly:在锁上等待,直到获取锁,但是会响应中断,
这个方法优先考虑响应中断,而不是响应锁的普通获取或重入获取。


二、代码示例


//Java code to illustrate Reentrant Locks 
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;

class worker implements Runnable {
    SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
    
    String jobName;
    ReentrantLock re;

    public worker(ReentrantLock rl, String n) {
        re = rl;
        jobName = n;
    }

    public void run() {
        boolean done = false;
        while (!done) {
            // Get lock - for the first time (outer lock)
            boolean ans = re.tryLock();

            // Returns True if lock is free
            if (ans) {
                try {
                    log("task %s - outer lock acquired at %s, Doing outer work - 1.5 s, lockHoldCount: %d", 
                            jobName, ft.format(new Date()), re.getHoldCount());
                    
                    Thread.sleep(1500);

                    // re lock - for the second time (inner lock)
                    // This can happens in other method of other classes.
                    re.lock();
                    try {
                        log("task %s - inner lock acquired at %s, Doing inner work - 1.5 s, lockHoldCount: %d", 
                                jobName, ft.format(new Date()), re.getHoldCount());
                        Thread.sleep(1500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        // Inner lock release
                        re.unlock();
                        
                        log("task %s - releasing inner lock, lockHoldCount: %d", 
                                jobName, re.getHoldCount());
                    }
                    log("task %s - work done", jobName);
                    
                    done = true;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // Outer lock release
                    re.unlock();
                    
                    log("task %s - releasing outer lock, lockHoldCount: %d", 
                            jobName, re.getHoldCount());
                }
            } else {
                log("task %s - waiting for lock - 1 s", jobName);
                
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    private void log(String pattern, Object... args) {
        System.out.println(String.format(pattern, args));
    }
}

public class ReentrantLockTest {
    static final int MAX_T = 4;

    public static void main(String[] args) {
        ReentrantLock rel = new ReentrantLock();
        ExecutorService pool = Executors.newFixedThreadPool(MAX_T);
        Runnable w1 = new worker(rel, "Job1");
        Runnable w2 = new worker(rel, "Job2");
        Runnable w3 = new worker(rel, "Job3");
        Runnable w4 = new worker(rel, "Job4");
        pool.execute(w1);
        pool.execute(w2);
        pool.execute(w3);
        pool.execute(w4);
        pool.shutdown();
    }
}


/* output:

task Job2 - waiting for lock - 1 s
task Job4 - waiting for lock - 1 s
task Job1 - outer lock acquired at 10:53:27, Doing outer work - 1.5 s, lockHoldCount: 1
task Job3 - waiting for lock - 1 s
task Job4 - waiting for lock - 1 s
task Job2 - waiting for lock - 1 s
task Job3 - waiting for lock - 1 s
task Job1 - inner lock acquired at 10:53:29, Doing inner work - 1.5 s, lockHoldCount: 2
task Job3 - waiting for lock - 1 s
task Job4 - waiting for lock - 1 s
task Job2 - waiting for lock - 1 s
task Job1 - releasing inner lock, lockHoldCount: 1
task Job1 - work done
task Job1 - releasing outer lock, lockHoldCount: 0
task Job3 - waiting for lock - 1 s
task Job4 - waiting for lock - 1 s
task Job2 - outer lock acquired at 10:53:31, Doing outer work - 1.5 s, lockHoldCount: 1
task Job4 - waiting for lock - 1 s
task Job3 - waiting for lock - 1 s
task Job2 - inner lock acquired at 10:53:32, Doing inner work - 1.5 s, lockHoldCount: 2
task Job4 - waiting for lock - 1 s
task Job3 - waiting for lock - 1 s
task Job2 - releasing inner lock, lockHoldCount: 1
task Job2 - work done
task Job2 - releasing outer lock, lockHoldCount: 0
task Job3 - waiting for lock - 1 s
task Job4 - outer lock acquired at 10:53:34, Doing outer work - 1.5 s, lockHoldCount: 1
task Job3 - waiting for lock - 1 s
task Job4 - inner lock acquired at 10:53:35, Doing inner work - 1.5 s, lockHoldCount: 2
task Job3 - waiting for lock - 1 s
task Job4 - releasing inner lock, lockHoldCount: 1
task Job4 - work done
task Job4 - releasing outer lock, lockHoldCount: 0
task Job3 - outer lock acquired at 10:53:37, Doing outer work - 1.5 s, lockHoldCount: 1
task Job3 - inner lock acquired at 10:53:38, Doing inner work - 1.5 s, lockHoldCount: 2
task Job3 - releasing inner lock, lockHoldCount: 1
task Job3 - work done
task Job3 - releasing outer lock, lockHoldCount: 0



*/








第二篇

一、Lock API 的主要类介绍

1、Lock 接口 - 实现类 ReentrantLock
接口类。规定了Lock的基本方法,这些方法可以满足所有 synchronized 的功能,
还提供了更多功能:Lock条件判断、Lock超时判断。

其最主要的方法:
lock():获取锁
unlock():释放锁
tryLock():等待锁一段时间再锁
newCondition():根据条件进行锁

1.1 Condition

背景知识:
- wait()、notify()
在多线程进行协同工作时,需要用到 wait()、notify() 。
wait()、notify() 只能用在 synchronized 块内部,而且是,synchronized 哪个对象,就得调用哪个对象的 wait()、notify() 方法。

Condition 类与Object类的 wait()、notify() 方法功能差不多。
但是提供了更多:可以创建不同的 wait 集合。
Condition 的实例必须由 Lock 类创建,而不是自己去 new 而产生。

主要方法:
await():类似于 Object.wait()
signal():类似于 Object.notify()
signalAll():类似于 Object.notifyAll()



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

import org.junit.Test;

// 方法一:
// 使用 wait 和 notify 控制线程,
// 使子线程和主线程各交替执行一次。
//=====================================================================

class UseWaitNotify {
	
	private Object dummy = new Object();  
	private boolean flag = true;
	
	public void subThread() {
		synchronized (dummy){
			while(!flag){
				try {
					dummy.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("[UseWaitNotify] sub..");
			flag = false;
			dummy.notify();
		}
	}
	
	public void mainThread(){
		synchronized (dummy){
			while(flag){
				try {
					dummy.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("[UseWaitNotify] main..");
			flag = true;
			dummy.notify();
		}
	}
}

// test method
class WaitNotify{

	@Test
	public void testUseWaitNotify(){
		UseWaitNotify useWaitNotify = new UseWaitNotify();
		new Thread(new Runnable(){
			@Override
			public void run() {
				for(int i = 0 ; i< 10; i++){
					useWaitNotify.subThread();
				}
			}
		}).start();
			
		
		for(int i = 0 ; i< 10; i++){
			useWaitNotify.mainThread();
		}
		
	}
}


//方法二:
//使用 await 和 signal 控制线程,
//使子线程和主线程各交替执行一次。
//=====================================================================

class UseCondition {
	
	private Lock lock = new ReentrantLock();  
	private Condition condition = lock.newCondition();
	
	private boolean flag = true;
	
	public void subThread(){
		lock.lock();
		try{
			while(!flag){
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("[UseCondition] sub..");
			flag = false;
			condition.signal();
		}finally{
			lock.unlock();
		}
	}
	
	public void mainThread(){
		lock.lock();
		try{
			while(flag){
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("[UseCondition] main..");
			flag = true;
			condition.signal();
		}finally{
			lock.unlock();
		}
	}
}

//test method
public class LockCondition {

	@Test
	public void testUseCondition(){
		UseCondition useCondition = new UseCondition();
		new Thread(new Runnable(){
			@Override
			public void run() {
				for(int i = 0 ; i< 10; i++){
					useCondition.subThread();
				}
			}
		}).start();
			
		
		for(int i = 0 ; i< 10; i++){
			useCondition.mainThread();
		}
		
	}
}








1.2 ReentrantLock
该类被使用的最为广泛。它是在功能上实现了 synchronized 的类。
除了实现了从 Lock 接口继承的方法,它还自己有一些方法:
比如让线程等待一段时间再去获取资源的锁。

什么是 reentrant (可重入)?
其实 synchronized 代码块原本就是可重入(reentrant)的:
例如:
某线程正在执行 synchronized 代码块一,代码块一中需要执行代码块二,
两个代码块锁定的是同一个资源,此时线程一可以顺利执行此两个代码块。
无需重复获取资源的锁,即:资源锁重用。

看下面的例子:
public class Test{

public synchronized foo(){
    //do something
    bar();
  }

  public synchronized bar(){
    //do some more
  }
}

线程在执行 foo()时,需要执行 bar(),此时直接执行即可,无需重复获取锁。
因为这两个 synchronized 代码块锁定的是同一个对象:this


2、ReadWriteLock 接口 - 实现类 ReentrantReadWriteLock
该类包含了一对相互关联的锁。
一种是:只读锁(Read-Only)。如果没有其它线程在占有写锁,该锁可以被多个线程同时拥有。
一种是:写锁。如果没有线程在占有读锁或写锁,该锁只能被一个线程独占。


二、Lock 使用示例

1、先看看用 synchronized 的写法:
public class Resource {
    
    private Object dummy = new Object();
    
    public void doSomething(){
        synchronized(dummy){
            System.out.println("do something...");
        }
    }
    
    public void doLogging(){
        System.out.println("do logging...");
    }

}


2、使用 java.util.concurrent.locks.Lock 的写法:
public class LockResource {
    
    private Lock lock;
    
    public LockResource(){
        this.lock = new ReentrantLock();
    }
    
    public void doSomething(){
        try {
            if(lock.tryLock(10, TimeUnit.SECONDS)){ //时间单位:秒
                System.out.println("do something..");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally{
            lock.unlock(); // release the lock.
        }
    }
    
    public void doLogging(){
        System.out.println("do logging...");
    }

}


3、ReentrantReadWriteLock

class Queue3{
	
	private Object data = null;
	ReadWriteLock lock = new ReentrantReadWriteLock();
	
	/*
	  Read lock: 
	  - make sure no thread is writing.
	 */
	public void get(){
		lock.readLock().lock();
		System.out.println("read data..." + data);
		lock.readLock().unlock();
	}
	
	/*
	  Write lock: 
	  - make sure no thread is writing.
	  - make sure no thread is reading.
	 */
	public void set(Object data){
		lock.writeLock().lock();
		System.out.println("write data...");
		this.data = data;
		lock.writeLock().unlock();
	}
	
}



4. ReentrantReadWriteLock 使用Lock 实现缓存。
 class CachedData {
   Object data;
   volatile boolean cacheValid;
   final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

   void processCachedData() {
     rwl.readLock().lock();
     if (!cacheValid) {
        // Must release read lock before acquiring write lock
        rwl.readLock().unlock();
        rwl.writeLock().lock();
        try {
          // Recheck state because another thread might have
          // acquired write lock and changed state before we did.
          if (!cacheValid) {
            data = ...
            cacheValid = true;
          }
          // Downgrade by acquiring read lock before releasing write lock
          rwl.readLock().lock();
        } finally {
          rwl.writeLock().unlock(); // Unlock write, still hold read
        }
     }

     try {
       use(data);
     } finally {
       rwl.readLock().unlock();
     }
   }
 }





5. 双condition实现的阻塞式消息队列。

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

/**
 * 双condition实现的阻塞式消息队列。
 * - 队列满时,不存
 * - 队列空时,不取
 */
public class LockConditionBlockingQueue {

	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();
		}
	}
}




6. 使用 3 个 condition,使每个子线程各自交替执行。

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

import org.junit.Test;

public class LockConditionThree {
	final int maxLoop = 100;
	
	@Test
	public void testUseCondition(){
		UseCondition useCondition = new UseCondition();
		new Thread(new Runnable(){
			@Override
			public void run() {
				for(int i = 0 ; i< maxLoop; i++){
					useCondition.sub1();
				}
			}
		}).start();

		new Thread(new Runnable(){
			@Override
			public void run() {
				for(int i = 0 ; i< maxLoop; i++){
					useCondition.sub2();
				}
			}
		}).start();
		
		new Thread(new Runnable(){
			@Override
			public void run() {
				for(int i = 0 ; i< maxLoop; i++){
					useCondition.sub3();
				}
			}
		}).start();
	
		
	}
	

	//使用 3 个 condition,使每个子线程各自交替执行。
	//=======================================================

	static class UseCondition {
		
		private Lock lock = new ReentrantLock();  
		private Condition condition1 = lock.newCondition();
		private Condition condition2 = lock.newCondition();
		private Condition condition3 = lock.newCondition();
		
		private int shouldSub = 1;
		
		public void sub1(){
			lock.lock();
			try{
				while(shouldSub != 1){
					try {
						condition1.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println("sub1..");
				shouldSub = 2;
				condition2.signal(); //notify thread 2
			}finally{
				lock.unlock();
			}
		}
		
		public void sub2(){
			lock.lock();
			try{
				while(shouldSub != 2){
					try {
						condition2.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println("sub2..");
				shouldSub = 3;
				condition3.signal(); //notify thread 3
			}finally{
				lock.unlock();
			}
		}
		public void sub3(){
			lock.lock();
			try{
				while(shouldSub != 3){
					try {
						condition3.await();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println("sub3..\n");
				shouldSub = 1;
				condition1.signal(); //notify thread 1
			}finally{
				lock.unlock();
			}
		}
	}
}





7. Semaphore
Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
比如在Windows下可以设置共享文件的最大客户端访问个数。

import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore;

public class LockSemephore {

	public static void main(String[] args) {

		ExecutorService exec = Executors.newCachedThreadPool();
		final Semaphore semp = new Semaphore(5); // 同时只能5个线程使用

		for (int index = 0; index < 20; index++) { // 模拟20个客户端访问
			final int NO = index;
			exec.execute(new Runnable() {
				public void run() {
					try {
						semp.acquire();// 获取许可
						System.out.println("Accessing: " + NO);
						Thread.sleep(2000);
						semp.release();// 访问完后,释放
						System.out.println("AvailablePermits:---------" + semp.availablePermits());
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
		}
		// 退出线程池
		exec.shutdown();
		System.out.println("done!");
	}

}










java.util.concurrent包之Execuotor系列文章

00_Java之 java.util.concurrent 包之概述

01_Java之java.util.concurrent包之Executor与ExecutorService

02_Java之 java.util.concurrent 包之ExecutorService之submit () 之 Future

03_Java之多线程之Callable与Future

04_Java之多线程之Lock






-
转载请注明,
原文出处:http://lixh1986.iteye.com/blog/2351294






引用:
http://www.journaldev.com/2377/java-lock-example-reentrantlock

分享到:
评论
1 楼 Lixh1986 2018-11-13  
Java并发编程:自己动手写一把可重入锁
https://blog.csdn.net/zhang5476499/article/details/83794711

相关推荐

    Java多线程中ReentrantLock与Condition详解

    主要介绍了Java多线程中ReentrantLock与Condition详解,需要的朋友可以参考下

    java多线程实现生产者和消费者

    java多线程实现生产者和消费者 ,4种实现方式,分别为synchronizated,condition和lock,信号量,阻塞队列

    Java学习源码Java多线程的代码

    在char01包里放置Java多线程基本知识的代码。内容如下: 如何使用多线程 如何得到多线程的一些信息 如何停止线程 如何暂停线程 线程的一些其他用法 在char02包里放置了Java对变量和对象并发访问的知识的代码...

    java 并发编程 多线程

    java 并发 编程 多线程 concurrent lock condition executorserice executor java.util.curcurrent.

    基础技术部牛路《Java多线程入阶分享》纯干货

    Java多线程入阶干货分享 1.使用线程的经验:设置名称、响应中断、使用ThreadLocal 2.Executor:ExecutorService和Future 3.阻塞队列:put和take、offer和poll、drainTo 4.线程间通信:lock、condition、wait、notify...

    JAVA高质量并发详解,多线程并发深入讲解

    本书全面解析了Java并发编程的核心概念、原理和实践,帮助读者深入理解多线程并发编程的精髓,提升编程能力和系统性能。 书中首先介绍了并发编程的基础知识,包括线程的基本概念、线程的生命周期、线程安全等问题。...

    【2018最新最详细】并发多线程教程

    【2018最新最详细】并发多线程教程,课程结构如下 1.并发编程的优缺点 2.线程的状态转换以及基本操作 3.java内存模型以及happens-before规则 4.彻底理解synchronized 5.彻底理解volatile 6.你以为你真的了解final吗...

    JUC多线程学习个人笔记

    JUC(Java Util Concurrent)是Java中用于并发编程的工具包,提供了一组接口和类,用于处理多线程和并发操作。JUC提供了一些常用的并发编程模式和工具,如线程池、并发集合、原子操作等。 JUC的主要特点包括: ...

    56.Lock-ReentranLock-使用多个Condition实现通知部分线程.mp4

    在学习Java过程中,自己收集了很多的Java的学习资料,分享给大家,有需要的欢迎下载,希望对大家有用,一起学习,一起进步。

    Lock锁的底层原理完整版

    Lock锁,一种线程同步机制,其主要功能是防止多个线程同时访问同一代码块,从而避免因并发问题引发的数据不一致或其他错误。...总的来说,Lock锁是Java多线程编程中的重要工具,能够有效保障程序运行的正确性和稳定性。

    Java并发编程原理与实战

    了解多线程所带来的安全风险.mp4 从线程的优先级看饥饿问题.mp4 从Java字节码的角度看线程安全性问题.mp4 synchronized保证线程安全的原理(理论层面).mp4 synchronized保证线程安全的原理(jvm层面).mp4 单例问题...

    Java 7并发编程实战手册

    如果你是一名Java开发人员,并且想进一步掌握并发编程和多线程技术,并挖掘Java 7并发的新特性,那么本书是你的合适之选。 《Java 7并发编程实战手册》 第1章 线程管理 1 1.1 简介 1 1.2 线程的创建和运行...

    locks框架:接口.pdf

    Lock 接口概述: 简要介绍 Lock 接口,解释其在多线程编程中的作用和优势。比较 Lock 接口与传统 synchronized 关键字的不同之处。 常用 Lock 接口实现类: 详细讲解 Lock 接口的一些常用实现类,如 ReentrantLock、...

    JAVA多线程实现2个producer和一个Consumer把整数放入到一个环形缓冲Circle Buffer中

    采用同步机制synchronized/wait(notify)或者lock(unlock)/condition variable实现两个producer和一个consumer之间协调运行。运行结果输出格式为:Put(or Get) number {[content] length start_index end_index} 包含...

    龙果java并发编程完整视频

    第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四个阶段并推荐学习并发的资料 ...

    龙果 java并发编程原理实战

    第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四个阶段并推荐学习并发的资料 ...

    Java JDK实例宝典

    7 一个支持多线程的服务器框架 13. 8 代理服务器 13. 9 Telnet客户端 13. 10 UDP编程 13. 11 聊天室服务器端 13. 12 聊天室客户端 13. 13 FTP客户端 第14章 数据库 14. 1 连接各种...

    Java 并发编程原理与实战视频

    第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四个阶段并推荐学习并发的资料 ...

    Linux多线程服务端编程:使用muduo C++网络库

    《Linux多线程服务端编程:使用muduo C++网络库》主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread。...

Global site tag (gtag.js) - Google Analytics