一、对象方法锁
1、成员方法加锁
同一个对象成员方法有3个synchronized修饰的方法,通过睡眠模拟业务操作
public class CaseOne {
public synchronized void m1(){
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace();}
System.out.println(SDF.format(new Date())+"------------>m1");
}
public synchronized void m2() {
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace();}
System.out.println(SDF.format(new Date())+"------------>m2");
}
public synchronized void m3() {
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace();}
System.out.println(SDF.format(new Date())+"------------>m3");
}
}
private final static SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private final static ExecutorService POOL = Executors.newFixedThreadPool(4);
public static void main(String[] args) {
CaseOne caseOne = new CaseOne();
System.out.println(SDF.format(new Date())+"------------>start");
POOL.execute(caseOne::m1);
POOL.execute(caseOne::m2);
POOL.execute(caseOne::m3);
}
运行结果:
通过运行结果可以看出,三个业务方法,执行完成总共花费了6s,虽然使用了多线程,这三三个方法其实是串行作业的,因此可得出一下结论:
同一个对象的不同方法加synchronized修饰,只要其中一个方法抢到锁,其他被synchronized修饰的方法都互斥,本质是对象上锁。
2、成员方法不加锁
public class CaseTwo {
public synchronized void m1(){
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace();}
System.out.println(SDF.format(new Date())+"------------>m1");
}
public void m2() {
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace();}
System.out.println(SDF.format(new Date())+"------------>m2");
}
public synchronized void m3() {
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace();}
System.out.println(SDF.format(new Date())+"------------>m3");
}
}
CaseTwo caseTwo = new CaseTwo();
System.out.println(SDF.format(new Date())+"------------>start");
POOL.execute(caseTwo::m1);
POOL.execute(caseTwo::m2);
POOL.execute(caseTwo::m3);
运行结果:
三个业务方法执行完成总共花费了4s,结合第一个案例可以得出以下结论:
同一个对象的不同方法加synchronized,其中加锁方法和不加锁的成员方法没有竞争关系,不产生互斥。
3、不同对象方法锁
案例1和案例2研究的是同一个的对象,加锁和不加锁的情况,下面来研究不同对象的情况。
public static class CaseThree {
private final String name;
public CaseThree(String name) {
this.name = name;
}
public synchronized void m1(){
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace();}
System.out.println(SDF.format(new Date())+ " "+this.name +"------------>m1");
}
}
CaseThree caseThree1 = new CaseThree("caseThree1");
CaseThree caseThree2 = new CaseThree("caseThree2");
System.out.println(SDF.format(new Date())+" "+"------------>start");
POOL.execute(caseThree1::m1);
POOL.execute(caseThree2::m1);
运行结果:
由执行结果可以看出,对象caseThree1 和对象caseThree12调用同一个方法,同时执行完成,可以得出以下结论:
不同的对象执行同一个成员方法,没有竞争关系,加锁方法不互斥。
二、类方法加锁
下面研究一下一个类型static修饰的静态方法加锁
1、静态方法锁
public static class CaseFour {
public static synchronized void m1(){
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace();}
System.out.println(SDF.format(new Date())+ " "+"------------>m1");
}
public static synchronized void m2(){
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace();}
System.out.println(SDF.format(new Date())+ " "+"------------>m2");
}
}
System.out.println(SDF.format(new Date())+" "+"------------>start");
POOL.execute(CaseFour::m1);
POOL.execute(CaseFour::m2);
由执行的结果可以得出以下结论:
同一个类的不同静态方法之间存在竞争关系,先抢到锁的先执行。
2、不同对象静态方法锁
public static class CaseFive {
public static synchronized void m1(String name){
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace();}
System.out.println(SDF.format(new Date())+ " "+name+"------------>m1");
}
}
CaseFive caseFive1 = new CaseFive();
CaseFive caseFive2 = new CaseFive();
System.out.println(SDF.format(new Date())+" "+"------------>start");
POOL.execute(()->caseFive1.m1("caseFive1"));
POOL.execute(()->caseFive2.m1("caseFive2"));
由运行结果可以得到以下结论:
同一个类的不同对象调用同一个静态方法,存在竞争关系,会产生互斥。
3、类方法锁和成员方法锁
◆场景一:
public static class CaseSix {
public static synchronized void m1(){
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace();}
System.out.println(SDF.format(new Date())+ " "+"------------>m1");
}
public synchronized void m2(){
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace();}
System.out.println(SDF.format(new Date())+ " "+"------------>m2");
}
}
CaseSix caseSix = new CaseSix();
System.out.println(SDF.format(new Date())+" "+"------------>start");
POOL.execute(()->caseSix.m1());
POOL.execute(()->caseSix.m2());
由执行结果可得出以下结论:
同一个类的静态方法和成员方法加锁,同一个对象同时调用,会场生竞争关系,先抢到锁的先执行。
◆场景二:
CaseSix caseSix1 = new CaseSix();
CaseSix caseSix2 = new CaseSix();
System.out.println(SDF.format(new Date())+" "+"------------>start");
POOL.execute(()->caseSix1.m1());
POOL.execute(()->caseSix2.m2());
由运行结果可知:
同一个类的不同对象,分别调用静态类和成员方法,不产生竞态关系。
三、线程锁总结
通过查看反编译字节码(javap -v CaseOne.class),可以看到synchronized加锁机制是通过monitorenter加锁,通过
monitorexit自动释放锁。
对象普通成员方法是通过ACC_SYNCHRONIZED标记加锁
类方法加锁是通过, ACC_STATIC, ACC_SYNCHRONIZED进行标记的
类方法是在类加载过程中已经打上标记了,类信息存储在jvm的常量池中,而对象的方法锁是在运行动态确定的,因此类方法锁和不同的对象成员方法锁之间不存在竞争关系。在并发情景,能用无锁的数据块就不要用锁,能锁区块,就不要锁整个方法体,能锁对象就不要用类锁,代码块锁、方法锁、类锁的锁粒度是主次增大的。