精华内容
下载资源
问答
  • java公平锁、非公平锁、可重入锁(递归锁)、自旋锁、独占锁(写锁)、共享锁(读锁)…… 公平锁 和 非公平锁 是什么: 公平锁:是指多个线程按照申请的顺序来获取值 非公平锁:是值多个线程获取值的顺序...

    大厂常见面试题:

    java 中锁你知道哪些?

    请手写一个自旋锁?


    首先,回答第一个问题:java 中锁你知道哪些?

    java中有公平锁、非公平锁、可重入锁(递归锁)、自旋锁、独占锁(写锁)、共享锁(读锁)……

     

    公平锁 和 非公平锁

    • 是什么:

    公平锁:是指多个线程按照申请的顺序来获取值

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

    • 两者区别:

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

    非公平锁:一上来就尝试占有锁,如果失败在进行排队

     

    可重入锁

    • 是什么

    可重入锁(递归锁):指的是同一个线程外层函数获得锁之后,内层仍然能获取到该锁的代码,在同一个线程在外层方法获                     取锁的时候,在进入内层方法或会自动获取该锁。也就是说,线程可以进入任何一个他已经拥有的锁的同步代码块。

                ReentrantLook / Synchronized就是一个典型的可重入锁,作用:防止死锁

    可重入锁代码:

    package com.lock.test;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 可重入锁(也叫做递归锁)
     *     在同一个线程在外层方法获取锁的时候,在进入内层方法或会自动获取该锁。
     *     也就是说,线程可以进入任何一个他已经拥有的锁的同步代码块。
     *
     * case one Synchronized就是一个典型的可重入锁
     *      ----------case one----------
     *      case one thread1	获取值。。      //thread1线程在外层方法获得锁的时候
     *      ---case one thread1	存放值。。      //thread1在进入内层方法会自动获取锁
     *      case one thread2	获取值。。
     *      ---case one thread2	存放值。。
     *
     * case two ReentrantLock也是一个可重入锁
     *      ----------case two----------
     *      case two thread1	获取值。。     //thread1线程在外层方法获得锁的时候
     *      ###case two thread1	存放值。。     //thread1在进入内层方法会自动获取锁
     *      case two thread2	获取值。。
     *      ###case two thread2	存放值。。
     */
    
    class Case {
        //case one Synchronized
        public synchronized void getNum() {
            System.out.println(Thread.currentThread().getName() + "\t获取值。。");
            setNum();
        }
        public synchronized void setNum() {
            System.out.println("---" + Thread.currentThread().getName() + "\t存放值。。");
        }
    
        //case two ReentrantLock
        private Lock lock = new ReentrantLock();
        public void get() {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "\t获取值。。");
            set();
            lock.unlock();
        }
    
        public void set() {
            lock.lock();
            System.out.println("###" + Thread.currentThread().getName() + "\t存放值。。");
            lock.unlock();
        }
    }
    
    public class ReEnterLockDemo {
        public static void main(String[] args) {
            Case a = new Case();
    
            //case one Synchronized
            System.out.println("----------case one----------");
            new Thread(()->{
                a.getNum();
            },"case one thread1" ).start();
    
            new Thread(()->{
                a.getNum();
            },"case one thread2" ).start();
    
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            //case two ReentrantLock
            System.out.println("\n\n----------case two----------");
            new Thread(()->{
                a.get();
            },"case two thread1" ).start();
    
            new Thread(()->{
                a.get();
            },"case two thread2" ).start();
        }
    }
    

     

     

     

    自旋锁

    • 是什么

    自旋锁:是指定尝试获取锁的线程不会立即堵塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上线文切换的                        消耗,缺点就是循环会消耗 CPU。


          自旋锁代码实现:

    package com.lock.test;
    
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicReference;
    
    /**
     * @description: 手写自旋锁
     * @author: Mr.Li
     * @create: 2019-09-26 16:31
     *
     * 优点:不会阻塞
     * 缺点:可能一直处于循环
     * 
     * 获取锁的时候,如果原子引用为空就获取锁,不为空表示有人获取了锁,就循环等待。     
     * 程序输出:
     *      AA	 come in ♀
     *      BB	 come in ♀
     *      AA	success unlock...
     *      BB	success unlock...
     **/
    public class SpinLock {
        //原子引用线程
        private AtomicReference<Thread> atomicReference = new AtomicReference<>();
        private void lock () {
            Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName() + "\t come in ♀");
            while (!atomicReference.compareAndSet(null, thread)) {
                // loop
            }
        }
    
        private void unlock() {
            Thread thread = Thread.currentThread();
            atomicReference.compareAndSet(thread, null);
            System.out.println(Thread.currentThread().getName() + "\tsuccess unlock...");
        }
    
        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();
            },"AA").start();
    
            //等待一秒
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            new Thread(() -> {
                spinLock.lock();
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                spinLock.unlock();
            },"BB").start();
        }
    }
    

     

    独占锁(写锁)/共享锁(读锁)

    • 是什么

    独占锁:指该锁一次只能被一个线程持有,ReentrantLook / Synchronized都是独占锁

    共享锁:该锁可以被多个线程持有

    对于 ReentrantLock 和 synchronized 都是独占锁;对与 ReentrantReadWriteLock 其读锁是共享锁写锁是独占锁。读锁的共享可保证并发读是非常高效的,读写、写读和写写的过程是互斥的


       独占锁与共享锁例子:

    package com.lock.test;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    /**
     * @program: acmPractice
     * @description: 读写锁案例
     * @author: Mr.Li
     * @create: 2019-09-26 16:46
     *
     *  多个线程同时读一个资源类没有问题,所以为了满足并发量,读取共享资源应该同时进行
     *  但是如果有一个线程想去写共享资源,就不应该再有其他线程可以对资源进行读或取
     *
     *  总结:读读能共存,读写不能共存,写写不能共存
     *      写操作:原子+独占,整个过程必须是一个完整的统一体,中间不许被分割、被打断
     *
     **/
    class MyCache {
        private volatile Map<String, Object> map = new HashMap<>();
        private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    
        public void put(String key, Object value) {
            rwLock.writeLock().lock();
            try {
                System.out.println(Thread.currentThread().getName() + " 正在写入..." + key);
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                map.put(key, value);
                System.out.println(Thread.currentThread().getName() + " 写入完成,写入结果是 " + value);
            } finally {
                rwLock.writeLock().unlock();
            }
        }
    
        public void get(String key) {
            try {
                rwLock.readLock().lock();
                System.out.println(Thread.currentThread().getName() + " 正在读...");
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Object res = map.get(key);
                System.out.println(Thread.currentThread().getName() + " 读取完成,读取结果是 " + res);
            } finally {
                rwLock.readLock().unlock();
            }
        }
    }
    
    public class ReadWriteLockDemo {
        public static void main(String[] args) {
            MyCache cache = new MyCache();
    
            for (int i = 1; i <= 5; i++) {
                final int temp = i;
                new Thread(() -> {
                    cache.put(temp + "", temp + "");
                },String.valueOf(i)).start();
            }
    
            for (int i = 1; i <= 5; i++) {
                final int temp = i;
                new Thread(() -> {
                    cache.get(temp + "");
                },String.valueOf(i)).start();
            }
        }
    }
    

     

    运行结果:

    2 正在写入...2
    2 写入完成,写入结果是 2
    4 正在写入...4
    4 写入完成,写入结果是 4
    1 正在写入...1
    1 写入完成,写入结果是 1
    3 正在写入...3
    3 写入完成,写入结果是 3
    5 正在写入...5
    5 写入完成,写入结果是 5
    1 正在读...
    2 正在读...
    4 正在读...
    3 正在读...
    5 正在读...
    1 读取完成,读取结果是 1
    3 读取完成,读取结果是 3
    2 读取完成,读取结果是 2
    5 读取完成,读取结果是 5
    4 读取完成,读取结果是 4
    
    Process finished with exit code 0
    

      ReentrantReadWriteLock :能保证读写、写读和写写的过程是互斥的时候是独享的,读读的时候是共享的。

     


    总结:

    不同的锁有不同特点,每种锁只有在其特定的场景下,才会有出色的表现,java中没有哪种锁能够在所有情况下都能有出色的效率,引入这么多锁的原因就是为了应对不同的情况;

     


    每日一言:

            学习编程技巧:理论——编码——总结

     

     

     

    展开全文
  • 公平锁/非公平锁可重入锁独享锁/共享锁互斥锁/读写锁乐观锁/悲观锁分段锁偏向锁/轻量级锁/重量级锁自旋锁这些分类并不是全是指锁的状态,的指锁的特性,的指锁的设计,下面总结的内容是对每个锁的名词进行一定的...

    公平锁/非公平锁

    可重入锁

    独享锁/共享锁

    互斥锁/读写锁

    乐观锁/悲观锁

    分段锁

    偏向锁/轻量级锁/重量级锁

    自旋锁

    这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释。

    公平锁/非公平锁

    公平锁是指多个线程按照申请锁的顺序来获取锁。

    非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

    对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

    对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

    可重入锁

    可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。

    对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁。

    对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。

    synchronized void setA() throws Exception{

    Thread.sleep(1000);

    setB();

    }synchronized void setB() throws Exception{

    Thread.sleep(1000);

    }

    上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。

    独享锁/共享锁

    独享锁是指该锁一次只能被一个线程所持有。

    共享锁是指该锁可32313133353236313431303231363533e58685e5aeb931333365653239被多个线程所持有。

    对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。

    读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。

    独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

    对于Synchronized而言,当然是独享锁。

    互斥锁/读写锁

    上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。

    互斥锁在Java中的具体实现就是ReentrantLock

    读写锁在Java中的具体实现就是ReadWriteLock

    乐观锁/悲观锁

    乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。

    悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。

    乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。

    从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。

    悲观锁在Java中的使用,就是利用各种锁。

    乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。

    分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

    我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

    当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。

    但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

    分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

    偏向锁/轻量级锁/重量级锁

    这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。

    偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

    轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

    重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

    自旋锁

    在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

    展开全文
  • 可重入锁 / 不可重入锁3.独享锁 / 共享锁4.互斥锁 / 读写锁5.乐观锁 / 悲观锁6.分段锁7.偏向锁 / 轻量级锁 / 重量级锁8.自旋锁一、公平锁 / 非公平锁公平锁公平锁是指多个线程按照申请锁的顺序来获取锁。非公平锁非...

    1.公平锁 / 非公平锁

    2.可重入锁 / 不可重入锁

    3.独享锁 / 共享锁

    4.互斥锁 / 读写锁

    5.乐观锁 / 悲观锁

    6.分段锁

    7.偏向锁 / 轻量级锁 / 重量级锁

    8.自旋锁

    一、公平锁 / 非公平锁

    公平锁

    公平锁是指多个线程按照申请锁的顺序来获取锁。

    非公平锁

    非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

    对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

    对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

    二、可重入锁 / 不可重入锁

    可重入锁

    广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁

    synchronized void setA() throws Exception{

    Thread.sleep(1000);

    setB();

    }

    synchronized void setB() throws Exception{

    Thread.sleep(1000);

    }

    上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。

    不可重入锁

    不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。看到一个经典的讲解,使用自旋锁来模拟一个不可重入锁,代码如下

    import java.util.concurrent.atomic.AtomicReference;

    public class UnreentrantLock {

    private AtomicReference owner = new AtomicReference();

    public void lock() {

    Thread current = Thread.currentThread();

    //这句是很经典的“自旋”语法,AtomicInteger中也有 for (;;) {

    if (!owner.compareAndSet(null, current)) {

    return;

    }

    }

    }

    public void unlock() {

    Thread current = Thread.currentThread();

    owner.compareAndSet(current, null);

    }

    }

    代码也比较简单,使用原子引用来存放线程,同一线程两次调用lock()方法,如果不执行unlock()释放锁的话,第二次调用自旋的时候就会产生死锁,这个锁就不是可重入的,而实际上同一个线程不必每次都去释放锁再来获取锁,这样的调度切换是很耗资源的。

    把它变成一个可重入锁:

    import java.util.concurrent.atomic.AtomicReference;

    public class UnreentrantLock {

    private AtomicReference owner = new AtomicReference();

    private int state = 0;

    public void lock() {

    Thread current = Thread.currentThread();

    if (current == owner.get()) {

    state++;

    return;

    }

    //这句是很经典的“自旋”式语法,AtomicInteger中也有 for (;;) {

    if (!owner.compareAndSet(null, current)) {

    return;

    }

    }

    }

    public void unlock() {

    Thread current = Thread.currentThread();

    if (current == owner.get()) {

    if (state != 0) {

    state--;

    } else {

    owner.compareAndSet(current, null);

    }

    }

    }

    }

    在执行每次操作之前,判断当前锁持有者是否是当前对象,采用state计数,不用每次去释放锁。

    ReentrantLock中可重入锁实现

    这里看非公平锁的锁获取方法:

    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;

    }

    在AQS中维护了一个private volatile int state来计数重入次数,避免了频繁的持有释放操作,这样既提升了效率,又避免了死锁。

    三、独享锁 / 共享锁

    独享锁和共享锁在你去读C.U.T包下的ReeReentrantLock和ReentrantReadWriteLock你就会发现,它俩一个是独享一个是共享锁。

    独享锁:该锁每一次只能被一个线程所持有。

    共享锁:该锁可被多个线程共有,典型的就是ReentrantReadWriteLock里的读锁,它的读锁是可以被共享的,但是它的写锁确每次只能被独占。

    另外读锁的共享可保证并发读是非常高效的,但是读写和写写,写读都是互斥的。

    独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享

    对于Synchronized而言,当然是独享锁。

    四、互斥锁 / 读写锁

    互斥锁

    在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。

    如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态, 第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。 在这种方式下,只有一个线程能够访问被互斥锁保护的资源

    读写锁

    读写锁既是互斥锁,又是共享锁,read模式是共享,write是互斥(排它锁)的。

    读写锁有三种状态:读加锁状态、写加锁状态和不加锁状态

    读写锁在Java中的具体实现就是ReadWriteLock

    一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

    只有一个线程可以占有写状态的锁,但可以有多个线程同时占有读状态锁,这也是它可以实现高并发的原因。当其处于写状态锁下,任何想要尝试获得锁的线程都会被阻塞,直到写状态锁被释放;如果是处于读状态锁下,允许其它线程获得它的读状态锁,但是不允许获得它的写状态锁,直到所有线程的读状态锁被释放;为了避免想要尝试写操作的线程一直得不到写状态锁,当读写锁感知到有线程想要获得写状态锁时,便会阻塞其后所有想要获得读状态锁的线程。所以读写锁非常适合资源的读操作远多于写操作的情况。

    五、乐观锁 / 悲观锁

    悲观锁

    总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

    乐观锁

    总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

    六、分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

    并发容器类的加锁机制是基于粒度更小的分段锁,分段锁也是提升多并发程序性能的重要手段之一。

    在并发程序中,串行操作是会降低可伸缩性,并且上下文切换也会减低性能。在锁上发生竞争时将通水导致这两种问题,使用独占锁时保护受限资源的时候,基本上是采用串行方式—-每次只能有一个线程能访问它。所以对于可伸缩性来说最大的威胁就是独占锁。

    我们一般有三种方式降低锁的竞争程度:

    1、减少锁的持有时间

    2、降低锁的请求频率

    3、使用带有协调机制的独占锁,这些机制允许更高的并发性。

    在某些情况下我们可以将锁分解技术进一步扩展为一组独立对象上的锁进行分解,这成为分段锁。

    其实说的简单一点就是:

    容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

    比如:在ConcurrentHashMap中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(N mod 16)个锁来保护。假设使用合理的散列算法使关键字能够均匀的分部,那么这大约能使对锁的请求减少到越来的1/16。也正是这项技术使得ConcurrentHashMap支持多达16个并发的写入线程。

    七、偏向锁 / 轻量级锁 / 重量级锁

    锁的状态:1.无锁状态

    2.偏向锁状态

    3.轻量级锁状态

    4.重量级锁状态

    锁的状态是通过对象监视器在对象头中的字段来表明的。

    四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。

    这四种状态都不是Java语言中的锁,而是Jvm为了提高锁的获取与释放效率而做的优化(使用synchronized时)。

    偏向锁

    偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

    轻量级

    轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

    重量级锁

    重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

    八、自旋锁

    我们知道CAS算法是乐观锁的一种实现方式,CAS算法中又涉及到自旋锁,所以这里给大家讲一下什么是自旋锁。

    简单回顾一下CAS算法

    CAS是英文单词Compare and Swap(比较并交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数1.需要读写的内存值 V

    2.进行比较的值 A

    3.拟写入的新值 B

    更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B,否则不会执行任何操作。一般情况下是一个自旋操作,即不断的重试。

    什么是自旋锁?

    自旋锁(spinlock):是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

    它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。

    Java如何实现自旋锁?

    下面是个简单的例子:

    public class SpinLock {

    private AtomicReference cas = new AtomicReference();

    public void lock() {

    Thread current = Thread.currentThread();

    // 利用CAS while (!cas.compareAndSet(null, current)) {

    // DO nothing }

    }

    public void unlock() {

    Thread current = Thread.currentThread();

    cas.compareAndSet(current, null);

    }

    }

    lock()方法利用的CAS,当第一个线程A获取锁的时候,能够成功获取到,不会进入while循环,如果此时线程A没有释放锁,另一个线程B又来获取锁,此时由于不满足CAS,所以就会进入while循环,不断判断是否满足CAS,直到A线程调用unlock方法释放了该锁。

    自旋锁存在的问题1、如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高。

    2、上面Java实现的自旋锁不是公平的,即无法满足等待时间最长的线程优先获取锁。不公平的锁就会存在“线程饥饿”问题。

    自旋锁的优点1、自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快

    2、非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。 (线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)

    可重入的自旋锁和不可重入的自旋锁

    文章开始的时候的那段代码,仔细分析一下就可以看出,它是不支持重入的,即当一个线程第一次已经获取到了该锁,在锁释放之前又一次重新获取该锁,第二次就不能成功获取到。由于不满足CAS,所以第二次获取会进入while循环等待,而如果是可重入锁,第二次也是应该能够成功获取到的。

    而且,即使第二次能够成功获取,那么当第一次释放锁的时候,第二次获取到的锁也会被释放,而这是不合理的。

    为了实现可重入锁,我们需要引入一个计数器,用来记录获取锁的线程数。

    public class ReentrantSpinLock {

    private AtomicReference cas = new AtomicReference();

    private int count;

    public void lock() {

    Thread current = Thread.currentThread();

    if (current == cas.get()) { // 如果当前线程已经获取到了锁,线程数增加一,然后返回 count++;

    return;

    }

    // 如果没获取到锁,则通过CAS自旋 while (!cas.compareAndSet(null, current)) {

    // DO nothing }

    }

    public void unlock() {

    Thread cur = Thread.currentThread();

    if (cur == cas.get()) {

    if (count > 0) {// 如果大于0,表示当前线程多次获取了该锁,释放锁通过count减一来模拟 count--;

    } else {// 如果count==0,可以将锁释放,这样就能保证获取锁的次数与释放锁的次数是一致的了。 cas.compareAndSet(cur, null);

    }

    }

    }

    }

    自旋锁与互斥锁1.自旋锁与互斥锁都是为了实现保护资源共享的机制。

    2.无论是自旋锁还是互斥锁,在任意时刻,都最多只能有一个保持者。

    3获取互斥锁的线程,如果锁已经被占用,则该线程将进入睡眠状态;获取自旋锁的线程则不会睡眠,而是一直循环等待锁释放。

    自旋锁总结1.自旋锁:线程获取锁的时候,如果锁被其他线程持有,则当前线程将循环等待,直到获取到锁。

    2.自旋锁等待期间,线程的状态不会改变,线程一直是用户态并且是活动的(active)。

    3.自旋锁如果持有锁的时间太长,则会导致其它等待获取锁的线程耗尽CPU。

    4.自旋锁本身无法保证公平性,同时也无法保证可重入性。

    5.基于自旋锁,可以实现具备公平性和可重入性质的锁。

    展开全文
  • 介绍的内容如下:公平锁/非公平锁可重入锁独享锁/共享锁互斥锁/读写锁乐观锁/悲观锁分段锁偏向锁/轻量级锁/重量级锁自旋锁上面是很多锁的名词,这些分类并不是全是指锁的状态,的指锁的特性,的指锁的设计,下面...

    java有哪些锁种类(转)

    在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类。介绍的内容如下:

    公平锁/非公平锁

    可重入锁

    独享锁/共享锁

    互斥锁/读写锁

    乐观锁/悲观锁

    分段锁

    偏向锁/轻量级锁/重量级锁

    自旋锁

    上面是很多锁的名词,这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计,下面总结的内容是对每个锁的名词进行一定的解释。

    公平锁/非公平锁

    公平锁是指多个线程按照申请锁的顺序来获取锁。

    非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

    对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

    对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

    可重入锁

    可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。说的有点抽象,下面会有一个代码的示例。

    对于Java ReentrantLock而言, 他的名字就可以看出是一个可重入锁,其名字是Re entrant Lock重新进入锁。

    对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。

    synchronized void setA() throws Exception{

    Thread.sleep(1000);

    setB();

    }

    synchronized void setB() throws Exception{

    Thread.sleep(1000);

    }

    上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。

    独享锁/共享锁

    独享锁是指该锁一次只能被一个线程所持有。

    共享锁是指该锁可被多个线程所持有。

    对于Java ReentrantLock而言,其是独享锁。但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。

    读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的。

    独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

    对于Synchronized而言,当然是独享锁。

    互斥锁/读写锁

    上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。

    互斥锁在Java中的具体实现就是ReentrantLock

    读写锁在Java中的具体实现就是ReadWriteLock

    乐观锁/悲观锁

    乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。

    悲观锁认为对于同一个数据的并发操作,一定是会发生修改的,哪怕没有修改,也会认为修改。因此对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出问题。

    乐观锁则认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。

    从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。

    悲观锁在Java中的使用,就是利用各种锁。

    乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。

    分段锁

    分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

    我们以ConcurrentHashMap来说一下分段锁的含义以及设计思想,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。

    当需要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来知道他要放在那一个分段中,然后对这个分段进行加锁,所以当多线程put的时候,只要不是放在一个分段中,就实现了真正的并行的插入。

    但是,在统计size的时候,可就是获取hashmap全局信息的时候,就需要获取所有的分段锁才能统计。

    分段锁的设计目的是细化锁的粒度,当操作不需要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。

    偏向锁/轻量级锁/重量级锁

    这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。

    偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

    轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

    重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

    自旋锁

    在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

    典型的自旋锁实现的例子,可以参考自旋锁的实现

    展开全文
  • java锁有哪些

    2020-09-26 11:48:44
    公平锁/非公平锁可重入锁独享锁/共享锁互斥锁/读写锁乐观锁/悲观锁分段锁偏向锁/轻量级锁/重量级锁自旋锁 Java实现锁两种语法,一种是synchronized语句,另外一种是reentrantlock关键字。上面是很多锁的名词,...
  • J2SE基础:1....4. Object有哪些公用方法?5. Java的四种引用,强弱软虚,用到的场景。6. Hashcode的作用。7. ArrayList、LinkedList、Vector的区别。8. String、StringBuffer与StringBuilder的区别。9...
  • 你知道的Java锁有哪些? synchronized?Lock?它们又有什么区别?锁可分为哪些种类?是如何实现的? 类型
  • 如何确定对象的锁什么是可重入性,为什么说Synchronized 是可重入锁?JVM对Java的原生锁做了哪些优化?为什么说Synchronized是非公平锁?什么是锁消除和锁粗化?为什么说Synchronized是一个悲观锁?乐观锁的实现原理...
  • JAVA锁有哪些种类

    2018-12-29 14:25:21
    其实如果按照名称来说,锁大概以下名词:自旋锁 ,自旋锁的其他种类,阻塞锁,可重入锁 ,读写锁 ,互斥锁 ,悲观锁 ,乐观锁 ,公平锁 ,偏向锁, 对象锁,线程锁,锁粗化, 锁消除,轻量级锁,重量级锁, 信号量...
  • Java锁有哪些

    2020-05-15 18:42:01
    可重入锁 / 不可重入锁 独享锁 / 共享锁 互斥锁 / 读写锁 乐观锁 / 悲观锁 分段锁 偏向锁 / 轻量级锁 / 重量级锁 自旋锁 上面是很多锁的名词,这些分类并不是全是指锁的状态,的指锁的特性,的指锁的设计,下面...
  • java锁有哪些类(转)

    2019-10-09 00:50:16
    可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 Java实现锁两种语法,一种是synchronized语句,另外一种是reentrantlock关键字。上面...
  • 介绍的内容如下:公平锁/非公平锁可重入锁独享锁/共享锁互斥锁/读写锁乐观锁/悲观锁分段锁偏向锁/轻量级锁/重量级锁自旋锁上面是很多锁的名词,这些分类并不是全是指锁的状态,的指锁的特性,的指锁的设计,下面...
  • 1.乐观锁/悲观锁 2.独享锁/共享锁 ...5.可重入锁 6.自旋锁 7.分段锁 8.偏向锁/轻量级锁/重量级锁 辅助理解 参考链接 最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁 java15种锁的介绍 ...
  • JAVA锁有哪些

    2020-07-21 11:25:18
    可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 上面是很多锁的名词,这些分类并不是全是指锁的状态,的指锁的特性,的指锁...
  • 介绍的内容如下:公平锁 / 非公平锁可重入锁 / 不可重入锁独享锁 / 共享锁互斥锁 / 读写锁乐观锁 / 悲观锁分段锁偏向锁 / 轻量级锁 / 重量级锁自旋锁上面是很多锁的名词,这些分类并不是全是指锁的状态,的指锁的...
  • Java 中的锁有哪些

    2020-11-26 10:31:38
    可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁...
  • 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 公平锁/非公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁。 非公平锁是指多个线程获取锁的顺序并不是...
  • java里面的常用的都有哪些; 常用的有synchronized(Object)、ReentrantLock(重用)这些; synchronized修饰一个静态类和静态方法和修饰一个非静态方法有什么区别吗? Synchronized修饰非静态方法,俗称...
  • 介绍的内容如下:公平锁/非公平锁可重入锁独享锁/共享锁互斥锁/读写锁乐观锁/悲观锁分段锁偏向锁/轻量级锁/重量级锁自旋锁上面是很多锁的名词,这些分类并不是全是指锁的状态,的指锁的特性,的指锁的设计,下面...
  • 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 上面是很多锁的名词,这些分类并不是全是指锁的状态,的指锁的特性,的指锁的设计,下面总结的内容是对每...
  • JAVA有哪些种类,以及区别(转) 在读很多并发文章中,会提及各种各样锁如...可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 上面是很多锁的名词,这些分类并不是...
  • 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 上面是很多锁的名词,这些分类并不是全是指锁的状态,的指锁的特性,的指锁的设计,下面总结的内容是对每个锁的...
  • [Java多线程 五]---JAVA锁有哪些种类

    万次阅读 热门讨论 2017-09-02 12:09:25
    转载自: ...上一篇既然提到了锁,这一篇来详细介绍JAVA中的锁,也为之后JUC下的锁做一个铺垫 ... 自旋锁 ,自旋锁的其他种类,阻塞锁,可重入锁 ,读写锁 ,互斥锁 ,悲观锁 ,乐观锁 ,公平锁 ,偏向锁, 对象锁,线程
  • 介绍的内容如下:公平锁/非公平锁可重入锁独享锁/共享锁互斥锁/读写锁乐观锁/悲观锁分段锁偏向锁/轻量级锁/重量级锁自旋锁上面是很多锁的名词,这些分类并不是全是指锁的状态,的指锁的特性,的指锁的设计,下面...

空空如也

空空如也

1 2 3 4 5 ... 9
收藏数 173
精华内容 69
关键字:

java有哪些可重入锁

java 订阅