【并发编程】 synchronized的普通方法,静态方法,锁对象,锁升级过程,可重入锁,非公平锁

目录

1.普通方法

2.静态方法

3.锁对象

4.锁升级过程

5.可重入的锁

6.不公平锁

[非公平锁的 lock 方法:](#非公平锁的 lock 方法:)


1.普通方法

将synchronized修饰在普通同步方法,那么该锁的作用域是在当前实例对象范围内,也就是说对于 SyncDemosd=newSyncDemo();这一个实例对象sd来说,多个线程访问access方法会有锁的限制。如果access已经有线程持有了锁,那这个线程会独占锁,直到锁释放完毕之前,其他线程都会被阻塞

java 复制代码
public SyncDemo{
   Object lock =new Object();
    //形式1
    public synchronized void access(){
       //
    }
    //形式2,作用域等同于形式1
    public void access1(){
       synchronized(lock){
         //
       }
    }
    //形式3,作用域等同于前面两种
    public void access2(){
       synchronized(this){
          //
       }
    }
}

2.静态方法

修饰静态同步方法或者静态对象、类,那么这个锁的作用范围是类级别。举个简单的例子,

java 复制代码
SyncDemo sd=SyncDemo();
SyncDemo sd2=new SyncDemo();} 

两个不同的实例sd和sd2, 如果sd这个实例访问access方法并且成功持有了锁,那么sd2这个对象如果同样来访问access方法,那么它必须要等待sd这个对象的锁释放以后,sd2这个对象的线程才能访问该方法,这就是类锁;也就是说类锁就相当于全局锁的概念,作用范围是类级别。

这里抛一个小问题,大家看看能不能回答,如果不能也没关系,后面会讲解;问题是如果sd先访问access获得了锁,sd2对象的线程再访问access1方法,那么它会被阻塞吗?

java 复制代码
public SyncDemo{
    static Object lock=new Object();
    //形式1
    public synchronized static void access(){
       //
    }
    //形式2等同于形式1
    public void access1(){
       synchronized(lock){
          //
       }
    }
    //形式3等同于前面两种
    public void access2(){
        synchronzied(SyncDemo.class){
          //
        }
    }
}

3.锁对象

普通同步方法,锁是当前实例对象。比如:

java 复制代码
public synchronized void doLongTimeTaskC() {}

4.锁升级过程

无锁------》偏向锁------》轻量级锁------》重量级锁------》GC锁

5.可重入的锁

可重入的含义:指的是同一个线程获得锁之后,再不释放锁的情况下,可以直接再次获取到该锁

java 复制代码
@Slf4j
public class SynReentrantDemo {
 
    public static void main(String[] args) {
 
        Runnable sellTicket = new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    String name = Thread.currentThread().getName();
                    log.info("我是run,抢到锁的是{}", name);
                    test01();
                } //正常来说走出临界区(这个括号)才会释放锁,但是再没走出之前,又进入test01,
                //而test01需要和本方法一样的锁
                //如果不可重入的话,就将出现死锁了-->即test01方法等着释放锁,而run方法又不会释放锁
                //因此synchronized只有可以在不释放run方法的锁的情况下,又再次获得该锁才不会有问题
            }
 
            public void test01() {
                synchronized (this) {
                    String name = Thread.currentThread().getName();
                    log.info("我是test01,抢到锁的是{}", name);
                }
            }
        };
        new Thread(sellTicket).start();
        new Thread(sellTicket).start();
    }
}

首先应该知道synchronized锁的并不是同步代码块,而是锁对象关联的一个monitor对象(在java中每一个对象都会关联一个monitor对象),而这个对象里有一个变量叫recursions --- 中文是递归的意思(我想大概是因为递归的时候发生可重入的几率应该是最大的,所以才用这个当变量名的吧),其实可以将它简单理解为一个计数器。

以上面的栗子为例:

(1)当线程1抢到run方法的执行权即抢到锁时,这个recursions的值就变为了1;

(2)线程1接着运行并进入test01方法后,发现还是线程1且还是要this这把锁,就将recursions的值再+1;

(3)当线程1,执行完test01方法时,recursions的值又-1

(4)执行完run方法时recursions的值又-1,就变为了0,也就是表示线程1已经释放了this锁。

(5)之后其他线程就可以继续抢this锁了。

6.不公平锁

非公平锁的 lock 方法:

java 复制代码
static final class NonfairSync extends Sync {
    final void lock() {
      
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    // AbstractQueuedSynchronizer.acquire(int arg)
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
 
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;
}

公平锁和非公平锁只有两处不同:

非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。

非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。

相关推荐
阿珊和她的猫3 小时前
v-scale-scree: 根据屏幕尺寸缩放内容
开发语言·前端·javascript
fouryears_234175 小时前
Flutter InheritedWidget 详解:从生命周期到数据流动的完整解析
开发语言·flutter·客户端·dart
我好喜欢你~6 小时前
C#---StopWatch类
开发语言·c#
桦说编程7 小时前
Java 中如何创建不可变类型
java·后端·函数式编程
lifallen7 小时前
Java Stream sort算子实现:SortedOps
java·开发语言
IT毕设实战小研7 小时前
基于Spring Boot 4s店车辆管理系统 租车管理系统 停车位管理系统 智慧车辆管理系统
java·开发语言·spring boot·后端·spring·毕业设计·课程设计
CCCC13101638 小时前
嵌入式学习(day 28)线程
jvm·学习
没有bug.的程序员8 小时前
JVM 总览与运行原理:深入Java虚拟机的核心引擎
java·jvm·python·虚拟机
甄超锋8 小时前
Java ArrayList的介绍及用法
java·windows·spring boot·python·spring·spring cloud·tomcat
cui__OaO8 小时前
Linux软件编程--线程
linux·开发语言·线程·互斥锁·死锁·信号量·嵌入式学习