【Java多线程与并发库】10.java5的线程锁(读写锁)技术
2016-11-20 20:05
507 查看
Lock&Condition实现线程同步通信
(1)Lock概念
Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,
锁本身也是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们
必须用同一个Lock对象。锁是加在代表要操作的资源的类的内部方法中,而不是
线程代码中!
例子:
package cn.edu.hpu.test;
public class LockTest {
public static
void main(String[] args) {
new TraditionalThreadSynchronized().init();
}
public void init(){
final Outputer outputer=new Outputer();
new Thread(new Runnable(){
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("ABCDEFGHIJKLNOPQRST");
}
}
}
).start();
new Thread(new Runnable(){
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("abcdefghijklmnopqrst");
}
}
}
).start();
}
class Outputer{
public void output(String name){
int len=name.length();
for (int i =
0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}
} 这段代码是没有加锁的,所以会发生在打印某个线程的数据的时候,内存会突然让给其它线程
去打印数据,导致数据只打印一半:
加锁:
package cn.edu.hpu.test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static
void main(String[] args) {
new TraditionalThreadSynchronized().init();
}
public void init(){
final Outputer outputer=new Outputer();
new Thread(new Runnable(){
public
void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("ABCDEFGHIJKLNOPQRST");
}
}
}
).start();
4000
new Thread(new Runnable(){
public
void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("abcdefghijklmnopqrst");
}
}
}
).start();
}
class Outputer{
Lock lock = new ReentrantLock();//创建一个锁
public void output(String name){
int len=name.length();
lock.lock();//上锁
try {
for (int i =
0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}finally{
//这么做是防止线程死掉大家都进不去
lock.unlock();//开锁
}
}
}
}
加锁之后所有的线程都不会被打断:
(2)读写锁
读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,
这是由JVM自己控制的,你只要加好相应的锁即可。如果代码只读数据,可以很
多人同时读,但是不能同时写,那就加读锁;如果代码修改数据,只能有一个人
在写,且不能同时读取,那就加写锁。总之,读的时候加读锁,写的时候加写锁。
读写锁例子:
产生三个线程,用来读数据,然后产生另外三个线程,用来写数据。如果不加线程锁,
我们就会看到读和写的线程交替运行,也即是“
读中有写,写中有读”。
package cn.edu.hpu.test;
import java.util.Random;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
public static
void main(String[] args) {
final Queue q =
new Queue();
for (int i =
0; i < 3; i++) {
new Thread(){
public void run(){
while(true){
q.get();
}
}
}.start();
new Thread(){
public void run(){
while(true){
q.put(new Random().nextInt(10000));
}
}
}.start();
}
}
}
class Queue{
private Object data = null;//共享数据,只有一个线程能写该数据,但可以有多个线程同时读该数据
public void get(){
System.out.println(Thread.currentThread().getName().toString()+"准备读取数据");
try {
Thread.sleep((long)Math.random()*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName().toString()+"读取数据:"+data);
}
public void put(Object data){
System.out.println(Thread.currentThread().getName().toString()+"准备改写数据");
try {
Thread.sleep((long)Math.random()*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data=data;
System.out.println(Thread.currentThread().getName().toString()+"改写数据为:"+data);
}
}
效果:
可以看到,在线程5准备改写数据的时候,线程1去改写了数据,然后等线程1准备改写数据
的时候,线程5去改写了数据,此时对于变量data是线程不安全的。
如果我们上了读写锁,读的时候没有写,写的时候没有读和其它写,
即是“读中无写,写中无读写”。
package cn.edu.hpu.test;
import java.util.Random;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
public static
void main(String[] args) {
final Queue q = new Queue();
for (int i =
0; i < 3; i++) {
new Thread(){
public
void run(){
while(true){
q.get();
}
}
}.start();
new Thread(){
public
void run(){
while(true){
q.put(new Random().nextInt(10000));
}
}
}.start();
}
}
}
class Queue{
private Object data =
null;//共享数据,只有一个线程能写该数据,但可以有多个线程同时读该数据
private ReentrantReadWriteLock rwl =
new ReentrantReadWriteLock();
public void get(){
rwl.readLock().lock();
try {
System.out.println(Thread.currentThread().getName().toString()+"准备读取数据");
Thread.sleep((long)Math.random()*1000);
System.out.println(Thread.currentThread().getName().toString()+"读取数据:"+data);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
rwl.readLock().unlock();
}
}
public void put(Object data){
rwl.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName().toString()+"准备改写数据");
Thread.sleep((long)Math.random()*1000);
this.data=data;
System.out.println(Thread.currentThread().getName().toString()+"改写数据为:"+data);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
rwl.writeLock().unlock();
}
}
}
结果:
可以看到,当一个线程去写的时候,永远都不会被读取动作或者改写动作打断,而当一个线程
去读取的时候,永远都不会被改写动作打断,但是可以被另外的读取动作打断,这是合理的。
这里,大家看一下JavaAPI给我们的一个读写锁的例子:
public class CacheData {
Object data;
volatile boolean cacheValid;
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCacheData(){
rwl.readLock().lock();
if(!cacheValid){//是否有缓存可用
//在加写锁之前必须释放读锁
rwl.readLock().unlock();
rwl.writeLock().lock();
//再次检查一下校验状态,以防止有其他线程修改
if(!cacheValid){
data=... //去真实的库中取值
cacheValid=true;
}
//在释放写锁之前要加读锁
rwl.readLock().lock();
rwl.writeLock().unlock();
}
use(data);//直接取缓存来使用
rwl.readLock().unlock();
}
}
看起来像是一个多个线程操作缓存数据的代码。在hibernate的二级缓存中出现过类似思想,
例如:
User user = session.get(id,User.class);
User user = session.load(id,User.class);
上下的区别是:
上面的是直接从数据库中把相应id的user数据取出来封装到User对象中去,
如果没有这个数据,返回的值是null;
下面是,不管数据库中有没有这个记录,都会得到一个User对象代理,实际
就是User$Proxy,该代理大概长这个样子(伪代码):
User$Proxy extends User{
private Integer id = id;
User realUser = null;
getName(){
if(realUser ==
null){
realUser = session.get(id);
if(realUser ==
null) throw exception;
}
return realUser;
}
}
即是,当真正的User不存在的时候,代理User从数据中去取数据,如果下次请求,
代理发现之前去过,就把缓存数据提供出去。
(3)缓存系统模拟
接下来我们通过使用读写锁,自己设计一个缓存系统。
package cn.edu.hpu.test;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheDemo {
private Map<String,Object> cache=new HashMap<String,Object>();//缓存池
private ReadWriteLock rwl=new ReentrantReadWriteLock();
public static
void main(String[] args) {
CacheDemo cacheDemo=new CacheDemo();
System.out.println("第一次取数据结果:"+cacheDemo.getData("1"));
System.out.println("第二次取数据结果:"+cacheDemo.getData("1"));
}
public Object getData(String key){
rwl.readLock().lock();
Object value = null;
try {
value = cache.get(key);//先去缓存中去取
if (value == null) {
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
if(value ==
null){
value = MySqlDB.getData(key);//实际去数据库取数据
cache.put(key, value);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
rwl.writeLock().unlock();
}
rwl.readLock().lock();
}
} catch (Exception e) {
e.printStackTrace();
}finally{
rwl.readLock().unlock();
}
return value;
}
}
class MySqlDB{
public static Object getData(String key)
throws InterruptedException{
Object data=null;
System.out.println("获取数据库连接...");
Thread.sleep(2000);
System.out.println("开启事务...");
Thread.sleep(2000);
System.out.println("编译sql语句...");
Thread.sleep(2000);
System.out.println("查找数据...");
Thread.sleep(5000);
System.out.println("返回数据...");
Thread.sleep(2000);
if(key.equals("1")){
data=new String("张三");
}
System.out.println("关闭事务...");
Thread.sleep(2000);
System.out.println("关闭数据库连接...");
Thread.sleep(2000);
return data;
}
}
结果:
这样就是实现了,当第一次取数据的时候去数据库取,后面取数据从缓存中取,
而且其中的读写都是线程绝对安全的。
出处:http://blog.csdn.net/acmman/article/details/52902128
(1)Lock概念
Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,
锁本身也是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们
必须用同一个Lock对象。锁是加在代表要操作的资源的类的内部方法中,而不是
线程代码中!
例子:
package cn.edu.hpu.test;
public class LockTest {
public static
void main(String[] args) {
new TraditionalThreadSynchronized().init();
}
public void init(){
final Outputer outputer=new Outputer();
new Thread(new Runnable(){
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("ABCDEFGHIJKLNOPQRST");
}
}
}
).start();
new Thread(new Runnable(){
public void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("abcdefghijklmnopqrst");
}
}
}
).start();
}
class Outputer{
public void output(String name){
int len=name.length();
for (int i =
0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
}
} 这段代码是没有加锁的,所以会发生在打印某个线程的数据的时候,内存会突然让给其它线程
去打印数据,导致数据只打印一半:
加锁:
package cn.edu.hpu.test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static
void main(String[] args) {
new TraditionalThreadSynchronized().init();
}
public void init(){
final Outputer outputer=new Outputer();
new Thread(new Runnable(){
public
void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("ABCDEFGHIJKLNOPQRST");
}
}
}
).start();
4000
new Thread(new Runnable(){
public
void run() {
while(true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
outputer.output("abcdefghijklmnopqrst");
}
}
}
).start();
}
class Outputer{
Lock lock = new ReentrantLock();//创建一个锁
public void output(String name){
int len=name.length();
lock.lock();//上锁
try {
for (int i =
0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}finally{
//这么做是防止线程死掉大家都进不去
lock.unlock();//开锁
}
}
}
}
加锁之后所有的线程都不会被打断:
(2)读写锁
读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,
这是由JVM自己控制的,你只要加好相应的锁即可。如果代码只读数据,可以很
多人同时读,但是不能同时写,那就加读锁;如果代码修改数据,只能有一个人
在写,且不能同时读取,那就加写锁。总之,读的时候加读锁,写的时候加写锁。
读写锁例子:
产生三个线程,用来读数据,然后产生另外三个线程,用来写数据。如果不加线程锁,
我们就会看到读和写的线程交替运行,也即是“
读中有写,写中有读”。
package cn.edu.hpu.test;
import java.util.Random;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
public static
void main(String[] args) {
final Queue q =
new Queue();
for (int i =
0; i < 3; i++) {
new Thread(){
public void run(){
while(true){
q.get();
}
}
}.start();
new Thread(){
public void run(){
while(true){
q.put(new Random().nextInt(10000));
}
}
}.start();
}
}
}
class Queue{
private Object data = null;//共享数据,只有一个线程能写该数据,但可以有多个线程同时读该数据
public void get(){
System.out.println(Thread.currentThread().getName().toString()+"准备读取数据");
try {
Thread.sleep((long)Math.random()*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName().toString()+"读取数据:"+data);
}
public void put(Object data){
System.out.println(Thread.currentThread().getName().toString()+"准备改写数据");
try {
Thread.sleep((long)Math.random()*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data=data;
System.out.println(Thread.currentThread().getName().toString()+"改写数据为:"+data);
}
}
效果:
可以看到,在线程5准备改写数据的时候,线程1去改写了数据,然后等线程1准备改写数据
的时候,线程5去改写了数据,此时对于变量data是线程不安全的。
如果我们上了读写锁,读的时候没有写,写的时候没有读和其它写,
即是“读中无写,写中无读写”。
package cn.edu.hpu.test;
import java.util.Random;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
public static
void main(String[] args) {
final Queue q = new Queue();
for (int i =
0; i < 3; i++) {
new Thread(){
public
void run(){
while(true){
q.get();
}
}
}.start();
new Thread(){
public
void run(){
while(true){
q.put(new Random().nextInt(10000));
}
}
}.start();
}
}
}
class Queue{
private Object data =
null;//共享数据,只有一个线程能写该数据,但可以有多个线程同时读该数据
private ReentrantReadWriteLock rwl =
new ReentrantReadWriteLock();
public void get(){
rwl.readLock().lock();
try {
System.out.println(Thread.currentThread().getName().toString()+"准备读取数据");
Thread.sleep((long)Math.random()*1000);
System.out.println(Thread.currentThread().getName().toString()+"读取数据:"+data);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
rwl.readLock().unlock();
}
}
public void put(Object data){
rwl.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName().toString()+"准备改写数据");
Thread.sleep((long)Math.random()*1000);
this.data=data;
System.out.println(Thread.currentThread().getName().toString()+"改写数据为:"+data);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
rwl.writeLock().unlock();
}
}
}
结果:
可以看到,当一个线程去写的时候,永远都不会被读取动作或者改写动作打断,而当一个线程
去读取的时候,永远都不会被改写动作打断,但是可以被另外的读取动作打断,这是合理的。
这里,大家看一下JavaAPI给我们的一个读写锁的例子:
public class CacheData {
Object data;
volatile boolean cacheValid;
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCacheData(){
rwl.readLock().lock();
if(!cacheValid){//是否有缓存可用
//在加写锁之前必须释放读锁
rwl.readLock().unlock();
rwl.writeLock().lock();
//再次检查一下校验状态,以防止有其他线程修改
if(!cacheValid){
data=... //去真实的库中取值
cacheValid=true;
}
//在释放写锁之前要加读锁
rwl.readLock().lock();
rwl.writeLock().unlock();
}
use(data);//直接取缓存来使用
rwl.readLock().unlock();
}
}
看起来像是一个多个线程操作缓存数据的代码。在hibernate的二级缓存中出现过类似思想,
例如:
User user = session.get(id,User.class);
User user = session.load(id,User.class);
上下的区别是:
上面的是直接从数据库中把相应id的user数据取出来封装到User对象中去,
如果没有这个数据,返回的值是null;
下面是,不管数据库中有没有这个记录,都会得到一个User对象代理,实际
就是User$Proxy,该代理大概长这个样子(伪代码):
User$Proxy extends User{
private Integer id = id;
User realUser = null;
getName(){
if(realUser ==
null){
realUser = session.get(id);
if(realUser ==
null) throw exception;
}
return realUser;
}
}
即是,当真正的User不存在的时候,代理User从数据中去取数据,如果下次请求,
代理发现之前去过,就把缓存数据提供出去。
(3)缓存系统模拟
接下来我们通过使用读写锁,自己设计一个缓存系统。
package cn.edu.hpu.test;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CacheDemo {
private Map<String,Object> cache=new HashMap<String,Object>();//缓存池
private ReadWriteLock rwl=new ReentrantReadWriteLock();
public static
void main(String[] args) {
CacheDemo cacheDemo=new CacheDemo();
System.out.println("第一次取数据结果:"+cacheDemo.getData("1"));
System.out.println("第二次取数据结果:"+cacheDemo.getData("1"));
}
public Object getData(String key){
rwl.readLock().lock();
Object value = null;
try {
value = cache.get(key);//先去缓存中去取
if (value == null) {
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
if(value ==
null){
value = MySqlDB.getData(key);//实际去数据库取数据
cache.put(key, value);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
rwl.writeLock().unlock();
}
rwl.readLock().lock();
}
} catch (Exception e) {
e.printStackTrace();
}finally{
rwl.readLock().unlock();
}
return value;
}
}
class MySqlDB{
public static Object getData(String key)
throws InterruptedException{
Object data=null;
System.out.println("获取数据库连接...");
Thread.sleep(2000);
System.out.println("开启事务...");
Thread.sleep(2000);
System.out.println("编译sql语句...");
Thread.sleep(2000);
System.out.println("查找数据...");
Thread.sleep(5000);
System.out.println("返回数据...");
Thread.sleep(2000);
if(key.equals("1")){
data=new String("张三");
}
System.out.println("关闭事务...");
Thread.sleep(2000);
System.out.println("关闭数据库连接...");
Thread.sleep(2000);
return data;
}
}
结果:
这样就是实现了,当第一次取数据的时候去数据库取,后面取数据从缓存中取,
而且其中的读写都是线程绝对安全的。
出处:http://blog.csdn.net/acmman/article/details/52902128
相关文章推荐
- 【Java多线程与并发库】10.java5的线程锁(读写锁)技术
- java并发-多线程之传统线程之互斥技术(Synchronized)(3)
- 【Java多线程与并发库】1.传统线程技术回顾
- Java多线程与并发应用-(4)-传统线程通信技术试题
- java多线程之线程并发库的线程锁技术
- 多线程并发库高级应用 之 java5中的线程并发库--线程锁技术
- 【Java多线程与并发库】01 传统线程技术
- 多线程并发库高级应用 之 java5中的线程并发库--线程锁技术
- Java多线程与并发应用-(3)-传统线程通信技术及生产者消费者模式
- 【Java多线程与并发库】1.传统线程技术回顾
- 【java多线程与并发库】---传统java多线程<1>线程基本概念
- 多线程并发库高级应用 之 java5中的线程并发库--线程池、Callable&Future
- Java5并发库之锁(二)——读写锁技术的妙用
- 黑马程序员——Java多线程与线程并发库高级应用笔记
- 【java多线程与并发库】---传统java多线程<4> .线程状态及优先级
- 多线程并发库高级应用 之 java5中的线程并发库--线程池、Callable&Future
- 【java多线程与并发库】---传统java多线程<5> 线程控制
- JAVA多线程并发同步,以及线程终止
- java多线程与并发之java线程简介(六)
- (13)多线程与并发库之java5阻塞队列(BlockingQueue)的应用----子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程循环100次,如此循环50次