一、加synchronized的两种方法
1. 加在普通方法上
加在普通方法上本质上是synchronized(this),是对调用本方法的对象上锁,即理解为:调用obj.test()方法时,只有拿到obj这个对象的对象锁,才可以执行test方法里的代码,否则就会阻塞在当前位置;
public class Synchronized {public synchronized void test(){}}
等价于:
public class Synchronized {public void test(){synchronized (this){}}}
2. 加在静态方法上
synchronized加在静态方法上,相当于对整个类的对象上锁,即当通过”类名.test()”调用方法时,会先看当前线程是否持有”类名.class”对象的对象锁,如果持有才可以进一步访问方法内的代码,否则就会阻塞到当前位置。
public class Synchronized {public synchronized static void test(){}}
等价于:
public class Synchronized {public static void test(){synchronized (Synchronized.class){}}}
二、不加synchronized的方法
不加synchronized的方法就好比不遵守规则的人,不去老实排队,就会胡乱访问临界资源,造成临界资源被随意篡改,不能保证原子性。
三、对象锁类设计总结
- 将共享资源作为该对象锁类的成员变量;
 - 访问该共享资源成员变量的方法都要加上synchronized块;
 - 创建对象锁类对象,在不同线程中通过该对象调用方法以实现分割对临界资源的访问;
 
四、线程八锁
synchronized作用在方法上的习题,其实是考察synchronized锁住的是哪个对象
情况1:
public class Test8Blocks {public static void main(String[] args) {Number n1 =new Number();//因为是在方法上的synchronized,所以是在锁n1对象;new Thread(()->{System.out.println("begin");n1.a();}).start();new Thread(()->{System.out.println("begin");n1.b();}).start();}}class Number{public synchronized void a(){System.out.println("1");}public synchronized void b(){System.out.println("2");}}
线程1与线程2启动后,比如线程1先执行到n1.a()方法,那么线程1则会占用该n1对象的对象锁,所以当线程2执行到n1.b()时则会阻塞住,当n1.a()方法执行完毕后,锁释放,线程2的b方法才可以继续执行。
情况2:
public class Test8Blocks {public static void main(String[] args) {Number n1 =new Number();//因为是在方法上的synchronized,所以是在锁n1对象;new Thread(()->{System.out.println("begin");n1.a();}).start();new Thread(()->{System.out.println("begin");n1.b();}).start();}}class Number{public synchronized void a(){Thread.sleep(1);System.out.println("1");}public synchronized void b(){System.out.println("2");}}
同情况1类似,共有两种执行情况:
 1)1秒后,打印1和2
 2)先打印2,然后1秒后打印1
注意sleep并不会释放锁,线程执行完毕才会释放锁。
情况3:
public class Test8Blocks {public static void main(String[] args) {Number n1 =new Number();//因为是在方法上的synchronized,所以是在锁n1对象;new Thread(()->{System.out.println("begin");n1.a();}).start();new Thread(()->{System.out.println("begin");n1.b();}).start();new Thread(()->{System.out.println("begin");n1.c();}).start();}}class Number{public synchronized void a(){System.out.println("1");}public synchronized void b(){System.out.println("2");}public void c(){System.out.println("3");}}
加入没有synchronized的c方法,当n1.c()调用的时候则不需要考虑是否互斥,因为只有方法上加了synchronized才会在执行的时候考虑当前线程是否拿到了n1的对象锁,而不加synchronized的方法只是正常方法,随意调用。
情况4:
public class Test8Blocks {public static void main(String[] args) {Number n1 =new Number();Number n2 = new Number();//因为是在方法上的synchronized,所以是在锁n1对象;new Thread(()->{System.out.println("begin");try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}}).start();new Thread(()->{System.out.println("begin");n2.b();}).start();}}class Number{public synchronized void a() throws InterruptedException {Thread.sleep(1);System.out.println("1");}public synchronized void b(){System.out.println("2");}}
因为拥有两个对象n1和n2,所以实际上两个线程互不干扰,因为不会存在两个线程抢占一把锁的情况,线程1独占n1对象锁,线程2独占n2对象锁。注意只有多个线程共用同一把对象锁,才会达到互斥的目的,因为调用方法时会考虑当前线程是否拿到当前对象的锁。
情况5:
public class Test8Blocks {public static void main(String[] args) {Number n1 =new Number();Number n2 = new Number();//因为是在方法上的synchronized,所以是在锁n1对象;new Thread(()->{System.out.println("begin");try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}}).start();new Thread(()->{System.out.println("begin");n2.b();}).start();}}class Number{public static synchronized void a() throws InterruptedException {Thread.sleep(1);System.out.println("1");}public synchronized void b(){System.out.println("2");}}
注意改动点是:a方法变成static synchronized方法,故调用该方法时会查看当前线程是否拥有“类对象锁”,而调用b方法还是查看当前线程是否拥有“实例n1对象锁”
所以实际上两个线程是拥有两个不同的对象锁,互不干扰。
情况6:
public class Test8Blocks {public static void main(String[] args) {Number n1 =new Number();Number n2 = new Number();//因为是在方法上的synchronized,所以是在锁n1对象;new Thread(()->{System.out.println("begin");try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}}).start();new Thread(()->{System.out.println("begin");n2.b();}).start();}}class Number{public static synchronized void a() throws InterruptedException {Thread.sleep(1);System.out.println("1");}public static synchronized void b(){System.out.println("2");}}
较情况5的改动是,将b方法也变成了static synchronized方法,则线程1和线程2会抢占”类对象锁”,会出现两线程互斥的情况,达到了控制资源访问的目的。
情况7:
public class Test8Blocks {public static void main(String[] args) {Number n1 =new Number();Number n2 = new Number();//因为是在方法上的synchronized,所以是在锁n1对象;new Thread(()->{System.out.println("begin");try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}}).start();new Thread(()->{System.out.println("begin");n2.b();}).start();}}class Number{public static synchronized void a() throws InterruptedException {Thread.sleep(1);System.out.println("1");}public synchronized void b(){System.out.println("2");}}
创建了两个对象,n1和n2,n1.a()实际上锁住的是类对象,n2.b()锁住的是n2对象,所以两个线程仍然是锁住的不同对象,故可以看出两个线程仍然不互斥。
情况8:
public class Test8Blocks {public static void main(String[] args) {Number n1 =new Number();Number n2 = new Number();//因为是在方法上的synchronized,所以是在锁n1对象;new Thread(()->{System.out.println("begin");try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}}).start();new Thread(()->{System.out.println("begin");n2.b();}).start();}}class Number{public static synchronized void a() throws InterruptedException {Thread.sleep(1);System.out.println("1");}public static synchronized void b(){System.out.println("2");}}
两个方法都经static synchronized修饰,故调用方法时n1.a()和n2.b()锁住的是同一个类对象Number.CLASS,所以即使是两个对象在访问,仍然可以实现互斥。
