文章目录
使用场景
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
是同步方法的标志位。
monitorenter
和monitorexit
是同步代码块的进出标志位。
synchronized
底层的源码就是引入了ObjectMonitor
重量级锁
在jdk1.6之前synchronized被称为重量级锁,是因为底层实现的ObjectMonitor
的调用过程涉及用户态和内存态的切换,这是Linux内核的复杂运行机制决定的,大量消耗系统资源,所以效率低
优化锁升级
流程
- 当一个线程需要锁资源时,首先判断当前资源锁持有者是否与当前线程是同一线程,如果是,拿到锁。(偏向锁/可重入)
- 如果不是同一线程,通过自旋+CAS尝试获取锁(自旋锁)
- 默认自旋10次,最终仍没有获得锁,升级为重量锁
简单说升级流程就是:无锁->偏向锁->轻量级锁->重量锁
synchronized和Lock的区别
- 原始构成
- synchronized关键字属于JVM层面,底层是通过monitor对象来完成,wait/notify方法也依赖于monitor对象,只有在同步块和方法中才能调用。
- Lock是具体类(java.util.concurrent.locks.Lock),是API层面的锁
- 使用方法
- synchronized不需要用户手动释放锁,当synchronized代码块执行完毕后系统会自动让线程释放对锁的占用
- ReentrantLock需要用户手动释放锁,如果没有主动释放锁,可能导致出现死锁现象。需要lock() unlock()配合try-finally语句块来完成
- 是否可中断
- synchronized不可中断,除非抛出异常或者正常运行完成
- ReentrantLock可中断:
- 设置超时方法tryLock(Long timeout,Timeunit unit)
- lockInterruptibly()放代码块中,调用interrupt()可中断
- 加锁是否公平
- synchronized是非公平锁
- ReentrantLock两者都可以,默认非公平锁,可以通过构造器传入
- 锁绑定多条件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();
}
}
}