羡慕案例中使用sleep方法制造延迟来测试。
学习目标: 什么是锁?锁是什么?如何判断锁的是谁?
1、一个对象,俩个同步方法
2、一个对象,俩个同步方法,一个方法延迟
3、两个对象,两个同步方法
4、一个对象,一个同步,一个普通方法
5、一个对象,俩个静态同步方法
6、两个对象,俩个静态同步方法
7、一个对象,一个静态的同步方法,一个同步方法
8、两个对象,一个静态的同步方法,一个同步方法
现象演示
两个线程调用Phone执行Phone下的发短信和打电话方法
1、标准情况下,两个线程先执行发短信,再打电话。
结果:发短信先执行
import java.util.concurrent.TimeUnit;
/**
* 八锁,就是关于锁的八个问题
* 1、标准情况下,两个线程先执行发短信,再打电话
*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
// 线程A发短信
new Thread(()->{phone.sendSms();},"A").start();
// 休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程B打电话
new Thread(()->{phone.call();},"B").start();
}
}
class Phone{
// 发短信
public synchronized void sendSms(){
System.out.println("发短信");
}
// 打电话
public synchronized void call(){
System.out.println("打电话");
}
}
现象演示
2、sendSms方法延时4秒,仍然是先执行发短信,再打电话。所以我们排除执行顺序是按照线程创建的先后顺序
结果:发短信先执行
import java.util.concurrent.TimeUnit;
/**
* 八锁,就是关于锁的八个问题
* 2、sendSms方法延时4秒,仍然是先执行发短信,再打电话
*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
// 线程A发短信
new Thread(()->{phone.sendSms();},"A").start();
// 休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程B打电话
new Thread(()->{phone.call();},"B").start();
}
}
class Phone{
// 发短信
public synchronized void sendSms(){
// 休息四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话
public synchronized void call(){
System.out.println("打电话");
}
}
被synchronized修饰的方法,锁的对象是方法的调用者,此方法中均是对象Phone来调用的,即两个方法调用的对象是同一个、使用的同一把锁,谁先拿到就谁先执行。
现象:phone、phone1两个不同对象在两个线程A、B里分别调用发短信、打电话,其中发短信方法有4秒休眠。
结果:打电话先执行
import java.util.concurrent.TimeUnit;
/**
也可以使用Phone phone = new Phone();Phone phone2 = new Phone();phone在线程A中调用发短信,phone2在线程B中调用打电话,这里锁的是两个不同的调用者,所以他们是两把锁,所以互不影响,因为发短信中有4秒的线程休眠,故而一定是打电话被先调用
*/
public class Test2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
Phone2 phone1 = new Phone2();
/**
* 被synchronized 修饰的方式和普通方法 先执行sendSms() 还是 hello()
* 答案: hello()
* 解释:新增加的这个方法没有 synchronized 修饰,不是同步方法,不受锁的影响!
*/
// 线程A调用phone发短信
new Thread(()->{phone.sendSms();},"A").start();
// 休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程B使用phone1调用打电话
new Thread(()->{phone1.call();},"B").start();
}
}
class Phone2{
// 发短信
public synchronized void sendSms(){
// 休息四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话
public synchronized void call(){
System.out.println("打电话");
}
}
使用Phone phone = new Phone();Phone phone2 = new Phone();phone在线程A中调用发短信,phone2在线程B中调用打电话,这里锁的是两个不同的调用者,所以他们是两把锁,所以互不影响,因为发短信中有4秒的线程休眠,故而一定是打电话被先调用
现象:phone对象在线程A中调用被synchronized修饰的发短信方法 和 在线程B中调用未被synchronized修饰的普通方法hello
结果:hello先执行
import java.util.concurrent.TimeUnit;
/**
* 被synchronized 修饰的方法和普通方法 先执行sendSms() 还是 hello()
* 答案: hello()
* 解释:新增加的这个方法没有 synchronized 修饰,不是同步方法,不受锁的影响!
*/
public class Test2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
// 线程A发短信
new Thread(()->{phone.sendSms();},"A").start();
// 休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程B调用未被synchronized修饰的方法hello()
new Thread(()->{phone.hello();},"B").start();
}
}
class Phone2{
// 发短信
public synchronized void sendSms(){
// 休息四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话
public synchronized void call(){
System.out.println("打电话");
}
// say hello 没有被锁 也就是说不是同步方法、不受锁的影响,线程会直接把它来执行
public void hello(){
System.out.println("hello");
}
}
被synchronized 修饰的方法和普通方法 先执行sendSms() 还是 hello()?
答案是hello()解释:新增加的这个方法没有 synchronized 修饰,不是同步方法,不受锁的影响!
现象:Phone3在线程A 和 线程B中分别调用Phone3的被synchronized和static修饰的静态同步方法 发短信 和 打电话。
结果:发信息先调用
import java.util.concurrent.TimeUnit;
/**
* 将两个同步方法设置为静态
*/
public class Test3 {
public static void main(String[] args) {
// 线程A发短信
new Thread(()->{Phone3.sendSms();},"A").start();
// 休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程B打电话
new Thread(()->{Phone3.call();},"B").start();
}
}
class Phone3{
/**
* 在此处我们将 两个同步方法均设为静态方法,此两个方法在类加载是被加载
* 所以,在此2个同步方法加载时,它们被全局唯一的Phone3.class对象加载,因为此Phone3.class全局唯一,故而他们被同一个对象加载
* 所以此时仍然是发短信先输出
*/
// 发短信
public static synchronized void sendSms(){
// 休息四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话
public static synchronized void call(){
System.out.println("打电话");
}
}
只要方法被 static 修饰,锁的对象就是 Class模板对象,这个则全局唯一!所以说这里是同一个锁,并不是因为synchronized 这里程序会从上往下依次执行
现象:phone、phone1两个对象来分别调用两个静态同步方法,哪个先执行?
结论:发短信先执行
import java.util.concurrent.TimeUnit;
/**
*/
public class Test3 {
public static void main(String[] args) {
// 因为sendSms和call方法均是静态方法,故而Phone3.sendSms()和new Phone3().sendSms()没有区别
Phone3 phone = new Phone3();
Phone3 phone1 = new Phone3();
// 线程A发短信
new Thread(()->{phone.sendSms();},"A").start();
// 休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程B打电话
new Thread(()->{phone1.call();},"B").start();
}
}
class Phone3{
// 发短信
public static synchronized void sendSms(){
// 休息四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话
public static synchronized void call(){
System.out.println("打电话");
}
}
/**
* 同被static+synchronized 修饰的两个方法,是先发短信还是先打电话()?
* 答案:发短信
* 解释:只要方法被 static 修饰,锁的对象就是 Class模板对象,这个则全局唯一,class模板只有一个,即使是phone和phone1不是同以次实例化的对象,但是无论 phone.静态方法() 还是 phone1.静态方法() 它们均相当于Phone.静态方法()
* 所以说这里是同一个锁,并不是因为synchronized导致。
*/
现象:被synchronized修饰的普通方法和被synchronized静态方法是先发短信还是先打电话?
结论:先打电话
import java.util.concurrent.TimeUnit;
/**
*/
public class Test3 {
public static void main(String[] args) {
// 因为sendSms和call方法均是静态方法,故而Phone3.sendSms()和new Phone3().sendSms()没有区别
Phone3 phone = new Phone3();
// 线程A发短信
new Thread(()->{phone.sendSms();},"A").start();
// 休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程B打电话
new Thread(()->{phone.call();},"B").start();
}
}
class Phone3{
// 发短信
public static synchronized void sendSms(){
// 休息四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话
public synchronized void call(){
System.out.println("打电话");
}
}
/**
* 被synchronized修饰的普通方法和被synchronized静态方法是先发短信还是先打电话?
* 答案:打电话
* 解释:只要被static修饰锁的是class模板, 而synchronized 锁的是调用的对象
* 这里是两个锁互不影响,按时间先后执行
*/
现象:phone、phone1两个对象来分别调用被static+synchronized修饰的静态同步方法发短信 和 被synchronized修饰的普通方法打电话,哪个先执行?
结论:随机执行
import java.util.concurrent.TimeUnit;
/**
*/
public class Test3 {
public static void main(String[] args) {
// 因为sendSms和call方法均是静态方法,故而Phone3.sendSms()和new Phone3().sendSms()没有区别
Phone3 phone = new Phone3();
Phone3 phone1 = new Phone3();
// 线程A发短信
new Thread(()->{phone.sendSms();},"A").start();
// 休息一秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程B打电话
new Thread(()->{phone1.call();},"B").start();
}
}
class Phone3{
// 发短信
public static synchronized void sendSms(){
// 休息四秒
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 打电话
public synchronized void call(){
System.out.println("打电话");
}
}
phone、phone1两个对象来分别调用被static+synchronized修饰的静态同步方法发短信 和 被synchronized修饰的普通方法打电话,先执行发短信还是打电话?
答案:打电话
解释: 只要被static 修饰的锁的就是整个class模板
这里一个锁的是class模板 一个锁的是调用者
所以锁的是两个对象,互不影响,按CPU调度顺序执行
八锁总结
synchronized(Demo.class){ } 和 synchronized(this){ }
- 1
- 2
- 3
- 4
- 5
1、new this 调用的是这个对象,是一个具体的对象!
2、static class 唯一的一个模板,即Class对象!synchronized 锁的对象是方法的调用者!
- 普通同步方法的调用者是类的实例(实例化对象)
- 静态同步方法的调用者是类的对象(class对象)
在我们编写多线程程序得时候,只需要搞明白这个到底锁的是什么就不会出错了!