Synchronized_详细解析

本文详细探讨了Java中`synchronized`关键字的使用场景,包括静态与非静态同步方法之间的区别,以及一个线程进入同步方法后其他线程的行为。同时,介绍了JVM中对象的构成、JMM内存模型的关键特性,如可见性、原子性和有序性,并讨论了`synchronized`的可重入性和不可中断性。文章还深入到`synchronized`的底层实现,包括锁升级的优化流程,以及与Lock接口的区别,如公平锁和非公平锁、可中断性等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用场景

public synchronized void test2(){
    log.info("实例方法");
}

public synchronized static void test3(){
    log.info("静态方法");
}

public void test4(){
    log.info("代码块");
}

引出两个小问题:

一个类中同时有synchronized static方法和synchronized的方法,这两个方法同步吗?

不同步

  • 静态方法与成员方法的区别是,静态方法归属类,成员方法归属于对象
  • synchronized方法锁定的是当前对象
  • 如果是静态同步方法,锁定的是类的Class对象。注意这里不是锁定此类的所有对象,仅是唯一的Class对象。
  • 如果是普通同步方法,锁定的是调用该方法的那个对象。

下面是我之前测试的代码,如果两个方法是同步的话,结果应该是一个线程锁定对象循环输出完释放对象后,另一个线程再循环输出,但实际上是两个线程交替执行的,所以这两个方法不同步。

public class Test {
	
	public static void main(String[] args) {
		Test test=new Test();
		test.new T1().start();
		test.new T2().start();
	}
	
	public synchronized static void func1() {
		for(int i=0;i<1000;i++) {
			System.out.println("this is a synchronized static method");
		}
	}
	
	public synchronized void func2() {
		for(int i=0;i<1000;i++) {
			System.out.println("this is a synchronized method");
		}
	}
	
	class T1 extends Thread{
		@Override
		public void run() {
			func1();
		}
	}
	class T2 extends Thread{
		@Override
		public void run() {
			func2();
		}
	}
}

当一个线程进入一个对象的synchronized方法后,其他线程是否可以进入此对象的其他方法?

  • 其他方法如果是synchronized方法
    • 如果当前synchronized方法中没有调用对象的wait方法,则其他线程不可以进入
    • 如果当前synchronized方法中调用了对象的wait方法,则其他线程可以进入
  • 其他方法如果是普通的成员方法或者static方法,则其他线程可以进入,因为未用synchronized修饰的方法意味着不需要数据同步。
  • 其他方法如果是synchronized static方法,则其他线程可以进入,因为因为synchronized成员方法同步锁定的是当前方法所属实例对象,synchronized static方法同步锁定的是当前类的Class对象,并非同一个对象。

特性

JVM中对象构成

对象头

  • Mark Word(标记字段):默认存储对象的HashCode,分代年龄和锁标志位信息。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。
  • Klass Point(类型指针):对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

实例数据

存放类的数据信息,父类的信息。

对齐填充

由于虚拟机要求对象起始地址必须是8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐。

JMM内存模型

https://blog.youkuaiyun.com/Chill_Lyn/article/details/106056359

可见性、原子性、有序性

可重入性

synchronized锁对象的时候有个计数器,他会记录下线程获取锁的次数,在执行完对应的代码块之后,计数器就会-1,直到计数器清零,就释放锁了。

不可中断性

一个线程获取锁之后,另外一个线程处于阻塞或者等待状态,前一个不释放,后一个也一直会阻塞或者等待,不可以被中断。

底层实现

测试代码

public class SynchronizedTest {

	//同步方法
    public synchronized void test1(){
    	//同步代码块
        synchronized (Object.class){
            System.out.println(123);
        }
    }
}

找到编译后的.class文件,执行javap -c -v -p SynchronizedTest.class命令
在这里插入图片描述
ACC_SYNCHRONIZED是同步方法的标志位。
monitorentermonitorexit是同步代码块的进出标志位。

synchronized底层的源码就是引入了ObjectMonitor

重量级锁

在jdk1.6之前synchronized被称为重量级锁,是因为底层实现的ObjectMonitor的调用过程涉及用户态和内存态的切换,这是Linux内核的复杂运行机制决定的,大量消耗系统资源,所以效率低

优化锁升级

流程

在这里插入图片描述

  • 当一个线程需要锁资源时,首先判断当前资源锁持有者是否与当前线程是同一线程,如果是,拿到锁。(偏向锁/可重入)
  • 如果不是同一线程,通过自旋+CAS尝试获取锁(自旋锁)
  • 默认自旋10次,最终仍没有获得锁,升级为重量锁
    在这里插入图片描述
    简单说升级流程就是:无锁->偏向锁->轻量级锁->重量锁

synchronized和Lock的区别

  1. 原始构成
  • synchronized关键字属于JVM层面,底层是通过monitor对象来完成,wait/notify方法也依赖于monitor对象,只有在同步块和方法中才能调用。
  • Lock是具体类(java.util.concurrent.locks.Lock),是API层面的锁
  1. 使用方法
  • synchronized不需要用户手动释放锁,当synchronized代码块执行完毕后系统会自动让线程释放对锁的占用
  • ReentrantLock需要用户手动释放锁,如果没有主动释放锁,可能导致出现死锁现象。需要lock() unlock()配合try-finally语句块来完成
  1. 是否可中断
  • synchronized不可中断,除非抛出异常或者正常运行完成
  • ReentrantLock可中断:
    • 设置超时方法tryLock(Long timeout,Timeunit unit)
    • lockInterruptibly()放代码块中,调用interrupt()可中断
  1. 加锁是否公平
  • synchronized是非公平锁
  • ReentrantLock两者都可以,默认非公平锁,可以通过构造器传入
  1. 锁绑定多条件Condition
  • synchronized只有随机唤醒或者全部唤醒
  • ReentrantLock可以通过Condition实现分组唤醒和精确唤醒
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditonDemo {


}

/**
 * A 打印5次 唤醒B 打印5次 再唤醒C打印5次
 */
class NumPrinter{
    private ReentrantLock lock=new ReentrantLock();
    Condition ca=lock.newCondition();
    Condition cb=lock.newCondition();
    Condition cc=lock.newCondition();

    private char name='a';

    public static void main(String[] args) {
        NumPrinter numPrinter=new NumPrinter();

        new Thread(numPrinter::printa,"A").start();
        new Thread(numPrinter::printb,"B").start();
        new Thread(numPrinter::printc,"C").start();
    }

    public void printa(){
        lock.lock();

        try {
            while (name!='a'){
                ca.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println('a');
            }
            name='b';
            cb.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }public void printb(){
        lock.lock();

        try {
            while (name!='b'){
                cb.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println('b');
            }
            name='c';
            cc.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }public void printc(){
        lock.lock();

        try {
            while (name!='c'){
                cc.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println('c');
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

参考

https://mp.weixin.qq.com/s/2ka1cDTRyjsAGk_-ii4ngw

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值