synchronized
synchronized是 Java 编程语言中的一个关键字,用于提供线程同步。在多线程编程中,synchronized可以确保在同一时间只有一个线程可以访问受保护的代码区域,从而避免数据的不一致性和其他并发问题。
使用方法
synchronized关键字通常使用在下面四个地方:
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
//修饰实例方法,锁的实例对象
public synchronized void demo1() {
System.out.println("junfeng");
}
//修饰静态方法,锁的类对象
public synchronized static void demo2() {
System.out.println("junfeng");
}
public void demo3() {
//锁的实例对象
synchronized (this) {
System.out.println("junfeng");
}
}
public void demo4() {
//锁的类对象
synchronized (SychronizedTest.class) {
System.out.println("junfeng");
}
}
同步方法
同步普通方法
public static void main(String[] args) {
for (int i = 0; i < 7; i++) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
}
}
package com.junfeng.manage.test;
public class MyThread implements Runnable {
@Override
public void run() {
printTest();
}
private synchronized void printTest() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "~~~~" + System.currentTimeMillis());
}
}
打印结果:
Thread-6~~~~1697077107016
Thread-0~~~~1697077107016
Thread-5~~~~1697077107016
Thread-2~~~~1697077107016
Thread-3~~~~1697077107016
Thread-4~~~~1697077107016
Thread-1~~~~1697077107016
上面代码执行一下子就打印出结果,说明多个线程之间是没有阻塞的。若是将printTest方法改成静态方法
同步静态方法
package com.junfeng.manage.test;
public class MyThread implements Runnable {
@Override
public void run() {
printTest();
}
private synchronized static void printTest() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "~~~~" + System.currentTimeMillis());
}
}
打印结果:
Thread-0~~~~1697078155367
Thread-6~~~~1697078156382
Thread-5~~~~1697078157383
Thread-4~~~~1697078158385
Thread-3~~~~1697078159387
Thread-2~~~~1697078160395
Thread-1~~~~1697078161401
发现打印每个一秒打印一次,说明线程之间是阻塞的。
综上代码执行结果,说明sychronized修饰普通方法时,其修饰的是方法实例对象;而sychronized修饰静态方法时,其修饰的为类对象。
同步代码块
同上同理,sychronized修饰this时,其修饰的是方法实例对象;而sychronized修饰MyThread.class时,其修饰的为类对象。
锁升级
在Java中,synchronized锁的升级是指锁的状态从无锁状态逐渐升级为偏向锁、轻量级锁和重量级锁的过程。
首先看看32位JVM中Mark Word存储结构:
sychronized锁升级详细过程如下图:
原子性、可见性、有序性
- 原子性: synchronized是通过monitorenter和monitorexit这两个字节码指令来实现原子性的。
- 可见性: 对变量调用的unlock解锁这个操作规定,放开对某个变量的锁的之前,需要把这个变量从缓存更新到主内存,因此它也具有可见性。
- 有序性:因为在一个线程内部,他不管怎么指令重排,他都是as-if-serial的,也就是说单线程即使重排序之后的运行结果和串行运行的结果是一样的,是类似串行的语义。
synchronized自旋
synchronized升级过程中有2次自旋。
- 第一次,发生在 synchronized 获取轻量级锁时,即当一个线程尝试获取一个被其他线程持有的轻量级锁时,它会自旋等待锁的持有者释放锁。最多15次
- 第二次,发生在 synchronized 轻量级锁升级到重量级锁的规程中。即当一个线程尝试获取一个被其他线程持有的重量级锁时,它会自旋等待锁的持有者释放锁。
ReentrantLock和synchronized区别
- synchronized是java的关键字,ReentrantLock是JDK1.5后提供的API。
- synchronized使用方便不用手动释放锁,ReentrantLock需要使用unlock()方法释放锁。
- synchronized为非公平锁,ReentrantLock可设置为公平锁,默认为非公平锁。
- synchronized不能响应中断,ReentrantLockkey中断(lockInterruptibly可打断模式,可以使用interrupt中断)。