java基础(8)-----Lock and synchronized

     synchronized可以实现同步,为什么我们还需要Lock呢?jdk5之后增加了一个新的包java.util.concurrent,java在这里提供了新的并发编程的工具,其中下面的Locks包都是关于Lock这一部分的,如图:

1.和synchronized比较

     synchronized可以修饰变量、代码块、方法,对象中有这个关键字的代码被访问的时候,线程就可以获取到此对象的锁,注意是整个对象的锁,而不是某个方法或者代码块,其他线程再次访问这个对象的任何一处被synchronized修饰的代码时都会被阻塞,当然访问不被synchronized修饰的方法仍然可以访问,下面是测试代码:

/**
 * @author zisong yue
 * @date 2018-11-22
 * @description synchronized测试
 */
public class SyncronizedTest {

    synchronized void synA(String threadName){
        System.out.println(threadName + "============excute synA method============");
        try {
            Thread.sleep(5000);
            //wait(5000);
            System.out.println(threadName + "============sleep 5 seconds end============");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized void synB(String threadName){
        System.out.println(threadName + "============excute synB method============");
    }

    public static void main(String[] args) {

        SyncronizedTest syncronizedTest = new SyncronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                syncronizedTest.synA(Thread.currentThread().getName());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                syncronizedTest.synB(Thread.currentThread().getName());
            }
        }).start();
    }
}

测试结果:

     值得注意的是,Thread.sleep()方法并不会使当前线程释放锁,我们看下Thread类对这个方法的介绍就明白了,其中有一段这样写的:The thread does not lose ownership of any monitors.翻译过来就是,当前线程不会失去任何监视器的所有权,这里的监视器的所有权其实就是我们通常说的锁,是不是很有意思,哈哈

    /**
     * Causes the currently executing thread to sleep (temporarily cease
     * execution) for the specified number of milliseconds, subject to
     * the precision and accuracy of system timers and schedulers. The thread
     * does not lose ownership of any monitors.
     *
     * @param  millis
     *         the length of time to sleep in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public static native void sleep(long millis) throws InterruptedException;

如果这里的Thread.sleep改成了Object对象的wait()方法呢,又会是什么样子

/**
 * @author zisong yue
 * @date 2018-11-22
 * @description synchronized测试
 */
public class SyncronizedTest {

    synchronized void synA(String threadName){
        System.out.println(threadName + "============excute synA method============");
        try {
            //Thread.sleep(5000);
            wait(5000);
            System.out.println(threadName + "============sleep 5 seconds end============");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized void synB(String threadName){
        System.out.println(threadName + "============excute synB method============");
    }

    public static void main(String[] args) {

        SyncronizedTest syncronizedTest = new SyncronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                syncronizedTest.synA(Thread.currentThread().getName());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                syncronizedTest.synB(Thread.currentThread().getName());
            }
        }).start();
    }
}

测试结果:

我们发现,线程B在线程A没有执行完时,居然获得了锁,好的,我们看下Object的wait()方法

/**
     * Causes the current thread to wait until another thread invokes the
     * {@link java.lang.Object#notify()} method or the
     * {@link java.lang.Object#notifyAll()} method for this object.
     * In other words, this method behaves exactly as if it simply
     * performs the call {@code wait(0)}.
     * <p>
     * The current thread must own this object's monitor. The thread
     * releases ownership of this monitor and waits until another thread
     * notifies threads waiting on this object's monitor to wake up
     * either through a call to the {@code notify} method or the
     * {@code notifyAll} method. The thread then waits until it can
     * re-obtain ownership of the monitor and resumes execution.
     * <p>
     * As in the one argument version, interrupts and spurious wakeups are
     * possible, and this method should always be used in a loop:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait();
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     * This method should only be called by a thread that is the owner
     * of this object's monitor. See the {@code notify} method for a
     * description of the ways in which a thread can become the owner of
     * a monitor.
     *
     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of the object's monitor.
     * @throws  InterruptedException if any thread interrupted the
     *             current thread before or while the current thread
     *             was waiting for a notification.  The <i>interrupted
     *             status</i> of the current thread is cleared when
     *             this exception is thrown.
     * @see        java.lang.Object#notify()
     * @see        java.lang.Object#notifyAll()
     */
    public final void wait() throws InterruptedException {
        wait(0);
    }

里面注释有一段说明:The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the {@code notify} method or the {@code notifyAll} method 翻译过来就是,当前线程必须拥有对象的监视器(也就是需要配合同步锁使用)。线程会释放监视器的所有权并且一直等待,直到另一个线程通过执行notify或者notifyAll方法重新唤醒

我们再看下面一个例子:

/**
 * @author zisong yue
 * @date 2018-11-22
 * @description synchronized测试
 */
public class SyncronizedTest {

    private Object locker = new Object();

    synchronized void synA(String threadName){
        System.out.println(threadName + "============excute synA method============");
        try {
            //Thread.sleep(5000);
            wait(5000);
            System.out.println(threadName + "============sleep 5 seconds end============");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    synchronized void synB(String threadName){
        System.out.println(threadName + "============excute synB method============");
    }

    void synC(String threadName){
        synchronized (locker){
            System.out.println(threadName + "============excute synC method============");
        }

    }

    public static void main(String[] args) {

        final SyncronizedTest syncronizedTest = new SyncronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                syncronizedTest.synA(Thread.currentThread().getName());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                syncronizedTest.synC(Thread.currentThread().getName());
            }
        }).start();
    }
}

测试结果:

可以发现,locker是另外一个对象,因此访问此synchronized内的代码块是不需要获取SynchronizedTest对象的锁的。这种也常被用来设计线程互斥的实现。

扯远了.......我们接着说synchronized

当其他线层访问被synchronized修饰的代码时,被阻塞,如果之前获取到锁的线程在执行一个非常耗时的操作,那么其他线程就只能一直等待下去,这会非常耗费资源。如果只阻塞一段时间或者可以响应中断,那么就可以在阻塞超时或者中断之后,线程可以接着执行下面的代码,Lock这里就可以办到了。注意synchronized的获取锁和释放锁完全由jvm完成,Lock需要手动获取锁和释放锁,因此释放锁必须要放在finally{}代码块中

2.java.util.concurrent.locks包下面的类,源码分析,结合实例理解应用场景

2.1lock

lock是一个接口,有以下几个方法,上源码

public interface Lock {

    /**
     * Acquires the lock.
     * <p>If the lock is not available then the current thread becomes
     * disabled for thread scheduling purposes and lies dormant until the
     * lock has been acquired.
     */
    void lock();

    /**
     * Acquires the lock unless the current thread is
     * {@linkplain Thread#interrupt interrupted}.
     *
     * <p>Acquires the lock if it is available and returns immediately.
     *
     * <p>If the lock is not available then the current thread becomes
     * disabled for thread scheduling purposes and lies dormant until
     * one of two things happens:
     *
     * <ul>
     * <li>The lock is acquired by the current thread; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts} the
     * current thread, and interruption of lock acquisition is supported.
     * </ul>
     */
    void lockInterruptibly() throws InterruptedException;

    /**
     * Acquires the lock only if it is free at the time of invocation.
     *
     * <p>Acquires the lock if it is available and returns immediately
     * with the value {@code true}.
     * If the lock is not available then this method will return
     * immediately with the value {@code false}.
     */
    boolean tryLock();

    /**
     * Acquires the lock if it is free within the given waiting time and the
     * current thread has not been {@linkplain Thread#interrupt interrupted}.
     *
     * <p>If the lock is available this method returns immediately
     * with the value {@code true}.
     * If the lock is not available then
     * the current thread becomes disabled for thread scheduling
     * purposes and lies dormant until one of three things happens:
     * <ul>
     * <li>The lock is acquired by the current thread; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts} the
     * current thread, and interruption of lock acquisition is supported; or
     * <li>The specified waiting time elapses
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    /**
     * Releases the lock.
     */
    void unlock();

    /**
     * Returns a new {@link Condition} instance that is bound to this
     * {@code Lock} instance.
     */
    Condition newCondition();
}

void lock()方法:如果锁可用,那么获取到锁,如果所有不可用,则进入伪休眠状态直到获取到锁,继续执行下面程序

void lockInterruptibly():如果锁可用,那么获取到锁,如果所有不可用,则进入伪休眠状态直到获取到锁或者被其他线程中断(执行Thread.interrupt()方法),继续执行下面程序

boolean trylock():如果锁可用,那么获取到锁并且立即返回true,如果所有不可用,则立即返回false

boolean trylock(long time, TimeUnit unit):如果锁可用,那么获取到锁并且立即返回true,如果所有不可用,则进入伪休眠状态直到获取到锁或者被其他线程中断(抛出中断异常InterruptedException,这个时候可以catch异常并处理异常的逻辑,之后执行下面的程序)或者超出了指定的时间(返回false)

void unlock():释放锁

Condition newConditon():返回一个绑定到lock实例上的condition实例

如果是synchronized修饰的代码,阻塞时不能被中断的,这里Lock提供的lockInterruptibly()可以响应中断,tryLock(long time, TimeUnit unit)可以在等待一段时间之后返回false,也相当与中断了

2.2ReenTrantLock可重入锁

怎么理解可重入锁呢,我们看到上面提到的源码注释,都是在说明线程是否获得对象监视器的拥有权,很显然,这里的对象锁(即对象监视器的所有权)是以线程为单位的。所以,如果一个线程获取到了对象锁,那么这个对象内的任何一处需要获取锁才能执行的代码块,都可以执行。重入锁测试:

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author zisong yue
 * @date 2018-11-22
 * @description ReenTrantLock测试
 */
public class SyncronizedTest {
    final Lock reentrantLock = new ReentrantLock();

    void synA(String threadName){
        reentrantLock.lock();
        System.out.println(threadName + "============excute synA method============");
        this.synB(threadName);
        try {
            Thread.sleep(5000);
            //wait(5000);
            System.out.println(threadName + "============sleep 5 seconds end============");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            reentrantLock.unlock();
        }
    }

    void synB(String threadName){
        reentrantLock.lock();
        System.out.println(threadName + "============excute synB method============");
        reentrantLock.unlock();
    }

    public static void main(String[] args) {

        SyncronizedTest syncronizedTest = new SyncronizedTest();

        new Thread(new Runnable() {
            @Override
            public void run() {
                syncronizedTest.synA(Thread.currentThread().getName());
            }
        }).start();
    }
}

测试结果:

关联condition使用场景

     java官方网站给出一个用condition实现bounded-buffer(有界缓冲区)例子,其实这也是PriorityBlokingQueue(可控制优先级的阻塞队列)的实现原理,我们执行复制过来:

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, takeptr, 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,如果缓冲数组满了,所有的写线程都会被阻塞,这时有一个读线程冲数组中读走了一个数据,通过notFull.sinal()方法唤醒队列中的一个写线程开始写数据。如果缓冲数组空了,所有的读线程会被阻塞,这时有一个写线程向数组中写入一个数据,notEmpty.singal()方法唤醒队列中的一个读线程开始读数据。

还有一个典型的应用,多线程的时序控制。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Main {
    static class NumberWrapper {
        public int value = 1;
    }

    public static void main(String[] args)  {
        //初始化可重入锁
        final Lock lock = new ReentrantLock();

        //第一个条件当屏幕上输出到3
        final Condition reachThreeCondition = lock.newCondition();
        //第二个条件当屏幕上输出到6
        final Condition reachSixCondition = lock.newCondition();

        //NumberWrapper只是为了封装一个数字,一边可以将数字对象共享,并可以设置为final
        //注意这里不要用Integer, Integer 是不可变对象
        final NumberWrapper num = new NumberWrapper();
        //初始化A线程
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                //需要先获得锁
                lock.lock();
                try {
                    System.out.println("threadA start write");
                    //A线程先输出前3个数
                    while (num.value <= 3) {
                        System.out.println(num.value);
                        num.value++;
                    }
                    //输出到3时要signal,告诉B线程可以开始了
                    reachThreeCondition.signal();
                } finally {
                    lock.unlock();
                }
                lock.lock();
                try {
                    //等待输出6的条件
                    reachSixCondition.await();
                    System.out.println("threadA start write");
                    //输出剩余数字
                    while (num.value <= 9) {
                        System.out.println(num.value);
                        num.value++;
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }

        });


        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();

                    while (num.value <= 3) {
                        //等待3输出完毕的信号
                        reachThreeCondition.await();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
                try {
                    lock.lock();
                    //已经收到信号,开始输出4,5,6
                    System.out.println("threadB start write");
                    while (num.value <= 6) {
                        System.out.println(num.value);
                        num.value++;
                    }
                    //4,5,6输出完毕,告诉A线程6输出完了
                    reachSixCondition.signal();
                } finally {
                    lock.unlock();
                }
            }

        });

        //启动两个线程
        threadB.start();
        threadA.start();
    }
}

这个例子是从别人那里复制过来的,看的时候你可能会有一个和我一样的疑问,threadB执行lock.lock()方法获取到锁,之后调用了reachThreeCondition.await()方法阻塞,后面不会再执行lock.unlock()方法释放锁,threadA在执行lock.lock()是怎么获取到锁的呢。我们看下await()方法做了什么,还是上源码:

 /**
     * Causes the current thread to wait until it is signalled or
     * {@linkplain Thread#interrupt interrupted}.
     *
     * <p>The lock associated with this {@code Condition} is atomically
     * released and the current thread becomes disabled for thread scheduling
     * purposes and lies dormant until <em>one</em> of four things happens:
     * <ul>
     * <li>Some other thread invokes the {@link #signal} method for this
     * {@code Condition} and the current thread happens to be chosen as the
     * thread to be awakened; or
     * <li>Some other thread invokes the {@link #signalAll} method for this
     * {@code Condition}; or
     * <li>Some other thread {@linkplain Thread#interrupt interrupts} the
     * current thread, and interruption of thread suspension is supported; or
     * <li>A &quot;<em>spurious wakeup</em>&quot; occurs.
     * </ul>
     */
    void await() throws InterruptedException;
The lock associated with this {@code Condition} is atomically released and the current thread becomes disabled for thread scheduling purposes and lies dormant until <em>one</em> of four things happens 翻译过来就是:和这个condition关联的锁会被原子的释放,并且当前线程进入休眠状态直到一下四件事中一件发生,那四件事就不翻译了,自己可以看看。

现在我们看到了它相对于Object 的wait()方法的强大之处,对线程有了更加精细的控制,通过condition方法await阻塞一类线程,sigal方法唤醒指定一类线程,而Object的notify方法无法做到,比如官网的例子,它无法分别唤醒的是读线程还是写线程

这个包下面还有几个类ReenTrantReadWriteLock、StampedLock,以后再补充吧,有不完善的地方,欢迎评论指正

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值