synchronized
功能
可用于修饰方法、代码块,用于防止线程干扰和内存一致性,保证同一时间只有一个线程执行该段代码。
synchronized锁的属性
1、可重入性
自己可以获得自己的内部锁。已经获得对象锁\类锁的线程,可以调用被锁对象的任何方法、成员。
也支持在父子类继承的环境中。
ReentrantLock.class 也具有可重复性,但不能自动加、解锁。
2、不可中断性
JVM自动控制线程状态的,不可像Lock.class一样,没有提供进行线程中断的方法。
synchronized锁不可控制自己的加解锁行为。Lock.class 可以利用方法lock\unlock,自定义锁行为
只有被同步代码执行完毕、或者执行过程中抛出异常(需要抛出),jvm会自动释放锁。
汇编语句通过monitorenter、monitorexit语句,利用 monitor命令 可以控制到synchronized锁的生存周期。
注意monitorenter、monitorexit语句不一定一一对应,应该可能同步代码会抛出异常的情况,此时不会exit.
线程资源(加锁对象)的状态 决定了 线程的状态。
wait/notify方法定义在Object类,没有定义在Thread类上。因为线程的状态是由对象资源决定的,加锁的对象类型都属于Object实例或Object Class。所以应该通过调用锁对象的阻塞、唤醒方法控制锁对象的状态,来影响待获取锁对象的线程的阻塞、唤醒状态
对象状态、锁状态、线程状态
3、同步方法不可继承
子类重写父类的同步方法syncMethod时,当子类调用super.syncMethod(),不会进行同步。
注意点
1、加锁解锁的对象
加锁操作实际上是获取 待加锁对象的monitor,解锁的对象是加锁对象。
同步代码块只对括号中的对象尝试加锁,可以是实例对象,也可以是Class对象。
同步方法只对调用本方法的对象进行加锁。若为非静态方法,则对this实例对象加锁;若同步方法为静态方法,则对定义了该方法的Class对象进行加锁。
2、同步代码或方法执行结束的两种方式
正常结束:所有代码依次成功执行;
异常结束:执行向代码块外抛出异常。
3、无论如何结束,都会在结束时释放同步锁
两种实现形式:代码块与方法
1、代码块
1.1、形式
synchronized(obj){
//statement
}
synchronized(Some.Class){
//statement
}
1.2、执行过程
A.加锁
执行到该代码块时,首先尝试获取待加锁对象的monitor,对该对象monitor加锁;
若该对象monitor已经被其他线程加锁,则本线程进入同步阻塞状态,等待获取锁;否则直接获取锁,进入到下一步。
注意:由于同步锁的可重入性,因此可以在本线程中,不断获取已经被本线程获取的锁
B.执行被synchronized修饰的代码块
若加锁操作完成,则开始执行同步代码块。
C.解锁
当加锁、代码块依次都执行结束后(代码块执行结束可以是正常也可以是异常结束,即所有代码依次成功执行、或是执行异常导致线程退出代码块),则synchronized同步锁会自动进行解锁操作,释放在对象monitor上的锁。
2、方法
2.1、形式
synchronized void anyReturnTypeMethodOne(){
//statement
}
static synchronized void anyReturnTypeMethodTwo(){
//statement
}
2.2、执行过程
A.加锁
当方法被调用时,进行加锁;
若不可获取锁,则进入同步阻塞状态。
B.执行被synchronized修饰的方法
若加锁操作完成,则开始执行方法定义体中的代码。
C.解锁
当加锁、方法体依次都执行结束后,则synchronized锁会自动进行解锁操作,释放在对象monitor上的锁。
两种用法:类锁与对象锁
1、类锁
锁对象是类class对象 —— 不允许两个或以上的线程持有同一个类锁,当一个线程获取了类锁后(且还未释放),存在其他线程对该类的所有获取静态变量和执行静态方法的操作都会进入阻塞状态,因为这些操作都需要通过类对象来执行,所以只有可获取类对象时(处于非锁定状态时),操作可以被执行。
两种实现形式:
A.静态的同步方法:使用static 修饰的 synchronized method
static synchronized void methodOne(){...}
B.方法中的代码块的锁对象为class类
static void methodOne{
synchronized(MyClass.class){...}
}
2、对象锁
锁对象是对象实例 —— 不允许两个或以上的线程持有同一个对象锁,当一个线程获取了对象锁后(且还未释放),存在其他线程对该对象的所有获取实例成员变量和执行非静态方法的操作都会进入阻塞状态,因为这些操作都需要通过实例对象来执行,所以只有可获取实例对象时(处于非锁定状态时),操作可以被执行。
两种实现形式:
A.非静态的同步方法
synchronized void methodOne(){...}
B.非静态方法中的代码块的锁对象为对象实例
void methodOne{
synchronized(this){...}
}
3、测试代码
对于可实例化类AClass,同时定义了静态同步方法staticMethodA,和非静态同步方法unstaticMethodA,以及包含需要获取类锁的同步代码块的方法staticMethodB,和包含需要获取对象锁的同步代码块的方法unstaticMethodB.定义如下.
package synchronize;
/**
* @author zhangjie
*/
class AClass {
static Object staticObj = new Object();
private Object obj = new Object();
private void normal() {
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "开始退出");
}
static synchronized void staticMethodA() {
System.out.println("这是静态同步方法,需要获取类锁");
try {
TimeUnit.SECONDS.sleep(2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("退出静态同步方法");
}
void staticMethodB() {
synchronized (AClass.class) {
System.out.println("这是普通方法,包含了需要获取类锁的同步代码块,需要获取类锁");
}
}
synchronized void unstaticMethodA() {
System.out.println("这是非静态同步方法,需要获取对象锁");
}
void unstaticMethodB() {
synchronized (this) {
System.out.println("这是普通方法,包含了需要获取对象锁的同步代码块,需要获取对象锁");
}
}
void unstaticMethodC() {
synchronized (obj) {//同步代码块获取对象锁的另一种方法
System.out.println("这是普通方法,包含了需要获取对象锁的同步代码块,需要获取对象锁");
}
}
public void lockNewObject() {
/**
* 锁对象为新建的对象,所以lockNewObject()执行的每次锁对象都不同
* 所以不会造成锁互斥,可以并行执行
*/
synchronized (gainLock()) {
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 作为synchronized参数,不可返回void类型
* 因为synchronized 必须获取对象锁
*
* @return
*/
private Object gainLock() {
return new Object();
}
}
结论:
A.对象锁与类锁不同,所以可以同时访问静态同步方法和非静态同步方法,或者同时访问非静态方法的非静态同步代码块和静态方法的静态同步代码块;
B.不同的对象的对象锁不同,所以可以通过不同对象执行相同的同步方法;
C.同步方法都需要获取对象锁\类锁,所以一个对象一次只允许执行一个同步方法(一个对象的多个同步方法不能同时执行);
D.需要获取对象锁\类锁的同步代码块,执行期间,不允许同时进行需要获取相同对象的操作的进行;
E.只要没有上锁,可以同步执行:一个对象的多个非静态非同步方法、一个类的多个静态非同步方法。
//验证E
public static void main(String[] args) {
AClass a = new AClass();
Thread one = new Thread(() -> a.normal());
Thread two = new Thread(() -> a.normal());
one.start();
two.start();
}
//验证C\D:类锁的互斥性
public static void main(String[] args) {
AClass.staticMethodA();
AClass.staticMethodAA();
System.out.println(AClass.staticObj);
}
//验证D:不同对象锁不互斥
public static void main(String[] args) {
AClass a = new AClass();
Thread one = new Thread(() -> a.lockNewObject());
Thread two = new Thread(() -> a.lockNewObject());
one.start();
two.start();
}