http://www.infoq.com/cn/articles/java-memory-model-5  深入理解Java内存模型(五)——锁

http://www.ibm.com/developerworks/cn/java/j-jtp10264/  Java 理论与实践: JDK 5.0 中更灵活、更具可伸缩性的锁定机制

http://blog.csdn.net/ghsau/article/details/7481142

Lock与synchronized

Lock可以实现synchronized的相同功能,它能以更优雅的方式处理线程同步问题。例:

class Outputter1 {
private Lock lock = new ReentrantLock();// 锁对象 public void output(String name) {
lock.lock(); // 得到锁 try {
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
} finally {
lock.unlock();// 释放锁
}
}
}

需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而是用Lock需要我们
手动释放锁,所以为了保证锁最终被释放(发生异常情况),
要把互斥区放在try内,释放锁放在finally内。

上例中展示的是和synchronized相同的功能,那Lock的优势在哪里?

例如一个类对其内部共享数据data提供了get()和set()方法,如果用synchronized,则代码如下:

class syncData {
private int data;// 共享数据
public synchronized void set(int data) {
System.out.println(Thread.currentThread().getName() + "准备写入数据");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = data;
System.out.println(Thread.currentThread().getName() + "写入" + this.data);
}
public synchronized void get() {
System.out.println(Thread.currentThread().getName() + "准备读取数据");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "读取" + this.data);
}
}

然后写个测试类来用多个线程分别读写这个共享数据:

public static void main(String[] args) {
// final Data data = new Data();
final syncData data = new syncData();
// final RwLockData data = new RwLockData(); //写入
for (int i = 0; i < 3; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5; j++) {
data.set(new Random().nextInt(30));
}
}
});
t.setName("Thread-W" + i);
t.start();
}
//读取
for (int i = 0; i < 3; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 5; j++) {
data.get();
}
}
});
t.setName("Thread-R" + i);
t.start();
}
}

运行结果:

Thread-W0准备写入数据
Thread-W0写入0
Thread-W0准备写入数据
Thread-W0写入1
Thread-R1准备读取数据
Thread-R1读取1
Thread-R1准备读取数据
Thread-R1读取1
Thread-R1准备读取数据
Thread-R1读取1
Thread-R1准备读取数据
Thread-R1读取1
Thread-R1准备读取数据
Thread-R1读取1
Thread-R2准备读取数据
Thread-R2读取1
Thread-R2准备读取数据
Thread-R2读取1
Thread-R2准备读取数据
Thread-R2读取1
Thread-R2准备读取数据
Thread-R2读取1
Thread-R2准备读取数据
Thread-R2读取1
Thread-R0准备读取数据 //R0和R2可以同时读取,不应该互斥!
Thread-R0读取1
Thread-R0准备读取数据
Thread-R0读取1
Thread-R0准备读取数据
Thread-R0读取1
Thread-R0准备读取数据
Thread-R0读取1
Thread-R0准备读取数据
Thread-R0读取1
Thread-W1准备写入数据
Thread-W1写入18
Thread-W1准备写入数据
Thread-W1写入16
Thread-W1准备写入数据
Thread-W1写入19
Thread-W1准备写入数据
Thread-W1写入21
Thread-W1准备写入数据
Thread-W1写入4
Thread-W2准备写入数据
Thread-W2写入10
Thread-W2准备写入数据
Thread-W2写入4
Thread-W2准备写入数据
Thread-W2写入1
Thread-W2准备写入数据
Thread-W2写入14
Thread-W2准备写入数据
Thread-W2写入2
Thread-W0准备写入数据
Thread-W0写入4
Thread-W0准备写入数据
Thread-W0写入20
Thread-W0准备写入数据
Thread-W0写入29

读写锁ReadWriteLock

现在一切都看起来很好!各个线程互不干扰!等等。。读取线程和写入线程互不干扰是正常的,但是两个读取线程是否需要互不干扰??

对!读取线程不应该互斥!

我们可以用读写锁ReadWriteLock实现:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

    class Data {
private int data;// 共享数据
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public void set(int data) {
rwl.writeLock().lock();// 取到写锁
try {
System.out.println(Thread.currentThread().getName() + "准备写入数据");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = data;
System.out.println(Thread.currentThread().getName() + "写入" + this.data);
} finally {
rwl.writeLock().unlock();// 释放写锁
}
} public void get() {
rwl.readLock().lock();// 取到读锁
try {
System.out.println(Thread.currentThread().getName() + "准备读取数据");
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "读取" + this.data);
} finally {
rwl.readLock().unlock();// 释放读锁
}
}
}

测试结果:

Thread-W1准备写入数据
Thread-W1写入9
Thread-W1准备写入数据
Thread-W1写入24
Thread-W1准备写入数据
Thread-W1写入12
Thread-W0准备写入数据
Thread-W0写入22
Thread-W0准备写入数据
Thread-W0写入15
Thread-W0准备写入数据
Thread-W0写入6
Thread-W0准备写入数据
Thread-W0写入13
Thread-W0准备写入数据
Thread-W0写入0
Thread-W2准备写入数据
Thread-W2写入23
Thread-W2准备写入数据
Thread-W2写入24
Thread-W2准备写入数据
Thread-W2写入24
Thread-W2准备写入数据
Thread-W2写入17
Thread-W2准备写入数据
Thread-W2写入11
Thread-R2准备读取数据
Thread-R1准备读取数据
Thread-R0准备读取数据
Thread-R0读取11
Thread-R1读取11
Thread-R2读取11
Thread-W1准备写入数据
Thread-W1写入18
Thread-W1准备写入数据
Thread-W1写入1
Thread-R0准备读取数据
Thread-R2准备读取数据
Thread-R1准备读取数据
Thread-R2读取1
Thread-R2准备读取数据
Thread-R1读取1
Thread-R0读取1
Thread-R1准备读取数据
Thread-R0准备读取数据
Thread-R0读取1
Thread-R2读取1
Thread-R2准备读取数据
Thread-R1读取1
Thread-R0准备读取数据
Thread-R1准备读取数据
Thread-R0读取1
Thread-R2读取1
Thread-R1读取1
Thread-R0准备读取数据
Thread-R1准备读取数据
Thread-R2准备读取数据
Thread-R1读取1
Thread-R2读取1
Thread-R0读取1

与互斥锁定相比,读-写锁定允许对共享数据进行更高级别的并发访问。虽然一次只有一个线程(writer 线程)可以修改共享数据,但在许多情况下,任何数量的线程可以同时读取共享数据(reader 线程)

从理论上讲,与互斥锁定相比,使用读-写锁定所允许的并发性增强将带来更大的性能提高。

在实践中,只有在多处理器上并且只在访问模式适用于共享数据时,才能完全实现并发性增强。——例如,某个最初用数据填充并且之后不经常对其进行修改的 collection,因为经常对其进行搜索(比如搜索某种目录),所以这样的 collection 是使用读-写锁定的理想候选者。

线程间通信Condition

Condition可以替代传统的线程间通信,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()。

传统线程的通信方式,Condition都可以实现。

注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。

Condition的强大之处在于它可以为多个线程间建立不同的Condition

看JDK文档中的一个例子:假定有一个绑定的缓冲区,它支持 puttake 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存put 线程和take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个Condition 实例来做到这一点。

——其实就是java.util.concurrent.ArrayBlockingQueue的功能

 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; //写索引
int takeptr; //读索引
int 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会有什么效果呢?缓存队列中已经存满,这个Lock不知道唤醒的是读线程还是写线程了,如果唤醒的是读线程,皆大欢喜,如果唤醒的是写线程,那么线程刚被唤醒,又被阻塞了,这时又去唤醒,这样就浪费了很多时间。

【Java线程】Lock、Condition的更多相关文章

  1. 玩转Java多线程(Lock.Condition的正确使用姿势)

    转载请标明博客的地址 本人博客和github账号,如果对你有帮助请在本人github项目AioSocket上点个star,激励作者对社区贡献 个人博客:https://www.cnblogs.com/ ...

  2. java 线程 Lock 锁使用Condition实现线程的等待(await)与通知(signal)

    一.Condition 类 在前面我们学习与synchronized锁配合的线程等待(Object.wait)与线程通知(Object.notify),那么对于JDK1.5 的 java.util.c ...

  3. Java多线程——Lock&Condition

    Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. package ...

  4. Java线程新特性--- Lock

    在Java5中,专门提供了锁对象,利用锁可以方便的实现资源的封锁,用来控制对竞争资源并发访问的控制,这些内容主要集中在java.util.concurrent.locks包下面,里面有三个重要的接口C ...

  5. java线程condition

    子线程先执行一段代码,再主线程再执行一段代码,两个线程都循环执行50遍.用2个condition来实现,一个是子线程的condition,一个是主线程的condition,代码如下: package ...

  6. java中的Condition协作线程接口类

    在Java的Condition接口中,存在的几个方法跟Synchronized中的wait(),waitall(),wait(time ^),这个几个方法一一对应起来,但是在Lock.newCondi ...

  7. 【java并发编程】Lock & Condition 协调同步生产消费

    一.协调生产/消费的需求 本文内容主要想向大家介绍一下Lock结合Condition的使用方法,为了更好的理解Lock锁与Condition锁信号,我们来手写一个ArrayBlockingQueue. ...

  8. Java线程锁一个简单Lock

    /** * @author * * Lock 是java.util.concurrent.locks下提供的java线程锁,作用跟synchronized类似, * 单是比它更加面向对象,两个线程执行 ...

  9. Java线程的概念

    1.      计算机系统 使用高速缓存来作为内存与处理器之间的缓冲,将运算需要用到的数据复制到缓存中,让计算能快速进行:当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了. 缓 ...

  10. 第23章 java线程通信——生产者/消费者模型案例

    第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...

随机推荐

  1. [原创] 小而美 | Mac上鲜为人知,但极大提升效率的小工具

    热爱收集实用又好用的软件,工具类软件不在多,发挥作用,提高效率最重要~推荐几个压箱底的藏货 一.Noizio -自然而然的白噪声,专注工作 Noizio是一款OS X 下的白噪音应用,可以让自己觉着是 ...

  2. class A<T> where T:class 这个泛型类中的Where T:class什么意思

    这是类型参数约束,.NET支持的类型参数约束有以下五种: where T : struct                               T必须是一个结构类型 where T : cla ...

  3. IE8,9下的ajax缓存问题

    最近在做一个网站的登录注册框,前端使用了jquery.由于sign和login不是在单独的页面上,而是以一个弹出框出现.所以决定使用ajax来实现注册和登录功能.本以为可以一帆风顺,结果在测试的时候发 ...

  4. 《JavaScript+DOM编程艺术》的摘要(一)---基本知识点

    保持良好的编程习惯:在同一脚本中,保持引号的一致性,都用单引 var mood = "don\"t worry";alert(mood); 数组:var arr=Arra ...

  5. RTTI、虚函数和虚基类的实现方式、开销分析及使用指导(虚函数的开销很小,就2次操作而已)

    白杨 http://baiy.cn “在正确的场合使用恰当的特性” 对称职的C++程序员来说是一个基本标准.想要做到这点,首先要了解语言中每个特性的实现方式及其开销.本文主要讨论相对于传统 C 而言, ...

  6. Make Yahoo! Web Service REST Calls With C#

    原文 http://developer.yahoo.com/dotnet/howto-rest_cs.html The .NET Framework provides classes for perf ...

  7. oracle中clob字段的使用

    oracle中定义了一个字段是clob的,由于用的是ssh的框架,结果在面向对象存取的时候出现clob类型字段和String类型字段的转换问题.开始查阅了clob字段和String字段的相互转换的方法 ...

  8. opengl模板缓冲区

    相信大家有些人对opengl的模板缓冲区不是很理解,包括我最开始也是,opengl的模板缓冲区其实就是采用过滤的技术来控制那些颜色可以绘制,那些不能进行绘制.这里的过滤技术也就是我们的一个控制方法,主 ...

  9. vmware虚拟机上linux操作系统进行tty1~tty6切换方法和具体步骤

    vmware虚拟机上linux操作系统怎样进行tty1~tty6切换? 现象: Linux的终端机(文字)界面与图形界面间的切换热键为: 进入终端机也就是字符界面(tty1-tty6):[Ctrl] ...

  10. 移动互联与大数据之美-逐浪CMS2 X1.1发布

    北京时间2013年7月1日: 领先的CMS研发软件厂商--上海逐浪CMS软件科技有限公司发布其年中重要更新,并以Zoomla!逐浪CMS2 X1.1为版本号向全球用户投递新版软件. 此次更新包括: 1 ...