java线程(4)——线程同步的锁技术

同步

同步,字面来看,有点一起工作的意思。但在线程同步中,“同”意为协同、互相配合。

比如:

A、B两个线程,并不是说两个线程必须同时一起工作,而是说互相配合工作,在某个时间可能线程A要等线程B去工作,之后线程A才能继续工作。如果理解不了,可以参考java线程(2)——模拟生产者与消费者中的例子。

思考:
为什么会有线程同步?上面例子中线程A为什么要等B工作之后才能继续工作?

在生产者和消费者的例子中,作为消费者,如果生产者还没生产,他就没办法消费只能等着,否则就会出现问题。同样,生产者也不能无止尽的生产,毕竟篮子的容量是有限的。

如果这个例子看不出来问题的严重性,我们可以想想银行取钱的例子,出现问题这后果就相当严重了。所以,多线程如果使用不当,很容易出现线程不安全的问题。尤其涉及同步问题时,一定要小心使用。

实现方式

主要有两种:synchronized和lock,前者被称为内置锁,后者叫外置锁。
关于这两者的不同之处,介绍之后再比较。

1、 synchronized关键字

在消费者和生产者的例子中,我们使用到了synchronized关键字,将篮子的“取”和“放”都做了限制,来实现线程同步。

2、lock

先来看一下他的接口定义,也是在包java.util.concurrent下。

package java.util.concurrent.locks;

public interface Lock {
  void lock();
  void lockInterruptibly() throws InterruptedException;
  boolean tryLock();
  void unlock();
  Condition newCondition();

}

在上篇博客中介绍了一个使用synchronized关键字实现打印当前时间的方法,我们这里来修改一下。

   /**
     * 获得当前时间
     */
    public static void getTime() {

        lock.lock();
        try {
            System.out.println("1、进入获取时间方法=====");
            Date date = new Date();
            DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String time = format.format(date);
            System.out.println("2、" + Thread.currentThread().getName() + ":"
                    + time);
            System.out.println("3、获取时间成功=====");
            System.out.println();
        } finally {
            lock.unlock();
        }
    }

//调用
    static Lock lock=null;
    public static void main(String[] args) {
        lock = new ReentrantLock();

        for (int i = 0; i < 100; i++) {

            new Thread(new Runnable() {

                @Override
                public void run() {
                    getTime();

                }

            }).start();

        }
    }

结果同样满足条件:

这里写图片描述

读写锁

Lock中的另一个亮点就是对读写锁的支持。在读写锁中,把对共享资源的访问者划分为读者和写者,读者进行读访问,写者进行写操作。但是,一个读写锁同时能有一个或多个读者,但不能同时既有读者又有写者。

java中的类为ReadWriteLock,提供了读锁和写锁的方法,返回类型都是Lock

public interface ReadWriteLock {

    Lock readLock();

    Lock writeLock();
}

举个例子:

在例子中,data为所要读写的数据,提供了读和写的方法。

    private Object data = null;// 共享数据
    /**
     * 读数据
     */
    public void get() {
        try {
          //准备数据
            System.out.println(Thread.currentThread().getName()
                    + " be ready to read data!");
            //sleep一段时间,主要为了更明显的体现出差别,无实际意义
            Thread.sleep((long) (Math.random() * 1000));
            //打印取出数据
            System.out.println(Thread.currentThread().getName()
                    + "have read data :" + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 写数据
     * 
     * @param data
     */
    public void put(Object data) {
        try {
            System.out.println(Thread.currentThread().getName()
                    + " be ready to write data!");
            Thread.sleep((long) (Math.random() * 1000));
            this.data = data; // 给data赋值
            System.out.println(Thread.currentThread().getName()
                    + " have write data: " + data);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } 

    }

执行结果:

这里写图片描述

从结果上看,当程序刚启动时,多个读写线程同时触发,等待执行。无法实现“多读”,也影响了写的操作。

那么,现在来看下加上读写锁之后的效果。

ReadWriteLock rwl = new ReentrantReadWriteLock();

    /**
     * 读数据
     */
    public void get(){
        rwl.readLock().lock(); //读锁
        try {
            ....略
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally{
            rwl.readLock().unlock(); //释放读锁
        }
    }

与上面没有使用读写锁相比,最大的不同在于,他可以实现“多读”,并且“读”与“写”互斥。例如:Thread0,2,4进入之后,可以同时读数据。并没有多条写数据同时写。

这里写图片描述

点睛之笔

1)Lock与ReadWriteLock

以Lock第一个例子ReentrantLock来说,他实现了标准的互斥操作,有点“独占”的感觉。这种情况下,不允许多读,多写,读写等情况发生。

读写锁ReadWriteLock中,允许多个读线程同时访问一个资源,但不允许多个写线程同时访问。更符合实际操作和需要。



2)Lock与synchronized

synchronized比较武断,只能实现互斥,无法实现读写锁的允许多读操作。在并发量比较小的情况下,他是一个不错的选择。但并发量较大的话,他的性能下降很严重。

Lock锁技术除了第一点介绍的优点外,他还具有可重入性,即可以进行多次加锁,也更具公平性。不过为了避免死锁,需要在finally中释放锁。

posted @ 2016-04-12 20:53  Sherry&Yang  阅读(151)  评论(0编辑  收藏  举报