一,Lock
Lock比传统的线程模型中的synchronized方式更加面向对象,因为“锁”本身就是一个对象。
两个线程执行的代码要实现同步互斥的效果,他们必须用同一个Lock对象。
读写锁:(1)读锁:多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,我们只需要代码中用对相应的锁即可。如果只读数据,那么可以很多人(线程)同时读,但是不能同时写,此时就加读锁。如果代码需要修改数据,此时只能一个人(一个线程)写,此时不能同时读,那么就加写锁。
总之,读时,上读锁;写时,上写锁。
二,Condition
Condition 将 Object 监视器方法(wait、notify和notifyAll)分解成截然不同的对象,
以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。
其中,Lock 替代了synchronized 方法和语句的使用,Condition 替代了 Object 监视器方
法的使用。
条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能
为 true的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信
息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一
个条件的主要属性是:以原子方式 释放相关的锁,并挂起当前线程,就像 Object.wait 做的
那样。
Condition 实例实质上被绑定到一个锁上。要为特定 Lock实例获得 Condition 实例,
使用newCondition()方法。
1,一个锁上一个Condition的简单使用 Demo
两个线程(主线程、子线程):子线程循环4次,接着主线程循环5,接着又回到子线程循环4
次,接着再回到主线程又循环5,如此循环3次,请写出程序。
代码如下:使用一个Condition对象完成两线程之间的通信
package com.tgb.thread13;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Condition实现线程同步通信——简单Demo
* @author hanxuemin
*
*/
public class ConditionCommunication {
public static void main(String[] args) {
final Business business = new Business();
/**
* 创建一个子线程
*/
new Thread(
new Runnable() {
@Override
public void run() {
/**
* 循环3次,调用sub()方法
*/
for(int i=1;i<=3;i++){
business.sub(i);
}
}
}
).start();
for(int i=1;i<=3;i++){
business.main(i);
}
}
static class Business {
Lock lock = new ReentrantLock(); //创建锁对象
Condition condition = lock.newCondition(); //创建condition对象
private boolean bShouldSub = true;
/**
* 子线程调用的方法
* @param i
*/
public void sub(int i){
lock.lock(); //加锁
try{
//如果标识变量bShouldSub=false,则阻塞线程
while(!bShouldSub){
try {
condition.await(); //阻塞该线程,等待。。。
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 循环4次,打印
*/
for(int j=1;j<=4;j++){
System.out.println("sub thread sequence of " + j + ",loop of " + i);
}
bShouldSub = false; //给标识变量bShouldSub=true
condition.signal(); //唤醒其他线程
}finally{
lock.unlock(); //释放锁
}
}
/**
* 主线程方法
* @param i
*/
public void main(int i){
lock.lock(); //加锁
try{
//如果标识变量bShouldSub=true,则阻塞线程
while(bShouldSub){
try {
condition.await(); //阻塞该线程,等待。。。
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 循环5次,打印
*/
for(int j=1;j<=5;j++){
System.out.println("main thread sequence of " + j + ",loop of " + i);
}
bShouldSub = true;
condition.signal(); //唤醒其他线程
}finally{
lock.unlock();//释放锁
}
}
}
}
Condition的功能类似在传统线程技术中的Object.wait()和Object.natify()的功能,但传统
线程技术实现的互斥只能一个线程单独执行,不能实现线程间通信(也就是说这个线程执行完了通
知另一个线程来执行);而Condition就是解决这个问题的,实现线程间的通信。比如CPU让小弟做
事,小弟说我先歇着并通知大哥,大哥就开始做事。
Condition 将 Object 监视器方法(wait、notify 和notifyAll)分解成截然不同的对象,以
便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其
中,Lock替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使
用。
Condition实例实质上被绑定到一个锁上。要为特定 Lock 实例获得Condition 实例,请使用
其 newCondition() 方法。
2,在java5中,一个锁可以有多个条件,每个条件上可以有多个线程等待,通过调
用await()方法,可以让线程在该条件下等待。当调用signalAll()方法,又可以唤醒该条件下的等待的线程。
可阻塞队列,代码:
package com.tgb.thread13;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 两个Condition实现的可阻塞队列
* @author hanxuemin
*
*/
public class BoundedBuffer {
final Lock lock = new ReentrantLock(); //创建锁
final Condition notFullCondition = lock.newCondition(); //写线程条件
final Condition notEmptyCondition = lock.newCondition(); //读线程条件
//创建大小为100的数组,相当于阻塞队列(缓存队列)
final Object[] items = new Object[100];
int putptr; //写索引
int takeptr; //读索引
int count; //队列中存在的数据个数
/**
* 往数组items中存放数据的方法
* @param x 存放的数据
* @throws InterruptedException
*/
public void put(Object x) throws InterruptedException{
lock.lock();
try {
while (count == items.length) { //如果队列已满,则阻塞写线程
notFullCondition.await(); //写线程阻塞
}
/**
* 以下四步为一大步
* 1,存值
* 2,判断写索引,如果是队列最后一个位置,则置为0
* 3,数据个数++
* 4,唤醒读线程
* */
items[putptr] = x; //数组items中存入数据x
if(++putptr == items.length) putptr = 0; //如果写索引写到队列的最后一个位置了,那么置为0
++count; //同时数据个数++
notEmptyCondition.signal(); //唤醒读线程
}finally{
lock.unlock(); //释放锁
}
}
/**
* 从数组items中取数据
* @return items中取出的数据
* @throws InterruptedException
*/
public Object take() throws InterruptedException{
lock.lock();
try {
while (count == 0) { //如果队列为空
notEmptyCondition.await();//阻塞读线程
}
/**
* 以下四步为一大步
* 1,取值
* 2,判断读索引,如果是队列最后一个位置,则置为0
* 3,数据个数--
* 4,唤醒写线程
* */
Object x = items[takeptr]; //取值
if(++takeptr == items.length) takeptr = 0; //如果读索引读到队列的最后一个位置,则置为0
--count; //同时数据个数--
notFullCondition.signal();//唤醒写线程
return x;
}finally{
lock.unlock(); //释放锁
}
}
}
这是一个处于多线程工作环境下的缓存区,缓存区提供了两个方法,put和take,put是存数
据,take是取数据,内部有个缓存队列,具体变量和方法说明见代码,这个缓存区类实现的功能:
有多个线程往里面存数据和从里面取数据,其缓存队列(先进先出后进后出)能缓存的最大数值
是100,多个线程间是互斥的,当缓存队列中存储的值达到100时,将写线程阻塞,并唤醒读线程,
当缓存队列中存储的值为0时,将读线程阻塞,并唤醒写线程,下面分析一下代码的执行过程:
1.一个写线程执行,调用put方法;
2.判断count是否为100,显然没有100;
3.继续执行,存入值;
4.判断当前写入的索引位置++后,是否和100相等,相等将写入索引值变为0,并将count+1;
5.仅唤醒读线程阻塞队列中的一个;
6.一个读线程执行,调用take方法;
7. ……
8.仅唤醒写线程阻塞队列中的一个。
这就是多个Condition的强大之处,假设缓存队列中已经存满,那么阻塞的肯定是写线程,唤
醒的肯定是读线程,相反,阻塞的肯定是读线程,唤醒的肯定是写线程,那么假设只有一
个Condition会有什么效果呢,缓存队列中已经存满,这个Lock不知道唤醒的是读线程还是写线程
了,如果唤醒的是读线程,皆大欢喜,如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这
时又去唤醒,这样就浪费了很多时间。
3,另一个一个锁有多个条件的Demo
子线程二循环10次,子线程三循环5次,接着主线程循环15,接着又回到子线程二循环10次,
子线程三循环5次,接着再回到主线程又循环15,如此循环20次,请写出程序。
——子线程二执行完了,唤醒子线程三;子线程三执行完了,唤醒主线程;主线程执行完了,唤醒
子线程二。并且子线程二只能唤醒子线程三,子线程三只能唤醒主线程,主线程只能唤醒子线二。
package com.tgb.thread13;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Lock&Condition三个线程彼此同步通信
*
* @author hanxuemin
*
*/
public class ThreeConditionCommunication {
public static void main(String[] args) {
final Business business = new Business();
/**
* 出主线程外,再创建两个线程
*/
// 创建一个子线程2,循环次,调用sub2()方法
new Thread(new Runnable() {
@Override
public void run() {
/**
* 循环20次调用sub2()方法
*/
for (int i = 1; i <= 20; i++) {
business.sub2(i); // 调用sub2()方法
}
}
}).start();
// 再创建一个子线程3,循环50次,调用sub3()方法
new Thread(new Runnable() {
@Override
public void run() {
/**
* 循环20次调用sub3()方法
*/
for (int i = 1; i <= 20; i++) {
business.sub3(i); // 调用sub3()方法
}
}
}).start();
/**
* 主线程 循环20次,调用main()方法
*/
for (int i = 1; i <= 20; i++) {
business.main(i);
}
}
/**
*
* @author hanxuemin
*
*/
static class Business {
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition(); // 线程条件1
Condition condition2 = lock.newCondition(); // 线程条件2
Condition condition3 = lock.newCondition(); // 线程条件3
private int shouldSub = 1; // 标识符变量,初始值为1;当值为1时主线程执行,子线程2和子线程3等待;
// 当值为2时子线程2执行,其他线程等待;当值为3时子线程3执行,其他线程等待
/**
* 子线程2调用的方法
*
* @param i
*/
public void sub2(int i) {
lock.lock(); // 加锁
try {
while (shouldSub != 2) { // 当标识符变量!=2时,子线程2阻塞,处于等待状态
try {
condition2.await();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 循环10次,打印当前线程信息(调用此方法的为子线程2,即打印子线程2的信息)
*/
for (int j = 1; j <= 10; j++) {
System.out.println("sub2 thread sequence of " + j
+ ",loop of " + i);
}
shouldSub = 3; // 给标识符变量赋值为3
condition3.signal(); // 唤醒子线程3
} finally {
lock.unlock(); // 释放锁
}
}
/**
* 子线程3调用的方法
*
* @param i
*/
public void sub3(int i) {
lock.lock(); // 加锁
try {
while (shouldSub != 3) { // 当标识符变量!=3时,子线程3阻塞,处于等待状态
try {
condition3.await();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 循环5次,打印当前线程信息(调用此方法的为子线程3,即打印子线程3的信息)
*/
for (int j = 1; j <= 5; j++) {
System.out.println("sub3 thread sequence of " + j
+ ",loop of " + i);
}
shouldSub = 1; // 给标识符变量赋值为1
condition1.signal(); // 唤醒主线程
} finally {
lock.unlock(); // 释放锁
}
}
/**
* 主线程调用的方法
*
* @param i
*/
public void main(int i) {
lock.lock(); // 加锁
try {
while (shouldSub != 1) { // 当标识符变量!=1时,子线程1阻塞,处于等待状态
try {
condition1.await();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 循环15次,打印当前线程信息(调用此方法的为主线程,即打印主线程的信息)
*/
for (int j = 1; j <= 15; j++) {
System.out.println("main thread sequence of " + j
+ ",loop of " + i);
}
shouldSub = 2; // 给标识符变量赋值为2
condition2.signal(); // 唤醒子线程2
} finally {
lock.unlock(); // 释放锁
}
}
}
}
总结:
(1) Condition实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其newCondition() 方法。
(2)Condition解决了线程间的通信问题。
(3)一个锁可以有多个Condition,每个条件上可以有多个线程等待,通过调用await()方法,可以让线程在该条件下等待。当调用signal()方法,又可以唤醒该条件下的等待的其他线程。