【并发编程】 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 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

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

相关推荐
wáng bēn3 分钟前
【java17】使用 Word 模板导出带替换符、动态表格和二维码的文档
java·word·itextpdf
脑袋大大的16 分钟前
判断当前是否为钉钉环境
开发语言·前端·javascript·钉钉·企业应用开发
Wy. Lsy44 分钟前
Kotlin基础学习记录
开发语言·学习·kotlin
全栈凯哥1 小时前
16.Spring Boot 国际化完全指南
java·spring boot·后端
M1A11 小时前
Java集合框架深度解析:LinkedList vs ArrayList 的对决
java·后端
Tanecious.1 小时前
C++--红黑树
开发语言·c++
Top`1 小时前
Java 泛型 (Generics)
java·开发语言·windows
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ2 小时前
如何使用Java WebSocket API实现客户端和服务器端的通信?
java·开发语言·websocket
Shartin2 小时前
Can201-Introduction to Networking: Application Layer应用层
服务器·开发语言·php
是小崔啊2 小时前
tomcat源码02 - 理解Tomcat架构设计
java·tomcat