线程安全之《Sychronized的八锁案例》
案例体现在一下几点上:
1. 作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
2. 作用于代码块,对括号里配置的对象加锁。
3. 作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
案例一:
public class Dog {public synchronized void eat(){System.out.println("-------------小狗----------吃吃吃");}public synchronized void drink(){System.out.println("--------------小狗---------喝喝喝");}
}public class SychronIzedTest1 {
// sychronized 案例 锁对象public static void main(String[] args) {Dog dog = new Dog();new Thread(dog::eat, "线程1").start();
// 暂停200mstry {TimeUnit.MICROSECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(dog::drink, "线程2").start();}}
执行结果:
-------------小狗----------吃吃吃
--------------小狗---------喝喝喝
案例二:
public class Dog {public synchronized void eat(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------小狗----------吃吃吃");}public synchronized void drink(){System.out.println("--------------小狗---------喝喝喝");}
}
public class SychronIzedTest1 {
// sychronized 案例 锁对象public static void main(String[] args) {Dog dog = new Dog();new Thread(dog::eat, "线程1").start();
// 暂停200mstry {TimeUnit.MICROSECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(dog::drink, "线程2").start();}}
执行结果:
-------------小狗----------吃吃吃
--------------小狗---------喝喝喝
总结案例1和案例二:
案例一和案例二执行结果是一样的,区别是在案例二上的Dog实体上加了
try {TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace(); }
猜测加了睡眠,线程1就可以先睡眠三秒,同时线程2执行drink()方法,所以想象的结果是
--------------小狗---------喝喝喝
-------------小狗----------吃吃吃
实际执行结果是
-------------小狗----------吃吃吃
--------------小狗---------喝喝喝
代码执行流程:
- 线程1调用dog.eat()方法,获取到dog对象的锁
- eat()方法内部执行TimeUnit.SECONDS.sleep(3),但锁并未释放
- 线程1继续执行,输出"-------------小狗----------吃吃吃"
- 线程2在200微秒后启动,尝试调用dog.drink(),但由于线程1仍在执行(还有约2.8秒才能完成),线程2需要等待获取锁。线程2会一直阻塞直到线程1释放锁
重要知识点:
synchronized关键字修饰实例方法时,锁住的是当前实例对象(this)。同一时间只有一个线程可以执行该实例的synchronized方法
案例三:
public class Dog {public synchronized void eat(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------小狗----------吃吃吃");}public synchronized void drink(){System.out.println("--------------小狗---------喝喝喝");}public void jump(){System.out.println("-------------小狗----------跳跳跳");}
}public class SychronIzedTest1 {
// sychronized 案例 锁对象public static void main(String[] args) {Dog dog = new Dog();new Thread(dog::eat, "线程1").start();
// 暂停200mstry {TimeUnit.MICROSECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(dog::jump, "线程2").start();}}
区别:
增加了一个普通方法: public void jump(){System.out.println("-------------小狗----------跳跳跳"); }
执行结果:
-------------小狗----------跳跳跳
-------------小狗----------吃吃吃
得出结论:
普通方法与同步锁无关
案例四:
public class Dog {public synchronized void eat(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------小狗----------吃吃吃");}public synchronized void drink(){System.out.println("--------------小狗---------喝喝喝");}public void jump(){System.out.println("-------------小狗----------跳跳跳");}
}public class SychronIzedTest1 {
// sychronized 案例 锁对象public static void main(String[] args) {Dog dog1 = new Dog();Dog dog2 = new Dog();new Thread(dog1::eat, "线程1").start();
// 暂停200mstry {TimeUnit.MICROSECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(dog2::drink, "线程2").start();}}
区别:
从一只狗变成两只狗,并将两只狗分别两个线程执行,以前是一只狗两个线程。
执行结果:
--------------小狗---------喝喝喝
-------------小狗----------吃吃吃
得出结论:
由于使用的是不同的对象实例,两个线程实际上不会产生竞争,因为它们锁定的是不同的对象。不是一把锁了互不影响。
案例五:
public class Dog {public static synchronized void eat(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------小狗----------吃吃吃");}public static synchronized void drink(){System.out.println("--------------小狗---------喝喝喝");}public void jump(){System.out.println("-------------小狗----------跳跳跳");}
}public class SychronIzedTest1 {
// sychronized 案例 锁对象public static void main(String[] args) {
// Dog dog1 = new Dog();
// Dog dog2 = new Dog();new Thread(Dog::eat, "线程1").start();
// 暂停200mstry {TimeUnit.MICROSECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(Dog::drink, "线程2").start();}}
区别:
两个静态实例方法,一个dog对象
执行结果:
-------------小狗----------吃吃吃
--------------小狗---------喝喝喝
案例六:
public class Dog {public static synchronized void eat(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------小狗----------吃吃吃");}public static synchronized void drink(){System.out.println("--------------小狗---------喝喝喝");}public void jump(){System.out.println("-------------小狗----------跳跳跳");}
}public class SychronIzedTest1 {
// sychronized 案例 锁对象public static void main(String[] args) {Dog dog1 = new Dog();Dog dog2 = new Dog();new Thread(()->dog1.eat(), "线程1").start();
// 暂停200mstry {TimeUnit.MICROSECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->dog2.drink(), "线程2").start();}}
区别:
两个静态实例方法,两个dog对象
执行结果:
-------------小狗----------吃吃吃
--------------小狗---------喝喝喝
总结案例五和案例六:
对于静态同步方法,锁定的是当前class类
案例七:
public class Dog {public static synchronized void eat(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------小狗----------吃吃吃");}public synchronized void drink(){System.out.println("--------------小狗---------喝喝喝");}public void jump(){System.out.println("-------------小狗----------跳跳跳");}
}public class SychronIzedTest1 {
// sychronized 案例 锁对象public static void main(String[] args) {Dog dog1 = new Dog();new Thread(()->dog1.eat(), "线程1").start();
// 暂停200mstry {TimeUnit.MICROSECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->dog1.drink(), "线程2").start();}}
区别:
一个普通同步方法,一个静态同步方法,一个dog对象
执行结果:
--------------小狗---------喝喝喝
-------------小狗----------吃吃吃
案例八:
public class Dog {public static synchronized void eat(){try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("-------------小狗----------吃吃吃");}public synchronized void drink(){System.out.println("--------------小狗---------喝喝喝");}public void jump(){System.out.println("-------------小狗----------跳跳跳");}
}public class SychronIzedTest1 {
// sychronized 案例 锁对象public static void main(String[] args) {Dog dog1 = new Dog();Dog dog2 = new Dog();new Thread(()->dog1.eat(), "线程1").start();
// 暂停200mstry {TimeUnit.MICROSECONDS.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->dog2.drink(), "线程2").start();}}
区别:
一个普通同步方法,一个静态同步方法,两个对象
执行结果:
--------------小狗---------喝喝喝
-------------小狗----------吃吃吃
总结案例七和案例八:
多线程下普通同步方法和静态同步方法互不影响。
1.锁对象不同
普通同步方法(如 drink()):锁定的是实例对象,即调用该方法的具体对象实例
静态同步方法(如 eat()):锁定的是类对象,即该类的 Class 对象
2.互不影响的原因
由于锁的对象不同,普通同步方法和静态同步方法确实互不影响
多个线程可以同时执行同一类中的普通同步方法和静态同步方法
总结:
- 对于普通同步方法,锁的是当前实例对象,通常指this,所有的普通同步方法用的 都是同一把锁——实例对象本身
- 对于静态同步方法,锁的是当前类的Class对象
- 对于同步方法块,锁的是 synchronized 括号内的对象