Synchronized使用场景
- Synchronized修饰实例方法:为当前实例this加锁
- Synchronized修饰静态方法:为当前Class实例加锁
- Synchronized修饰代码块:为Synchronized后面括号里修饰的实例加锁
注意: - 同一个类的不同实例拥有不同的锁,因此不会相互阻塞。
- 使用Synchronized修饰Class和实例时,由于Class和实例分别拥有不同的锁,因此不会相互阻塞。
- 如果一个线程正在访问实例的一个Synchronized修饰的实例方法时,其它线程不仅不能访问该Synchronized修饰的实例方法,该实例的其它ynchronized修饰的实例方法也不能访问,因为一个实例只有一个监视器锁,但是其它线程可以访问该实例的无Synchronized修饰的实例方法或Synchronized修饰的静态方法。
Synchronized如何保证线程安全
1.Synchronized保证原子性
Synchronized保证只有一个线程能拿到锁,进入同步代码块
2.synchronized保证可见性
执行synchronized时,对应的lock原子操作会让工作内存中从主内存中更新共享变量的值
3.synchronized保证有序性
synchronized后,虽然进行了重排序,保证只有一个线程会进入同步代码块,也能保证有序性。
Synchronized特性
可重入
public class SynchronizedDemo {
private static Object obj = new Object();
public static void main(String[] args) {
Runnable sellTicket = new Runnable() {
@Override
public void run() {
synchronized (SynchronizedDemo.class) {
System.out.println("我是run");
test01(); }
}
public void test01() {
synchronized (SynchronizedDemo.class) {
System.out.println("我是test01"); }
} };
new Thread(sellTicket).start();
}
}
我是run
我是test01
synchronized是可重入锁,内部锁对象中会有一个计数器记录线程获取几次锁,在执行完同步代码块时,计数器的数量会-1,知道计数器的数量为0,就释放这个锁。
不可中断
public class SynchronizedDemo {
private static Object obj = new Object();
public static void main(String[] args) throws InterruptedException {
Runnable run = () -> {
synchronized (obj) {
String name = Thread.currentThread().getName();
System.out.println(name + "进入同步代码块");
// 保证不退出同步代码块
try {
Thread.sleep(888888);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 3.先开启一个线程来执行同步代码块
Thread t1 = new Thread(run);
t1.start();
Thread.sleep(1000);
// 4.后开启一个线程来执行同步代码块(阻塞状态)
Thread t2 = new Thread(run);
t2.start();
// 5.停止第二个线程
System.out.println("停止线程前");
t2.interrupt();
System.out.println("停止线程后");
System.out.println(t1.getState());
System.out.println(t2.getState());
}
}
Thread-0进入同步代码块
停止线程前
停止线程后
TIMED_WAITING
BLOCKED
可以看到t2.interrupt()之后,t2线程仍然是阻塞状态,不可被中断
不可中断是指,当一个线程获得锁后,另一个线程一直处于阻塞或等待状态,前一个线程不释放锁,后一个线程会一直阻塞或等待,不可被中断。
synchronized属于不可被中断
Lock的lock方法是不可中断的
Lock的tryLock方法是可中断的
Synchronized原理
public class SynchronizedDemo {
private int i;
public void sync() {
synchronized (this) {
i++;
}
}
}
javap命令对class文件进行反汇编,查看字节码指令如下:
可以发现synchronized同步代码块是通过加monitorenter和monitorexit指令实现的。
每个对象都有个监视器锁(monitor),当monitor被占用的时候就代表对象处于锁定状态,而monitorenter指令的作用就是获取monitor的所有权,monitorexit的作用是释放monitor的所有权
public class SynchronizedDemo {
public synchronized void sync() {
}
}
javap命令对class文件进行反汇编,查看字节码指令如下:
当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。
两种同步方式本质上没有区别,只是方法的同步是一种隐式的方式来实现。
Synchronized锁升级
synchronized的锁升级,说白了,就是当JVM检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级。
synchronized是悲观锁,在操作同步资源之前需要给同步资源先加锁,这把锁就是存在Java对象头里的。得到锁的线程能访问同步资源。
Java对象头中的MarkWord
Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。这些信息都是与对象自身定义无关的数据,所以Mark Word被设计成一个非固定的数据结构以便在极小的空间内存存储尽量多的数据。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。
无锁
CAS
偏向锁
适用情况
一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低获取锁的代价。
偏向锁的偏是指会偏向第一个获得锁的线程。
原理
当一个线程访问同步代码块并获取锁时,会通过对象头里存储锁偏向的线程ID。
优点
在线程进入和退出同步块时不再通过CAS操作来加锁和解锁,而是检测对象头里是否存储着指向当前线程的偏向锁。
引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的锁执行操作。
轻量级锁
适用情况
当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,适用在多线程交替执行同步块的情况
原理
当一个线程访问同步代码块并获取锁时,会通过CAS操作修改对象头里锁的状态位标记为轻量级锁
优点
在多线程交替执行同步块的情况下,用CAS进行加锁和解锁而不是直接用重量级锁,避免性能消耗
重量级锁
monitor锁
锁升级自己的一些理解:
偏向锁是一段同步代码一直被一个线程所访问,只需要第一次用CAS存储在Mark Word里存储锁偏向的线程ID,后续就直接判断这个线程ID在不在,不需要再使用CAS了。
轻量级锁就是多线程交替执行同步块的情况下,每次都是用CAS操作尝试将对象的MarkWord更新为指向LockRecord的指针,而不是使用重量级锁阻塞其他线程。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。