Java锁

本文详细介绍了Java中的多种锁机制。包括公平锁与非公平锁的区别、特点及吞吐量差异;可重入锁避免死锁的作用;自旋锁的原理及手写实现;独占锁、共享锁的特性及读写锁案例。还对比了synchronized和Lock在原始构成、使用方法等方面的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:

http://www.cnblogs.com/xingzc/p/5750152.html

http://www.cnblogs.com/tison/p/8283233.html

1.公平锁与非公平锁

[1].公平锁

是指多个线程按照申请锁的顺序来获取锁,类似排队

[2].非公平锁

是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获得锁,在高并发情况下,有可能会造成优先级反转或者饥饿现象

[3].两者的区别并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认即非公平锁

/**

* Creates an instance of {@code ReentrantLock}.

* This is equivalent to using {@code ReentrantLock(false)}.

*/

public ReentrantLock() {

    sync = new NonfairSync();

}


/**

* Creates an instance of {@code ReentrantLock} with the

* given fairness policy.

*

* @param fair {@code true} if this lock should use a fair ordering policy

*/

public ReentrantLock(boolean fair) {

    sync = fair ? new FairSync() : new NonfairSync();

}

①.公平锁:   

Threads acquire a fair lock in the order in which they requested it

公平锁,就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁, 否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己

java.util.concurrent.locks.ReentrantLock$FairSync.java


protected final boolean tryAcquire( int acquires) {

     final Thread current = Thread.currentThread();

     int c = getState();

     //状态为0,说明当前没有线程占有锁

     if (c ==  0 ) {

        //如果当前线程是等待队列的第一个或者等待队列为空,则通过cas指令设置state为1,当前线程获得锁

         if (isFirst(current) &&

             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 ;

}

②.非公平锁:

a nonfair lock permits barging: threads requesting a lock can jump ahead of the queue of waiting threads if the lock happens to be available when it is requested.

非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式

java.util.concurrent.locks.ReentrantLock$Sync.java

final boolean nonfairTryAcquire(int acquires) {

            final Thread current = Thread.currentThread();

            int c = getState();

            if (c == 0) {

                //如果当前没有线程占有锁,当前线程直接通过cas指令占有锁,管他等待队列,就算自己排在队尾也是这样

                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;

        }

[4].非公平锁比公平锁吞吐量大

[5].synchronized也是一种非公平锁

2.可重入锁

[1].重入锁(ReentrantLock)是一种递归无阻塞的同步机制。重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,在同一个线程外层方法获取锁的时候,在进入内层方法会自动获得锁,也就是说,线程可以进入它任何一个它已经拥有的锁锁同步着的代码块。

[2].ReentrantLock 和synchronized 都是 可重入锁。

package com.w4xj.interview.thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


/**

* @Author by w4xj

* @Classname ReentrantLockTest

* @Description TODO

* @Date 2019/5/6 20:53

* @Created by IDEA

*/

public class ReentrantLockTest {

    public static void main(String[] args) {
        CXK cxk = new CXK();
        new Thread(cxk, "Thread1").start();
        new Thread(cxk, "Thread2").start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("-----------------------------------------");


        new Thread(() ->{ cxk.sing(); },"Thread3").start();
        new Thread(() ->{ cxk.sing(); },"Thread3").start();
        /*

        打印:

            Thread1 music~
            Thread1 basketball~
            Thread2 music~
            Thread2 basketball~
            -----------------------------------------
            Thread3 sing~
            Thread3 jump~
            Thread3 rap~
            Thread3 sing~
            Thread3 jump~
            Thread3 rap~

         */
    }

}



class CXK implements Runnable{

    Lock lock = new ReentrantLock();


    @Override
    public void run() {
        music();
    }


    public void music(){
        //注意这里双锁依然是可以,但是锁一定要配对,不然会真正的死锁
        lock.lock();
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " music~");
            basketball();
        }finally {
            lock.unlock();
            lock.unlock();
        }
    }

    public void basketball(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " basketball~");
        }finally {
            lock.unlock();
        }
    }


    public synchronized void sing(){
        System.out.println(Thread.currentThread().getName() + " sing~");
        jump();
    }


    public synchronized void jump(){
        System.out.println(Thread.currentThread().getName() + " jump~");
        rap();
    }

    public synchronized void rap(){
        System.out.println(Thread.currentThread().getName() + " rap~");

    }
}

[3].可重入锁最大的作用就是避免死锁

3.自旋锁

[1].自旋锁,由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。如何旋转呢?何为自旋锁,就是如果发现锁定了,不是睡眠等待,而是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。这样的好处是减少上下文切换的消耗,缺点是会消耗CPU。Unsafe类的getAndAddxxx就是典型的自旋锁

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}

[2].手写自旋锁

package com.w4xj.interview.thread;



import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicReference;



/**

* @Author by w4xj

* @Classname SpinLock

* @Description TODO

* @Date 2019/5/7 8:07

* @Created by IDEA

*/

public class SpinLock {



    AtomicReference<Thread> atomicReference = new AtomicReference<>();



    /**

     * 获得锁

     */

    public void lock(){

        Thread thread = Thread.currentThread();

        System.out.println(thread.getName() + " try to get the lock");

        //自旋

        while (!atomicReference.compareAndSet(null, thread)){

            //获取锁失败

        }

        //已经获得锁

        System.out.println(thread.getName() + " got the lock");

    }



    /**

     * 解锁

     */

    public void unlock() {

        Thread thread = Thread.currentThread();

        atomicReference.compareAndSet(thread,null);

        System.out.println(thread.getName() + " release the lock");

    }



    public static void main(String[] args) {

        SpinLock spinLock = new SpinLock();

        new Thread(() ->{

            spinLock.lock();

            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }

            spinLock.unlock();

        },"ThreadA").start();





        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }



        new Thread(() ->{

            spinLock.lock();

            spinLock.unlock();

        },"ThreadB").start();

        

        /*

        打印:

            ThreadA try to get the lock

            ThreadA got the lock

            ThreadB try to get the lock

            ThreadA release the lock

            ThreadB got the lock

            ThreadB release the lock

         */

    }

}

4.独占锁(写锁)、共享锁(读锁)与互斥锁

[1].独占锁:指该锁一次只能被一个线程所持有。对ReentrantLock和Synchronized而言都是独占锁

[2].共享锁:指该锁可以被多个线程所持有。对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁。

[3].读锁的共享锁可以保证并发是非常高效的,读写、写读、写写的过程都是互斥的

[4].ReentrantReadWriteLock代码案例,加锁和不加锁的对比

package com.w4xj.interview.thread;



import java.util.HashMap;

import java.util.Map;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.locks.ReentrantReadWriteLock;



/**

* @Author by w4xj

* @Classname ReentrantReadWriteLockTest

* @Description TODO

* @Date 2019/5/7 8:41

* @Created by IDEA

*/

public class ReentrantReadWriteLockTest {

    public static void main(String[] args) {

        Cache cache1 = new Cache();

        Cache cache2 = new Cache();



        //cache1会产生写争抢,因为没有加锁

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

            final String key = String.valueOf(i);

            final Integer value = i;

            new Thread(() ->{

                cache1.put1(key, value);

            },"threadName" + i).start();

        }



        while (Thread.activeCount() > 2){

            Thread.yield();

        }



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

            final String key = String.valueOf(i);

            new Thread(() ->{

                cache1.get1(key);

            },"threadName" + i).start();

        }



        try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("--------------------------------------");



        //cache2不会会产生写争抢,因为put方法加了write锁

        for (int i = 10 ; i < 14 ; i++){

            final String key = String.valueOf(i);

            final Integer value = i;

            new Thread(() ->{

                cache2.put2(key, value);

            },"threadName" + i).start();

        }



        while (Thread.activeCount() > 2){

            Thread.yield();

        }



        for (int i = 10 ; i < 14 ; i++){

            final String key = String.valueOf(i);

            new Thread(() ->{

                cache2.get2(key);

            },"threadName" + i).start();

        }



        /*

        打印:

            threadName0     正在写入(0,0)

            threadName1     正在写入(1,1)

            threadName2     正在写入(2,2)

            threadName3     正在写入(3,3)

            threadName3     写入(3,3)完成

            threadName1     写入(1,1)完成

            threadName2     写入(2,2)完成

            threadName0     写入(0,0)完成

            threadName0     正在读取0

            threadName1     正在读取1

            threadName2     正在读取2

            threadName3     正在读取3

            threadName1     读取(1,1)完成

            threadName0     读取(0,0)完成

            threadName2     读取(2,2)完成

            threadName3     读取(3,3)完成

            --------------------------------------

            threadName11    正在写入(11,11)

            threadName11    写入(11,11)完成

            threadName10    正在写入(10,10)

            threadName10    写入(10,10)完成

            threadName13    正在写入(13,13)

            threadName13    写入(13,13)完成

            threadName12    正在写入(12,12)

            threadName12    写入(12,12)完成

            threadName10    正在读取10

            threadName11    正在读取11

            threadName12    正在读取12

            threadName13    正在读取13

            threadName10    读取(10,10)完成

            threadName13    读取(13,13)完成

            threadName11    读取(11,11)完成

            threadName12    读取(12,12)完成

         */



    }

}



class Cache {

    private volatile Map<String, Object> map = new HashMap<>();



    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();



    public void put1(String key, Object value) {

        System.out.println(Thread.currentThread().getName() + "\t 正在写入(" + key + "," + value + ")");

        //睡0.1秒

        try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }

        map.put(key, value);

        System.out.println(Thread.currentThread().getName() + "\t 写入(" + key + "," + value + ")完成");

    }



    public void get1(String key){

        System.out.println(Thread.currentThread().getName() + "\t 正在读取" + key + "");

        //睡0.1秒

        try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }

        Object value = map.get(key);

        System.out.println(Thread.currentThread().getName() + "\t 读取(" + key + "," + value + ")完成");

    }



    public void put2(String key, Object value) {

        readWriteLock.writeLock().lock();

        try {

            System.out.println(Thread.currentThread().getName() + "\t 正在写入(" + key + "," + value + ")");

            //睡0.1秒

            try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }

            map.put(key, value);

            System.out.println(Thread.currentThread().getName() + "\t 写入(" + key + "," + value + ")完成");

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            readWriteLock.writeLock().unlock();

        }

    }



    public void get2(String key){

        readWriteLock.readLock().lock();

        try {

            System.out.println(Thread.currentThread().getName() + "\t 正在读取" + key + "");

            //睡0.1秒

            try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }

            Object value = map.get(key);

            System.out.println(Thread.currentThread().getName() + "\t 读取(" + key + "," + value + ")完成");

        }catch (Exception e){

            e.printStackTrace();

        }finally {

            readWriteLock.readLock().unlock();

        }

    }

}

5.补充:synchronized和Lock的区别

[1].原始构成

①.synchronized是关键字属于JVM层面,monitorenter/monitorexit(底层是通过monitor对象来完成,其实wait/notify等方法也依赖monitor对象只有在同步代码块和同步方法中才能调用wait/notify等方法)

②.Lock是具体的类,是api层面的锁;

[2].使用方法

synchronized不需要用户手动释放锁,synchronized代码执行完成以后系统会自动让线程释放对锁的占有

ReentrantLock则需要用户手动去释放锁,若没有主动释放锁,就有可能导致死锁现象。需要使用lock()和unlock()方法配合try finally语句块来完成。

[3].等待是否可以中断

①.synchronized不可中断,除非抛出异常或者正常运行完成。

②.ReetrantLock可中断,

a.设置超时方法tryLock(long timeout, TimeUnit unit);

b.lockInterruptibly()放入代码块中,调用interrupt()方法可中断;

[4].加锁是否公平

①.synchronized是非公平锁

②.ReentrantLock默认是非公平锁,可设置为公平锁。

[5].锁绑定多个条件condition

①.synchronized没有;

②.ReentrantLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个,要么唤醒全部线程。

package com.w4xj.interview.thread;





import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;





/**

* @Author by w4xj

* @Classname MultipleCondition

* @Description 3个线程交替输出ABC

* @Date 2019/5/9 8:47

* @Created by IDEA

*/

public class MultipleCondition {

    public static void main(String[] args) {

        ShareResouce shareResouce = new ShareResouce();

        new Thread(() ->{

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

                shareResouce.printA();

            }

        },"ThreadA").start();

        new Thread(() ->{

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

                shareResouce.printB();

            }

        },"ThreadB").start();

        new Thread(() ->{

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

                shareResouce.printC();

            }

        },"ThreadC").start();

        /*

        打印:

            ThreadA A

            ThreadB B

            ThreadC C

            ...

            ThreadA A

            ThreadB B

            ThreadC C

         */

    }





}





//资源类

class ShareResouce{

    /**

     * 标志位

     */

    private int current = 1;

    /**

     * 锁

     */

    private Lock lock = new ReentrantLock();

    /**

     * 多条件

     */

    private Condition condition1 = lock.newCondition();

    private Condition condition2 = lock.newCondition();

    private Condition condition3 = lock.newCondition();





    public void printA(){

        lock.lock();

        try {

            while (current != 1){

                condition1.await();

            }

            System.out.println(Thread.currentThread().getName() + " A");

            //修改标志位

            current = 2;

            //唤醒

            condition2.signal();

        }catch (Exception e){

            e.printStackTrace();

        }finally {

            lock.unlock();

        }

    }





    public void printB(){

        lock.lock();

        try {

            while (current != 2){

                condition2.await();

            }

            System.out.println(Thread.currentThread().getName() + " B");

            //修改标志位

            current = 3;

            //唤醒

            condition3.signal();

        }catch (Exception e){

            e.printStackTrace();

        }finally {

            lock.unlock();

        }

    }



    public void printC(){

        lock.lock();

        try {

            while (current != 3){

                condition3.await();

            }

            System.out.println(Thread.currentThread().getName() + " C");

            //修改标志位

            current = 1;

            //唤醒

            condition1.signal();

        }catch (Exception e){

            e.printStackTrace();

        }finally {

            lock.unlock();

        }

    }

}

如果考虑到上述特性,就用ReentrantLock

[6].性能:两者的性能已经相差无几

在JDK1.6之前,synchronized 的性能是比 ReenTrantLock 差很多。具体表示为:synchronized 关键字吞吐量岁线程数的增加,下降得非常严重。而ReenTrantLock 基本保持一个比较稳定的水平。我觉得这也侧面反映了, synchronized 关键字还有非常大的优化余地。后续的技术发展也证明了这一点,我们上面也讲了在 JDK1.6 之后 JVM 团队对 synchronized 关键字做了很多优化。JDK1.6 之后,synchronized 和 ReenTrantLock 的性能基本是持平了。所以网上那些说因为性能才选择 ReenTrantLock 的文章都是错的!JDK1.6之后,性能已经不是选择synchronized和ReenTrantLock的影响因素了!而且虚拟机在未来的性能改进中会更偏向于原生的synchronized,所以还是提倡在synchronized能满足你的需求的情况下,优先考虑使用synchronized关键字来进行同步!优化后的synchronized和ReenTrantLock一样,在很多地方都是用到了CAS操作。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值