线程同步的基础概念:
同步 指的是调用一个方法时,在没有得到结果之前,这个调用就不返回。线程同步的意思与之类似,但线程同步并不是说让一个线程执行完了再执行其它线程,一般是指让线程中的某一些操作进行同步就可以。
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全主要是指多个线程对同一个对象中的同一个实例变量进行操作会出现值被更改、值不同步的情况,进而影响程序的执行流程。
使用synchronized同步:
1.对象锁:
对象锁,当不同线程同时调用同一个对象的普通方法或者代码块时会进行同步,最先拥有该对象锁的线程会先执行,其他线程阻塞等待锁。
public class SynchronizeMethod {
public synchronized void method() {
System.out.println("synchronize 同步普通方法");
}
public void method() {
synchronized(this) {
System.out.println("synchronize 同步代码块");
}
}
}
2.类锁:
针对于类级别的锁,当不同线程同时调用该类的所有对象的同步方法或者代码块时会进行同步,最先拥有该对象锁的线程会先执行,其他线程阻塞等待锁。
public class SynchronizeMethod {
public synchronized static void method() {
System.out.println("synchronize 同步静态方法");
}
public void method() {
synchronized(SynchronizeMethod.class) {
System.out.println("synchronize 同步类");
}
}
}
2.底层实现:synchronized底层是通过一个对象监视器锁(monitor)来实现的。
2.1 synchronized针对代码块加锁时,代码块编译后的字节码前后会出现monitorenter、monitorexit指令。
monitorenter:每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
monitorexit:线程必须是object所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
2.2 synchronized针对方法加锁时,方法编译会出现一个ACC_SYNCHRONIZED标志符。
JVM就是根据该标识符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。
结束语
本文介绍synchronized关键字的用法,以及相关底层原理,通俗易懂,如有不到之处方请指正。