多线程——同步机制synchronized、lock

目录

 

1、方法内的局部变量是线程安全的

2、实例化对象的变量是非线程安全的

3、同步锁方法

3.1 锁重入

3.2 锁的自动释放

3.3 同步没有继承性

4、同步语句块

4.1 同步语句块之间的同步性

4.2 同步方法和同步块的同步性

4.3 将 非this对象 作为  锁

5、 静态同步方法  和   以类为锁的同步块

6、通常情况下最好不要将String字符串作为锁

7、锁的改变

8、死锁

8.1 预防死锁

9、Lock锁

9.1 ReentrantLock可重入锁

9.1.1、Condition —— 等待/通知

10、ReadWriteLock 读写锁

10.1、ReentrantReadWriteLock 可重入读写锁


1、方法内的局部变量是线程安全的

假设方法中有一个局部变量,多个线程同时访问这个方法,同时使用这个局部变量,仍然是线程安全的。

为什么是这样呢?

jvm在运行时,内存中分区:程序计数器、本地方法栈、虚拟机栈、堆、方法区。

方法区:用于存储我们的代码,当然这里的代码和我们编程的代码不一样,是将编程的代码经过解析后,转换成JVM能使用的代码结构方式。

虚拟机栈:线程私有的,生命周期和线程一样,线程里的方法就对应于栈中的一个栈帧。出栈入栈的动态过程就和一般程序的一样(如果不了解栈帧,可能无法理解)。栈帧的内容是动态变化的,即栈帧是一个方法执行的动态体现。

需要知道虚拟机栈的概念,试想,当多个线程同时访问某一个方法 aa() 时,其实就是在各自的 run() 方法中调用这个方法,aa()方法的代码就存在方法区,我们根据方法的具体代码内容,动态地在一个虚拟机栈中体现出来,方法的执行模型就是栈帧,栈帧是线程私有的,也就是说:每个线程执行 aa() 方法时,都会先为aa()方法分配一个栈帧,然后才会去执行aa()方法内的具体代码,所以不管你怎么操作方法内的变量,都始终是操作属于自己的那一份栈帧,线程之前相互不影响,也就是“线程安全”。

自己做了个实验:两个线程同时访问一个方法,然后操作其变量,打印出变量结果。

public class Demon2 { //实体类,作为测试的方法内的变量
    public int num = 0;
    public int getNum() {return num; }
    public void setNum(int num) { this.num = num; }
}
public class Demon {  //测试的方法
    public void ff(){
        Demon2 d = new Demon2();
        d.setNum(++d.num);
        System.out.println(d.num);
    }
}

public class DemonThread2 extends Thread {  //线程2
    Demon demon;
    public  DemonThread2(Demon d){ this.demon = d; }
    @Override
    public void run(){ demon.ff(); }
}

public class DemonThread extends Thread { //线程2
    Demon demon;
    public  DemonThread(Demon d){ this.demon = d; }
    @Override
    public void run(){
        demon.ff();
    }
}

void main(){ //启动两个线程,同时操作该方法内的变量
    Demon demon = new Demon();
    DemonThread demonThread = new DemonThread(demon);
    DemonThread2 demonThread2 = new DemonThread2(demon);
    demonThread.start();
    Thread.sleep(1000);
    demonThread2.start();
}

输出结果:

分析: 如果两个线程操作的是同一个变量,那么输出结果应该是1、2,但是结果却是1、1。说明两个线程的执行互不影响,也就是操作的变量是自己的那一个拷贝。

特别需要注意的是:上面说的局部变量是指基本数据类型的变量,比如int,short,long等等,如果是引用类型变量的话,就另说了,因为引用相当于指针,指针和指针的副本其实指向了同一个数据。

2、实例化对象的变量是非线程安全的

因为类被实例化的时候,通过new创建的对象,是存在堆中的,堆是线程共享的,所以多线程同时访问该对象的成员变量时,可能出现“赃读”。

3、同步锁方法

synchronized关键字声明的方法就是同步方法,   声明同步方法:synchronized public void name(){}  给方法添加关键字就行了。

作用:任何时刻,都最多只能有一个线程在调用此方法。若有多个线程请求调用此方法,那就只能排队了,一个一个来。要想执行同步方法,那么就要先获取这个同步方法的锁,执行完方法后,会释放这个锁。

3.1 锁重入

        在同一个类中,有多个同步方法,一个同步方法是可以调用另外的同步方法的,只要在同一个类中。

        同理:一个子类继承了父类,子类中的同步方法也是可以调用父类的同步方法的。

        这两种情况都不会造成死锁。

        这就是此特点的原因:假设对象a有三个方法,同步方法aa,ab, 非同步方法ac, X线程访问a对象的aa方法,Y线程此时就无法立刻访问到a对象的aa方法,同时也无法访问ab方法,但是Y线程可以访问ac方法。  还有,X线程访问a对象的aa方法,但是aa方法会调用ab方法,这样也是可行的。

为什么出现这种现象呢?因为同步方法的锁是所属的对象,因为只要有线程在访问一个对象里的同步方法时,其它线程就无法访问其它同步方法了,只能访问其它的非同步方法,因为非同步方法没有锁这一说。

        那如何才能让两个同步方法aa 和 ab 异步呢,就是不用相互阻塞呢? 用两个同步块分别包含两个方法,而且设置两个同步块的锁对象不能是同一个对象,这样,就异步了。

3.2 锁的自动释放

        一个线程正常执行完同步方法就会自动释放锁。

        一个线程执行过程中发生异常,但是这个异常没有被捕获,也会自动释放所拥有的锁。

        这里的“锁”可理解为拥有该同步方法的实例化对象。

3.3 同步没有继承性

        同步无法被继承,也无法被重载。

4、同步语句块

一般情况下,同步方法要等待当前线程将这个方法执行完了,其它线程才会去执行,如果这个同步方法需要执行的时间很长,那不是要等很久,所以同步语句块就可以解决这个问题。

synchronized (this){  语句块代码 }。  这样就可以定义同步语句块了,修饰的是一段代码,而不是整个方法;{}中的代码就是同步语句块,这段代码只能同时被至多一个线程访问。this代表当前对象。

4.1 同步语句块之间的同步性

在将当前对象作为锁的情况下:同一个实例化对象中,有多个同步语句块,当一个线程A在访问其中一个同步块的时候,其它线程不仅无法访问这个同步块,而其也不能访问这个对象中的其它同步块,这些线程只能等,等到线程A访问同步块结束。

4.2 同步方法和同步块的同步性

只要是普通的同步方法(普通的同步方法也是将当前对象作为锁),和将this(当前对象)作为锁的同步块,都具有同步性,在同一个对象内,1、一个线程A获取了同步方法的锁(当前对象),正在执行同步方法,那么其它线程请求该同步方法,或者请求该对象内的其它同步方法,都将等待; 2、一个线程A获取了同步方法或者同步块的锁(当前对象),正在执行同步方法或块,那么其它线程请求该对象内的其它同步块或同步方法,都将等待;

说一千,道一万,你就死盯那个锁到底是指什么,是否是同一个对象(变量)。

4.3 将 非this对象 作为  锁

synchronized (this){  语句块代码 }是将this当前对象作为 锁 ;

还可以定义synchronized (非 this对象 x){  语句块代码 },这样就将x作为锁了,当同步方法或者同步块被多线程访问时,x对象同时只能最多被一个线程锁占有,x对象可以是基本数据类型对象(一定要是继承了Object的类的对象,比如: int就不行,Integer就行,因为int不是类,Integer才是类),自定义对象等等。

为什么这样做呢? 因为将this对象作为锁的时候,整个对象中的synchronized定义的方法或者块会同步,如果这些方法和块之间没有同步的必要,那岂不是意味着不必要阻塞等待,降低执行效率,synchronized (非 this对象 x){}定义非this对象为锁的话,就可以实现它们之间的异步了,提高执行效率。

举个例子验证(这个例子很有意思):

public class CCC { 
    Integer a=0;
    Integer b=3;
    public  void  ff() throws InterruptedException {
        synchronized (this){
            Thread.sleep(2000);
            System.out.println("this synchronized");
        }
    }
    public void ww(){
        synchronized (a){
            System.out.println("非 this synchronized");
        }
    }
}

public class CCCThread1 extends Thread { 调用this锁的块
    public CCC ccc;
    public CCCThread1(CCC c){ this.ccc=c; }
    @Override
    public void run(){
        try {
            ccc.ff();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class CCCThread2 extends Thread { 调用非this锁的块
    public CCC ccc;
    public CCCThread2(CCC c){ this.ccc=c; }
    @Override
    public void run(){ccc.ww(); }
}

、、、、main方法中的执行代码
CCC c = new CCC();
CCCThread1 cccThread1 = new CCCThread1(c);
CCCThread2 cccThread2 = new CCCThread2(c);
cccThread1.start();
cccThread2.start();

 执行结果 : 

结果分析: 两个块,一个的锁是this对象,另一个是Integer对象,Integer是继承了Object的,是一个类,但是int 就不行。两者执行是异步的,得到了证明。

ff()里的同步块的锁是this对象,意味着整个this对象的同步内容都只能被当前线程使用,其它线程需要等待,this对象的整个内容有哪些呢? 包括:Integer a引用,Integer b的引用(注意Integer是类,a和b只是引用而已),还有两个方法。this对象这个锁只能管住引用a和b,还有两个方法,而无法管住a和b指向的两个Integer对象内存空间。

ww()里的同步块的锁是Integer a对象。

这两个锁都不是同一个对象,当然就不会同步了。

为什么一定要是“类的实例化对象”才能作为synchronized()的锁呢? 

因为类的实例化对象是在堆中,有自己的内存空间的,我们只要守住它的内存空间,就行了,至于对象之间的调用,只不过是通过引用来访问操作,引用不过是一个地址而已,真正的数据是存在对象自己的内存空间里面的,只要保证任何时刻只能最多让一个线程去访问对象的内存空间就行了,就不会出现赃读了。

5、 静态同步方法  和   以类为锁的同步块

synchronized public static void name(){ } 这样的就是静态同步方法,也是静态方法,不过加了个锁而已,将包含这个静态方法的类作为锁,等价于同步块synchronized (类名.class){ } ,静态同步方法无法指定锁(即包含此方法的类),而静态同步块可以指定锁。

调用静态方法:类名.静态方法名   ;    对象名.静态方法名。

静态方法是共享的,不属于哪个类,更不属于哪个对象,只不过声明在某个类中,然后用该类名(或则该类的对象)调用静态方法而已。

public class CC{
    synchronized public static void ff(){ }
    synchronized public static void ww(){ }
    synchronized public void aa(){ }
    public void bb(){
        synchronized(CC.class){ }
    } 
}

如上图所示,线程A执行 ff() 时,只要其它线程想调用以此CC类作为锁的同步方法或者块,都要阻塞。 ww()、bb()中的同步块要阻塞,而aa()不会,因为aa()的锁是CC类的实例化对象。

6、通常情况下最好不要将String字符串作为锁

原因:这就涉及JVM的运行时常量池知识了。

String a = "hello";
String b = "hello";

如上图中,假如有两个线程A 和 B,A将a作为锁,B将b作为锁,显然a 和 b指向的内存地址是同一个(即字符串常量池中的同一个“hello”),一旦A(B)先拥有了锁a,那么只要不释放,线程B(A)就得永远等着。

String a = new String("hello");
String b = new String("hello");

如上图所示,假如线程A和B的锁分别是a和b,那么就不会出现等待的情况了,因为new 创建的对象在堆中,两个“hello”字符串对象不是同一个,他们是有独立的内存空间的,内存地址是不同的,所以a和b不是同一个东西,当然不会出现等待的情况了。

7、锁的改变

当一个线程A将某个对象b作为锁的时候,b就是该对象的引用而已,只要b指向的还是那个对象,线程A的锁就没有变,只要b指向的内存(对象)改变了,那么这个锁也就变了。

8、死锁

经常看资料介绍“产生死锁的4个必要条件”,为什么是必要条件呢?

因为当死锁发生时,这4个条件必定发生,利用数学上的(原问题为真,其逆否问题也为真)知识可知:如果这4个条件不全发生,那么死锁就不会发生。 因此,我们只需要保证我们的程序不会让这4个条件全发生了就OK。

4个必要条件:1、资源是互斥的。2、非抢占式地竞争资源。3、线程要请求至少2个互斥资源,而且已经拥有了一部分资源,还要去请求竞争另外的互斥资源。4、线程之间形成了循环等等(循环圈里至少有2个线程)。

死锁:通俗一点地讲就是,我手里资源,你手里也有资源,我想要你的,你也想要我的,但是我们俩都不会放手,所以就这么死死地僵持下去吧,直到永远。

死锁的解决方法有两种思路:预防死锁(我不让它发生不就行了吗),处理死锁(当死锁发生时,我再去解决处于死锁状态的线程不就行了吗)。

8.1 预防死锁

预防就是去至少破坏4个必要条件之一。

1、所有资源都不是互斥的。(显然这个不实际)

2、抢占式地竞争资源,多个线程在竞争资源时,就拼优先级,我优先级高,我就抢你已经占有的资源。

3、要么获取到所需的全部资源,要么就一个资源也不要。

4、将系统的互斥资源按顺序编号,线程要获取资源时,必须按照这个顺序去获取。

上面都是预防思路,具体用什么技术手段和算法去实现,就看具体情况吧。

9、Lock锁

下面是Lock接口,给出了几个基本操作方法,所有的Lock锁系列都实现自此接口。

public interface Lock {
    // 获取锁
    void lock();
    // 如果当前线程没有被中断,则获取锁
    void lockInterruptibly() throws InterruptedException;
    // 尝试获取锁
    boolean tryLock();
    // 在规定的时间内尝试获取锁
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    // 释放锁
    void unlock();
    // 返回一个新的condition对象
    Condition newCondition();
}

9.1 ReentrantLock可重入锁

ReentrantLock 是 Lock 接口的实现类,常用的一般的可重入锁。举个例子怎么用,lock锁只能针对于代码块,在代码块的前后分别指向 lock()方法 和 unlock() 方法,用于获取锁和释放锁, lock() 和 unlock 方法必须配合使用,锁的释放必须调用unlock方法去手动释放,而且最好放在finally 里面,否则一旦出现异常,锁不能得到释放的话,那麻烦了。 可重入的意思是同一个锁可以获取多次,不过在释放的时候,要对应地释放多次。

以前用synchronized的时候,锁可以是当前对象,类,其它对象,但是ReentrantLock是将显示的创建一个锁对象,然后以此对象为锁。

public class Main{
    public void handler(){
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        lock.lock();
        try{
            执行代码
        }catch(Exception e){
            异常的处理
        }finally{
            lock.unlock();
            lock.unlock();
        }
    }
}

下面看看ReentrantLock的源码 ,内部提供了很多关于锁的操作方法,简单介绍一下吧。

public ReentrantLock():// 构造方法,默认是非公平锁

public ReentrantLock(boolean fair) :// 构造方法,指定是公平锁还是非公平锁

public void lock():// 获得锁,如果当前线程正在获取锁,然后被中断了,它是不会响应中断的,一直要到获取到锁为止

public void lockInterruptibly():// 获得锁,如果已有其他线程占有了锁,则当前线程阻塞,阻塞期间允许中断,一旦中断,就不会去获取锁了

public boolean tryLock():// 尝试获取锁,获取不到就立即返回

public boolean tryLock(long timeout, TimeUnit unit):// 尝试在指定时间内获取锁,获取不到就返回

public void unlock() :// 释放锁

public Condition newCondition() :// 返回此锁的一个新的condition对象

public int getHoldCount() :// 当前线程拥有此锁的次数

public boolean isHeldByCurrentThread():// 当前线程是否拥有此锁

public boolean isLocked():// 此锁是否可以被任何线程拥有

public final boolean isFair() :// 是否是公平锁

protected Thread getOwner() :// 此锁的当前拥有者(线程)

public final boolean hasQueuedThreads():// 是否有线程在等待获取此锁

public final boolean hasQueuedThread(Thread thread) :// 是否给定的这个线程在等待获取此锁

public final int getQueueLength() :// 等待获取锁的线程数(预估的)

protected Collection<Thread> getQueuedThreads():// 返回正在等待获取锁的所有线程

public boolean hasWaiters(Condition condition) :// 是否有线程在等待此锁的condition条件

public int getWaitQueueLength(Condition condition):// 返回等待此锁的condition条件的线程数(预估的)

几乎所有的方法里面,都是由sync对象执行的,所以真正的同步功能是由sync提供的,AbstractQueuedSynchronizer类才是真正提供同步功能的类,而Sync 继承了AbstractQueuedSynchronizer 类,并且重写了一些方法,因此具备了同步功能。

Sync有两个子类,一个是公平锁类,一个是非公平锁类(默认的)。

讲一讲大概原理(非公平锁):有一个volatile  int类型的state变量,线程在获取锁时,会查看这个state变量是否为0,如果是0,表示锁没有被任何线程占有,那就可以占有了,同时 state变量加1,如果线程调用 lock() 多次,那 state = 次数;如果不为0,表示锁被其它线程占有,那线程的表现有3种:

1、如果是tryLock() 尝试获取锁的话,立即返回,不会阻塞等待锁的。

2、如果是 lock() 的话,线程会被阻塞,放到一个阻塞队列中,等待获取锁。

获取到锁之后,如果要释放锁,就会必须调用 unlock() 方法,实际就是将 state 变量减1。

在公平锁模式下,一个线程去获取锁,不会去看state变量的,而是直接放进阻塞队列,如果队列中就它一个,那就去获取锁,如果还有别的线程,那就只好排队了。

总结一下:但凡是进了阻塞队列,就按照FIFO来,先进先得锁,在公平锁模式下,必须先进一趟阻塞队列,才能获取锁,因此它是完全按照先后顺序来的;在非公平模式下,先去获取锁,state为0的话,就直接得到了锁,如果不是话,才进入阻塞队列,因此,新来的线程可能会抢占到锁,因为阻塞队列中的线程需要被唤醒,上一个线程释放锁后,会唤醒下一个线程去获取锁,在这期间就容易被新来的线程抢占了,一旦被抢占了,被唤醒的锁就要继续在队列中阻塞呆着了。

非公平模式的性能高,但不保证公平性。

public class ReentrantLock implements Lock, java.io.Serializable {
    private static final long serialVersionUID = 7373984872572414699L;
    // 同步功能的真正实现者
    private final Sync sync;
    // 定义了Sync类,给出了内部的几个方法,真正实现同步的类
    abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = -5179523762034025860L;
        // 这是抽象方法,因为具体的lock执行是由公平锁对象或者非公平锁对象实现的
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }
        protected final boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
        // 返回此锁的condition对象
        final ConditionObject newCondition() {
            return new ConditionObject();
        }
        // 当前拥有锁的线程
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }
        // 线程拥有此锁的次数
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }
        // 是否有线程拥有此锁
        final boolean isLocked() {        return getState() != 0;        }
        private void readObject(java.io.ObjectInputStream s)
            throws java.io.IOException, ClassNotFoundException {
            s.defaultReadObject();
            setState(0); // reset to unlocked state
        }
    }

    // 非公平锁
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

    // 公平锁
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        final void lock() {    acquire(1);     }
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
    // 默认是非公平锁
    public ReentrantLock() {   sync = new NonfairSync();  }
    // 构造方法,指定是公平锁还是非公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
    // 获得锁,不会响应中断
    public void lock() {  sync.lock(); }
    // 获得锁,如果已有其他线程占有了锁,则当前线程阻塞,阻塞期间允许中断,一旦中断,就不会去获取锁了
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    // 尝试获取锁,获取不到就立即返回
    public boolean tryLock() {  return sync.nonfairTryAcquire(1);  }
    // 尝试在指定时间内获取锁,获取不到就返回
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
    // 释放锁
    public void unlock() {  sync.release(1);   }
    // 返回此锁的一个新的condition对象
    public Condition newCondition() {  return sync.newCondition();   }
    // 当前线程拥有此锁的次数
    public int getHoldCount() {  return sync.getHoldCount(); }
    // 当前线程是否拥有此锁
    public boolean isHeldByCurrentThread() {  return sync.isHeldExclusively(); }
    // 此锁是否可以被任何线程拥有
    public boolean isLocked() {  return sync.isLocked();   }
    // 是否是公平锁
    public final boolean isFair() {    return sync instanceof FairSync;    }
    // 此锁的当前拥有者(线程)
    protected Thread getOwner() {   return sync.getOwner();   }
    // 是否有线程在等待获取此锁
    public final boolean hasQueuedThreads() {  return sync.hasQueuedThreads(); }
    // 是否给定的这个线程在等待获取此锁
    public final boolean hasQueuedThread(Thread thread) {
        return sync.isQueued(thread);
    }
    // 等待获取锁的线程数(预估的)
    public final int getQueueLength() {   return sync.getQueueLength();   }
    // 返回正在等待获取锁的所有线程
    protected Collection<Thread> getQueuedThreads(){return sync.getQueuedThreads();}
    // 是否有线程在等待此锁的condition条件
    public boolean hasWaiters(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
    }
    // 返回等待此锁的condition条件的线程数(预估的)
    public int getWaitQueueLength(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
    }
    // 返回等待此锁的condition条件的线程
    protected Collection<Thread> getWaitingThreads(Condition condition) {
        if (condition == null)
            throw new NullPointerException();
        if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
            throw new IllegalArgumentException("not owner");
        return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
    }
    // 放回当前锁的状态,是未被占有,还是被占有了,哪个线程占有
    public String toString() {
        Thread o = sync.getOwner();
        return super.toString() + ((o == null) ?
                                   "[Unlocked]" :
                                   "[Locked by thread " + o.getName() + "]");
    }
}

9.1.1、Condition —— 等待/通知

功能上和synchronized 的 wait() 、notify()、notifyAll() 一样的,Condition 的优势在于可以指定唤醒哪个线程。

ReentrantLock  lock = new ReentrantLock();

Condition condition = lock.newCondiction();

当前线程调用 condition.await();  释放锁,进行等待,       其它线程调用该condition.signal() 就可以唤醒该线程了。

newCondition() 方法每次都放回一个新的condition对象,所以 await() 和 signal() 方法要看准是否是同一个condition对象,

如果多个线程调用了同一个condition对象的 await()方法,另一个线程调用此condition对象的 signal() 方法的话,会唤醒多个线程。

public interface Condition {
    // 让当前线程等待
    void await() throws InterruptedException;
    // 让当前线程等待,等待过程中,不可被中断
    void awaitUninterruptibly();
    // 让当前线程去等待,直到会唤醒、中断或者规定的时间(纳秒)用完了
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    // 让当前线程去等待,直到会唤醒、中断或者规定的时间用完了
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    // 让当前线程去等待,直到会唤醒、中断或者截止时间到了
    boolean awaitUntil(Date deadline) throws InterruptedException;
    // 随机选择一个线程唤醒
    void signal();
    // 唤醒所有等待此condition的线程,然后它们去竞争这个condition的锁
    void signalAll();
}

10、ReadWriteLock 读写锁

synchronized 和 ReentrantLock 都是锁,只不过没有读写之分,不管是读,还是写,都是互斥的,而读是不用加锁的,因此造成性能开销,于是有了 读写锁,将读和写分开,读锁,写锁。

1、一个线程获取了读锁,其它线程仍然可以获取该读锁,不阻塞。读读锁不阻塞。

2、写锁仍然是互斥的,读锁和写锁也是互斥的。读写锁,写读锁,写写锁阻塞。

public interface ReadWriteLock {
    // 返回读锁
    Lock readLock();
    // 返回写锁
    Lock writeLock();
}

10.1、ReentrantReadWriteLock 可重入读写锁

源码就不贴了,凡是ReentrantLock有的方法,ReentrantReadWriteLock类基本都有,大概介绍一下原理,下面是ReentrantReadWriteLock 类的3个成员,看到 Sync 就知道同步功能,阻塞队列这些原理了, ReadLock 和 WriteLock 是内部类,分别代表读锁 和 写锁,一个 ReentrantReadWriteLock 对象内部同时包含读锁 和 写锁。

对于readerLock 和 writerLock,它们的使用基本是 ReentrantLock 的使用一样。

ReentrantReadWriteLock 仍然分公平模式和非公平模式。

当然,ReentrantReadWriteLock 有一些自己的特色的方法,此处不作介绍了,以后补上。

    private final ReentrantReadWriteLock.ReadLock readerLock;
    private final ReentrantReadWriteLock.WriteLock writerLock;
    final Sync sync;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值