Java多线程锁

1.简述

  :把需要的代码块,资源或数据锁上,只允许一个线程去操作,保证了并发时共享数据的一致性。

2.公平锁&非公平锁

  公平锁:多个线程按照申请锁的顺序去获得锁,所有线程都在队列里排队,这样就保证了队列中的第一个先得到锁。

  非公平锁:多个线程不按照申请锁的顺序去获得锁,而是同时直接去尝试获取锁(插队),获取不到(插队失败),再进入队列等待(失败则乖乖排队),如果能获取到(插队成功),就直接获取到锁。

  公平锁的优缺点

  • 优点:所有的线程都能得到资源,不会饿死在队列中。
  • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

  非公平锁的优缺点

  • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
  • 缺点:可能导致队列中排队的线程一直获取不到锁或者长时间获取不到锁,活活饿死。

  性能

  • 非公平锁性能优于公平锁:因为公平锁在获取锁时,永远是等待时间最长的线程获取到锁,这样当线程释放锁以后,如果还想继续再获取锁,它也得去同步队列尾部排队,这样就会频繁的发生线程的上下文切换,当线程越多,对CPU的损耗就会越严重。非公平锁性能虽然优于公平锁,但是会存在导致线程饥饿的情况。在最坏的情况下,可能存在某个线程一直获取不到锁。不过相比性能而言,饥饿问题可以暂时忽略,这可能就是ReentrantLock默认创建非公平锁的原因之一了。

  以买票为例子实现公平锁与非公平锁demo如下

public class Demo{
    //公平锁
    private static Lock fairLock = new ReentrantLock(true);
    //非公平锁
    private static Lock nonFairLock = new ReentrantLock();
    private static int num = 100;

    public static void main(String[] args) throws Exception {
        new Thread(new Runnable(){
            public void run() {
                testFairLock();
            }
        }, "A窗口").start();
        new Thread(new Runnable(){
            public void run() {
                testFairLock();
            }
        }, "B窗口").start();
    }
    
    /**公平锁测试方法
     */
    public static void testFairLock(){
        while (true) {
            try {
                fairLock.lock();
                if (num > 0) {
                    Thread.sleep(100);
                    --num;
                    System.out.println(Thread.currentThread().getName() + "占用1个座位,还剩余 " + num + "个座位");
                } else {
                    System.out.println(Thread.currentThread().getName() + ":不好意思,票卖完了!");
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                fairLock.unlock();
            }
        }
    }
    
    /**非公平锁测试方法
     */
    public static void testNonFairLock(){
        while (true) {
            try {
                nonFairLock.lock();
                if (num > 0) {
                    Thread.sleep(100);
                    --num;
                    System.out.println(Thread.currentThread().getName() + "占用1个座位,还剩余 " + num + "个座位");
                } else {
                    System.out.println(Thread.currentThread().getName() + ":不好意思,票卖完了!");
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                nonFairLock.unlock();
            }
        }
    }
}
View Code

  通过输出可以看出公平锁是有序的排队执行,而非公平则不是。

  对比公平锁与非公平锁的性能demo如下

public class Demo{
    //公平锁
    private static Lock fairLock = new ReentrantLock(true);
    //非公平锁
    private static Lock nonFairLock = new ReentrantLock();
    //公平锁计数器
    private static int fairCount = 0;
    //非公平锁计数器
    private static int nonFairCount = 0;

    public static void main(String[] args) throws Exception {
        System.out.println("公平锁耗时:" + testFairLock(10));
        System.out.println("非公平锁耗时:" + testNonFairLock(10));
        System.out.println("公平锁累加结果:" + fairCount);
        System.out.println("非公平锁累加结果:" + nonFairCount);
    }
    
    /**公平锁测试方法
     */
    public static long testFairLock(int threadNum) throws InterruptedException{
        long startTime = System.currentTimeMillis();
        final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        // 创建threadNum个线程,让其以公平锁的方式,对fairCount进行自增操作
        for (int i = 0; i < threadNum; i++) {
            new Thread(new Runnable(){
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        fairLock.lock();
                        fairCount++;
                        fairLock.unlock();
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }
        // 让所有线程执行完
        countDownLatch.await();
        long endTime = System.currentTimeMillis();
        return endTime - startTime;
    }
    
    /**非公平锁测试方法
     */
    public static long testNonFairLock(int threadNum) throws InterruptedException{
        long startTime = System.currentTimeMillis();
        final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        // 创建threadNum个线程,让其以公平锁的方式,对fairCount进行自增操作
        for (int i = 0; i < threadNum; i++) {
            new Thread(new Runnable(){
                public void run() {
                    for (int j = 0; j < 10000; j++) {
                        nonFairLock.lock();
                        nonFairCount++;
                        nonFairLock.unlock();
                    }
                    countDownLatch.countDown();
                }
            }).start();
        }
        // 让所有线程执行完
        countDownLatch.await();
        long endTime = System.currentTimeMillis();
        return endTime - startTime;
    }
}
View Code

  从上面的测试结果可以发现,非公平锁的耗时远远小于公平锁的耗时,这说明非公平锁在并发情况下,性能更好,吞吐量更大。当线程数越多时,差异越明显。

3.可重入锁&不可重入锁

  可重入锁:也叫递归锁,指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响。在Java中有ReentrantLock和synchronized。

  不可重入锁:若当前线程执行中已经获取了锁,如果再次获取该锁时,就会获取不到被阻塞。在Java中有NonReentrantLock。

  可重入锁诞生的目的就是防止死锁,导致同一个线程不可重入上锁代码段,目的就是让同一个线程可以重新进入上锁代码段

  可重入锁​synchronized示例

public class Demo{

    public static void main(String[] args) throws Exception {
        new Thread(new Runnable() {
            public void run() {
                synchronized (this) {
                    System.out.println("第1次获取锁,这个锁是:" + this);
                    int index = 1;
                    while (true) {
                        synchronized (this) {
                            System.out.println("第" + (++index) + "次获取锁,这个锁是:"+ this);
                        }
                        if (index == 10) {
                            break;
                        }
                    }
                }
            }
        }).start();
    }
}
View Code

  可重入锁reentrantLock示例

public class Demo{

    public static void main(String[] args) throws Exception {
        final Lock lock = new ReentrantLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("第1次获取锁,这个锁是:" + lock);
                    int index = 1;
                    while (true) {
                        try {
                            lock.lock();
                            System.out.println("第" + (++index) + "次获取锁,这个锁是:"+ lock);
                            try {
                                Thread.sleep(new Random().nextInt(200));
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            if (index == 10)
                                break;
                        } finally {
                            lock.unlock();
                        }
                    }
                } finally {
                    lock.unlock();
                }
            }
        }).start();
    }
}
View Code

  注意点:ReentrantLock和synchronized不一样,ReentrantLock需要手动释放锁,所以使用 ReentrantLock的时候一定要手动释放锁,并且加锁次数和释放次数要一样,最好在 finally中进行锁释放

  自定义可重入所示例

public class Demo{

    public static void main(String[] args) throws Exception {
        final Lock lock = new Lock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("第1次获取锁,这个锁是:" + lock);
                    int index = 1;
                    while (true) {
                        try {
                            lock.lock();
                            System.out.println("第" + (++index) + "次获取锁,这个锁是:"+ lock);
                            try {
                                Thread.sleep(new Random().nextInt(200));
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            if (index == 10)
                                break;
                        } catch (Exception e) {
                            System.out.println("第" + index + "次获取锁出现异常:"+ e.getMessage());
                        } finally {
                            lock.unlock();
                        }
                    }
                } catch (Exception e) {
                    System.out.println("第1次获取锁出现异常:"+ e.getMessage());
                } finally {
                    lock.unlock();
                }
            }
        }).start();
    }
}

class Lock{
    boolean isLocked = false;
    Thread  lockedBy = null;
    int lockedCount = 0;
    public synchronized void lock()throws InterruptedException{
        Thread thread = Thread.currentThread();
        while(isLocked && lockedBy != thread){
            wait();
        }
        isLocked = true;
        lockedCount++;
        lockedBy = thread;
    }
    
    public synchronized void unlock(){
        if(Thread.currentThread() == this.lockedBy){
            lockedCount--;
            if(lockedCount == 0){
                isLocked = false;
                notify();
            }
        }
    }
}
View Code

  所谓可重入,意味着线程可以进入它已经拥有的锁的同步代码块儿。

  可重入锁,是指同一个线程不可以重入上锁后的代码段

  不可重入锁示例

public class Demo{

    public static void main(String[] args) throws Exception {
        final NonReentrantLock lock = new NonReentrantLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("第1次获取锁,这个锁是:" + lock);
                    int index = 1;
                    while (true) {
                        try {
                            lock.lock();
                            System.out.println("第" + (++index) + "次获取锁,这个锁是:"+ lock);
                            try {
                                Thread.sleep(new Random().nextInt(200));
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            if (index == 10)
                                break;
                        } catch (Exception e) {
                            System.out.println("第" + index + "次获取锁出现异常:"
                                    + e.getMessage());
                        } finally {
                            lock.unlock();
                        }
                    }
                } catch (Exception e) {
                    System.out.println("第1次获取锁出现异常:" + e.getMessage());
                } finally {
                    lock.unlock();
                }
            }
        }).start();
    }
}

class NonReentrantLock {
    private boolean isLocked = false;

    public synchronized void lock() throws InterruptedException {
        while (isLocked) {
            wait();
        }
        isLocked = true; //线程第一次进入后就会将器设置为true,第二次进入是就会由于where true进入死循环
    }

    public synchronized void unlock() {
        isLocked = false;//将这个值设置为false目的是释放锁
        notify();//结束阻塞
    }
}
View Code

  第一次上锁后,由于没有释放锁,因此执行第一次lock后isLocked = true,这个时候又一次调用了lock(),由于上个线程将isLocked = true,再次进入的时候就进入死循环。从而导致线程无法执行下去。这种现象就造成了不可重入锁。

4.悲观锁&乐观锁

  悲观锁:悲观锁在操作数据时比较悲观,认为别人会同时修改数据。Java中Synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

  乐观锁:乐观锁在操作数据时非常乐观,认为别人不会同时修改数据。在Java中java.util.concurrent.atomic包下面的原子变量类就是基于CAS实现的乐观锁。

  使用场景

  • 悲观锁:适用于读比较少的情况下(多写场景),如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行重试,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
  • 乐观锁:适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。

  悲观锁的demo如下

public class Demo{

    public static void main(String[] args) throws Exception {
        for (int i = 1; i <= 5; i++) {
            new Thread(new Runnable(){
                public void run() {
                    System.out.println(Thread.currentThread().getName() + ":等待");
                    visit();
                }
            }, i+"号线程").start();
        }
    }
    
    private static synchronized void visit() {
        System.out.println(Thread.currentThread().getName()+ ":操作数据 ");
        try {
            Thread.sleep((long) (Math.random() * 5000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
View Code

  从输出可以看到,每次有线程访问这个资源visit()方法时,在访问前先锁定了资源,导致其他线程只能等待,也就是说不能多个线程在访问它。

  乐观锁可以使用版本号机制CAS算法实现。

  乐观锁:乐观锁不需要线程挂起等待,所以也叫非阻塞同步。

  版本号机制一般在一个数据表中加一个version字段,表示这个数据被更新的次数,当这个数据被修改一次,版本号就加1。

  版本号机制条件:提交版本必须大于当前记录的版本。

  版本号机制实现例子如下

我现在银行账户有10元,现在有一个version字段,版本号为1。

现在我A操作取出2元,我先读入数据version=1,然后扣除。

与此同时,B操作也要取出1元,读入数据version=1,然后扣除。

这个时候,A操作完成,上传数据,版本号加1,version=2,这个版本大于当前的记录值1,所以更新操作完成。

这个时候,B操作也完成了,也要更新,他的版本号加1,version=2,然后更新的时候发现这个版本号和当前记录的版本号相同,不满足提交版本号必须大于当前记录的版本号的条件,不允许更新。

这个时候,B操作就要重新读入再重复之前的步骤。

  通过这样的方式,我们就保证了B操作不会将A操作所更新的值覆盖,保证了数据的同步。

  CAS算法:即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。

  CAS算法涉及到三个操作数

  • 需要读写的内存值V。
  • 进行比较的值A。
  • 拟写入的新值B。

  当且仅当V的值等于A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。

  使用AtomicBoolean原子类实现的一个无阻塞多线程争抢资源的模型,示例如下

public class Demo{
    
    private static AtomicBoolean flag = new AtomicBoolean(true);

    public static void main(String[] args) throws Exception {
        new Thread(new Runnable() {
            public void run() {
                test();
            }
        }, "线程1").start();
        new Thread(new Runnable() {
            public void run() {
                test();
            }
        }, "线程2").start();
    }
    
    private static void test() {
        System.out.println(Thread.currentThread().getName()+":flag="+flag.get());
        if (flag.compareAndSet(true, false)){
            System.out.println(Thread.currentThread().getName()+":flag="+flag.get());
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag.set(true);
        }else{
            System.out.println(Thread.currentThread().getName()+"重试机制:flag="+flag.get());
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            test();
        }
    }
}
View Code

  示例中compareAndSet(true, false)方法可以拆开成compare(true)方法和Set(false)方法理解,是compare(true)等于true后,就马上设置共享内存为false,这个时候,其它线程无论怎么走都无法走到只有得到共享内存为true时的程序隔离方法区。

  但是这种得不到状态为true时使用递归算法是很耗cpu资源的,所以一般情况下,都会有线程sleep。

  CAS的缺点

  • CPU开销较大:在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。
  • 不能保证代码块的原子性:CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证多个变量共同进行原子性的更新,就不得不使用Synchronized了。

5.独享锁&共享锁

  独享锁和共享锁都是通过AQS队列来实现的,通过实现不同的方法,来实现独享或者共享。

  独享锁:该锁一次只能被一个线程所持有,参考synchronized以及JUC包下的ReentrantLock。

  共享锁:该锁可被多个线程所持有,参考JUC包下的ReentrantReadWriteLock。

6.互斥锁&读写锁

  互斥锁:互斥锁与悲观锁、独占锁同义,表示某个资源只能被一个线程访问,其他线程不能访问,Java提供了两种互斥锁来解决在共享资源时存在的并发问题,一种方式是synchronized关键字,另一种方式是显式的使用Lock对象。

  读写锁:读写锁是一种技术,通过ReentrantReadWriteLock类来实现。为了提高性能, Java提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由 jvm 自己控制的。

  读写锁:

  • 读锁: 允许多个线程获取读锁,同时访问同一个资源。
  • 写锁: 只允许一个线程获取写锁,不允许同时访问同一个资源。

  synchronized同步代码块的方式实现互斥锁

public class Demo {
    //数量
    private static int count = 100;
    
    public static void main(String[] args) {
        for (int i = 1; i <= 3; i++) {
            new Thread(new Runnable(){
                public void run() {
                    while(true){
                        if(count > 1)
                            synchronizedTest();
                        else
                            break;
                    }
                }
            },i+"号线程").start();
        }
    }
    
    private synchronized static void synchronizedTest(){
        try {
            System.out.println(Thread.currentThread().getName()+"剩余数量为:" + count--);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
View Code

  显式Lock锁方式实现互斥锁

public class Demo {
    //数量
    private static int count = 100;
    private static Lock lock = new ReentrantLock();
    
    public static void main(String[] args) {
        for (int i = 1; i <= 3; i++) {
            new Thread(new Runnable(){
                public void run() {
                    while(true){
                        if(count > 1)
                            synchronizedTest();
                        else
                            break;
                    }
                }
            },i+"号线程").start();
        }
    }
    
    private static void synchronizedTest(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"剩余数量为:" + count--);
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        lock.unlock();
    }
}
View Code

  读写锁实现操作缓存

class ReadWriteCache {
    //充当cache
    static Map<String, Object> map = new HashMap<String, Object>();
    //实例化读写锁对象
    static ReadWriteLock rwLock = new ReentrantReadWriteLock();
    //实例化读锁
    static Lock read = rwLock.readLock();
    //实例化写锁
    static Lock write = rwLock.writeLock();

    //获取缓存中值
    public static final Object get(String key) {
        read.lock();
        try {
            return map.get(key);
        } finally {
            read.unlock();
        }
    }

    //写缓存中值,并返回对应value
    public static final Object set(String key, Object obj) {
        write.lock();
        try {
            return map.put(key, obj);
        } finally {
            write.unlock();
        }
    }

    //清空所有内容
    public static final void clear() {
        write.lock();
        try {
            map.clear();
        } finally {
            write.unlock();
        }
    }
}
View Code

7.分段锁

  分段锁:其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作(Java 8, JDK弃用了这个策略,重新使用了synchronized)。

  基于散列的Map的实现分段锁

class StripedMap {
    // 同步策略: buckets[n]由 locks[n%N_LOCKS] 来保护
    private static final int N_LOCKS = 16;// 分段锁的个数
    private final Node[] buckets;
    private final Object[] locks;

    /**结点
     * @param <K>
     * @param <V>
     */
    private static class Node<K, V> implements Map.Entry<K, V> {
        final K key;// key
        V value;// value
        Node<K, V> next;// 指向下一个结点的指针
        int hash;// hash值

        // 构造器,传入Entry的四个属性
        Node(int h, K k, V v, Node<K, V> n) {
            value = v;
            next = n;// 该Entry的后继
            key = k;
            hash = h;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }
    }

    /**构造器: 初始化散列桶和分段锁数组
     * @param numBuckets
     */
    public StripedMap(int numBuckets) {
        buckets = new Node[numBuckets];
        locks = new Object[N_LOCKS];
        for (int i = 0; i < N_LOCKS; i++) {
            locks[i] = new Object();
        }
    }

    /**返回散列之后在散列桶之中的定位
     * @param key
     * @return
     */
    private final int hash(Object key) {
        return Math.abs(key.hashCode() % N_LOCKS);
    }

    /**分段锁实现的get
     * @param key
     * @return
     */
    public Object get(Object key) {
        int hash = hash(key);// 计算hash值
        // 获取分段锁中的某一把锁
        synchronized (locks[hash % N_LOCKS]) {
            for (Node m = buckets[hash]; m != null; m = m.next) {
                if (m.key.equals(key)) {
                    return m.value;
                }
            }
        }
        return null;
    }

    /**清除整个map
     */
    public void clear() {
        // 分段获取散列桶中每个桶地锁,然后清除对应的桶的锁
        for (int i = 0; i < buckets.length; i++) {
            synchronized (locks[i % N_LOCKS]) {
                buckets[i] = null;
            }
        }
    }
}
View Code

  实现示例中使用了N_LOCKS个锁对象数组,并且每个锁保护容器的一个子集,对于大多数的方法只需要回去key值的hash散列之后对应的数据区域的一把锁就行了。但是对于某些方法却要获得全部的锁,比如clear()方法,但是获得全部的锁不必是同时获得,可以使分段获得。

8.自旋锁

  自旋锁:是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。

  Java自旋锁:Jdk提供的java.util.concurrent.atomic包里面提供了一组原子类。基本上就是当前获取锁的线程,执行更新的方法,其他线程自旋等待,比如atomicInteger类中的getAndAdd方法内部实际上使用的就是Unsafe的方法。

  利用CAS实现自旋锁

public class Demo {
    static int count = 0;
    public static void main(String[] args) throws Exception {
        //线程池
        ExecutorService exec = Executors.newFixedThreadPool(2);
        //任务集合
        List<Callable<Integer>> tasks = new ArrayList<Callable<Integer>>();
        //任务
        Callable<Integer> task = null;
        final SpinLock spinLock = new SpinLock();
        for (int i = 0; i < 2; i++) {
            task = new Callable<Integer>() {
                public Integer call() throws Exception {
                    while(count < 50){
                        spinLock.lock();
                        System.out.println(Thread.currentThread().getName()+" 开始运行");
                        count++;
                        Thread.sleep(500);
                        System.out.println(Thread.currentThread().getName()+" 结束运行");
                        spinLock.unLock();
                    }
                    return 1;
                }
            };
            //这里提交的任务容器列表和返回的Future列表存在顺序对应的关系
            tasks.add(task);
        }
        //执行线程任务
        exec.invokeAll(tasks);
        //关闭线程池
        exec.shutdown();
        System.out.println(count);
    }
}

class SpinLock {
    /**持有锁的线程,null表示锁未被线程持有
     */
    private AtomicReference<Thread> cas = new AtomicReference<>();

    public void lock() {
        Thread currentThread = Thread.currentThread();
        while (!cas.compareAndSet(null, currentThread)) {//利用CAS
            //通过循环不断的自旋判断锁是否被其他线程持有
        }
        System.out.println(Thread.currentThread().getName()+" 获取锁");
    }

    public void unLock() {
        Thread cur = Thread.currentThread();
        cas.compareAndSet(cur, null);
        System.out.println(Thread.currentThread().getName()+" 释放锁");
    }
}
View Code

  实现示例中看出,自旋就是在循环判断条件是否满足,如果锁被占用很长时间的话,自旋的线程等待的时间也会变长,白白浪费掉处理器资源。因此在JDK中,自旋操作默认10次,我们可以通过参数“-XX:PreBlockSpin”来设置,当超过来此参数的值,则会使用传统的线程挂起方式来等待锁释放。

9.偏向锁&轻量级锁&重量级锁

  锁的状态总共有四种,级别由低到高依次为:无锁、偏向锁、轻量级锁、重量级锁,只能升级,不能降级。

  偏向锁:偏向锁就是在运行过程中,对象的锁偏向某个线程。即在开启偏向锁机制的情况下,某个线程获得锁,当该线程下次再想要获得锁时,不需要再获得锁(即忽略synchronized关键词),直接就可以执行同步代码,比较适合竞争较少的情况。

  轻量级锁:轻量级锁不是用来替代传统的重量级锁的,而是在没有多线程竞争的情况下,使用轻量级锁能够减少性能消耗,但是当多个线程同时竞争锁时,轻量级锁会膨胀为重量级锁。

  重量级锁:即当有其他线程占用锁时,当前线程会进入阻塞状态。

  这些锁不等同于Java API中的ReentratLock这种锁,这些锁是概念上的,是JDK1.6中为了对synchronized同步关键字进行优化而产生的的锁机制。这些锁的启动和关闭策略可以通过设定JVM启动参数来设置,当然在一般情况下,使用JVM默认的策略就可以了。

原文地址:https://www.cnblogs.com/bl123/p/14875245.html