您的位置:首页 > 编程语言 > Java开发

【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

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐