Java基础-线程同步(synchronized,ReentrantLock)

synchronized与ReentrantLock比较

  1. 它们都是线程同步方式,都是阻塞式实现方式(即一个线程获取锁后,其他要获取该锁的线程处于阻塞状态)
  2. 都是可重入性锁,即可以对某一对象重复加锁。常见于递归,例如方法A调用加锁的方法B,而方法B又递归调用自身。如果不可以重入,递归调用时发现方法B已经上锁,需要等待。这是就出现了自身等待自身释放锁的情况。
  3. 两者性能差不多。在synchronized优化之前,性能较差。在优化后引入了偏向锁,轻量级锁(自旋锁),性能较ReentrantLock差不多。
  4. synchronized是Java关键字使用比较简单,加锁、释放锁都是自动进行的;而ReentrantLock是API层面的锁,是为了替代优化前synchronized而开发出的java.util.current包下的工具类。需要手动lock(),unlock(),一般配合try、catch、finally(释放锁)使用
  5. synchronized是非公平锁,线程唤醒是随机的。ReentrantLock可实现公平锁,即按照等待时间唤醒线程
  6. ReentrantLock可响应中断,使用lockInterruptibly()方法上锁时可以响应中断。当线程互相获取临界资源时而发生了死锁,ReentrantLock可以自动中断其中一个线程释放资源,从而使其他线程正常结束。
package thread;

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

public class ReenTrantLockDemo {
    private static Lock lock1 = new ReentrantLock();
    private static Lock lock2 = new ReentrantLock();

    public static void main(String[] args){
        Thread t1 = new Thread(new ThreadDemo(lock1, lock2), "t1");
        Thread t2 = new Thread(new ThreadDemo(lock2, lock1), "t1");

        t1.start();
        t2.start();
        t1.interrupt();
    }




    static class ThreadDemo implements Runnable{
        Lock lock1 ;
        Lock lock2;
        ThreadDemo(Lock lock1, Lock lock2){
            this.lock1 = lock1;
            this.lock2 = lock2;
        }
        @Override
        public void run() {
            try {
                lock1.lockInterruptibly();//可以响应中断的获取锁方式
                lock2.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock1.unlock();
                lock2.unlock();
                System.out.println(Thread.currentThread().getName()+"正常结束");
            }
        }
    }
}

在这里插入图片描述

ReentrantLock分析

ReentrantLock手动加锁、释放锁,锁的粒度比较小,适合加锁复杂的情况下使用。
ReentrantLock基于AQS(AbstractQueuedSynchronizer)和SupportLock。AQS利用硬件原语指令CAS(Compare-And-Swap)实现轻量级多线程控制,不会引起CPU上下文切换和调度。同时提供内存可见性和原子性。

源码分析
使用链表作为等待队列,状态state用volatile修饰,保证个线各线程可见。在这里插入图片描述
在初始化锁时通过传入参数(true)或者不传构造公平锁或非公平锁。
在这里插入图片描述
非公平方式获取锁,在lock方法中检查该对象是否被锁住,如果没有直接获取锁,否则调用acquire方法申请锁。
在这里插入图片描述

synchronized

在字节码文件中:
同步代码块:synchronized对代码块的同步使用monitorenter和monitorexit控制,当执行代码遇到monitorenter时,尝试获取当前monitor的锁,如果获得锁则继续往下执行,否则阻塞进入等待队列。继续执行到monitorexit时,释放锁。
方法:对于方法的同步,是将该方法的access_flag的synchronized的标志位置1,表示该方法是一个同步方法。

在Hotspot虚拟机中锁信息存放在对象头中,在对象头中还存放的有hashcode、GC分代信息、线程持有的锁、锁状态标志、偏向ID等。

锁分级
在Hotspot虚拟机中,Java锁分为四种:偏向锁、轻量级锁、自旋锁、重量级锁。锁只能升级,不可以降级,即轻量级锁可以升级为自旋锁而自旋锁不能降级为轻量级锁。
偏向锁
偏向锁是一种优化后的锁,它可以减少切换锁的开销。在Java中锁的获取大多数情况下都被同一线程获取到,而为了减少同一线程锁切换的开销,从而引入偏向锁。当一个线程获得锁之后,就进入偏向锁模式,当该线程继续请求该锁的时候,可以直接将锁给该线程,省去了切换的开销。当有其他线程请求锁的时候,则退出偏向锁,进入轻量级锁状态。

轻量级锁
在偏向锁失败后,会进入轻量级锁状态。轻量级锁是为了减少传统重量级锁使用用操作系统互斥而设计的。Java经验发现,在同一时间段大多只有一个线程在执行,很少有其他线程在同一时刻竞争锁,线程大多是交替执行。所以为了减少操作系统互斥的开销,引入轻量级锁。当在同一时刻有多个线程竞争锁时,轻量级锁则会升级为自旋锁。

自旋锁
当线程因阻塞而需要执行挂起操作时,操作系统会从用户态转成核心态,需要耗费较长的时间。
当轻量级锁失败后,为了优化锁的执行效率,会进入自旋锁。自旋锁是让线程执行无意义的循环等待需要的锁,从而避免了线程的挂起操作而引起的系统从用户态转为核心态。但是这个等待时间是有限,当一段时间后该线程仍未获得锁,则将线程挂起,进入重量级锁,减少处理器资源的消耗。

重量级锁
重量级锁就是通常意义上的锁,利用监视器(monitor)实现。当前线程会被阻塞挂起,系统需要从用户态转为核心态来执行该操作。需要耗费较多的时间。这就是synchronized在优化之前效率低下的原因。

发布了13 篇原创文章 · 获赞 0 · 访问量 107

猜你喜欢

转载自blog.csdn.net/weixin_43602614/article/details/104950813