文章目录
前言
最近在看狂神老师的JUC课程时遇到了八锁问题,看的时候懵懵懂懂,于是在第二天又看了一遍,并记录了本次笔记,希望对大家有所帮助。时间匆忙的话一定要看文章最后的总结部分!
一、标准情况下,两个线程谁先打印?
public class Lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
},"A").start();
TimeUnit.SECONDS.sleep(2);
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone extends Thread {
public synchronized void sendSms(){
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
答案
是先发短信,一秒后打电话。原因是sendSms先调用吗?
显然不是的。我们让sleep()睡4s看看。
二、sendSms延迟4s,谁先打印?
public class Lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSms();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone extends Thread {
public synchronized void sendSms() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
这时还是先发短信再打电话,为什么呢?
分析
通过一、二我们可以知道,锁的存在影响了结果,一、二中锁的对象是方法的调用者,由于sendSms()和call()的调用者是同一个phone,故谁先拿到锁谁先执行。(一次只能有一个线程访问该类的方法)
三、添加一个普通方法,谁先打印?
这里把调用call()改成调用hello()
public class Lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSms();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.hello();
},"B").start();
}
}
class Phone extends Thread {
public synchronized void sendSms() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("hello");
}
}
答案
hello先调用,sendSms后调用
分析
hello()没有synchronized关键字修饰,故不参与锁的进程,无需参与锁的等待。它会随着线程的启动立刻执行。
四、两个对象两种方法,谁先打印?
phone1调用sendSms(),phone2调用call()
public class Lock8 {
public static void main(String[] args) throws InterruptedException {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
try {
phone1.sendSms();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone{
public synchronized void sendSms() throws InterruptedException {
TimeUnit.SECONDS.sleep(4);
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
答案
先打电话后发短信
分析
两个对象->两个调用者->两把锁
因此两个线程互不干扰,谁睡眠得少谁先打印。
五、增加两个静态的同步方法,只有一个对象,谁先打印?
sendSms()和call()都用static修饰
public class Lock8 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
Phone.call();
}, "B").start();
}
}
class Phone {
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一加载就存在了,锁的是Class模板,而Phone只有唯一一个Class模板,二者用的是同一把锁。
六、两个对象,两个静态同步方法,谁先打印?
public class Lock8 {
public static void main(String[] args) {
Phone phone1 = new Phone();
Phone phone2=new Phone();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone {
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是类锁,而两个对象出自同一个类,两个对象的本质是一样的,用的是同一把锁。
七、一个对象,一个同步方法,一个静态同步方法,谁先打印?
sendSms()是静态同步方法,call()是同步方法
public class Lock8 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone {
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("打电话");
}
}
答案
先打电话后发短信
分析
sendSms()是静态同步方法,锁的是Class模板;call()是同步方法,锁的是调用者。因此这里是双线程。
八、两个对象,一个同步方法,一个静态同步方法,谁先打印?
public class Lock8 {
public static void main(String[] args) {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone1.sendSms();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone {
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 静态同步方法使用的锁是类锁(Xxxx.class),非静态同步方法使用的是对象锁(this)
- 多个线程中同一对象的非静态同步方法使用的是同一把对象锁,同一个类的静态方法使用的是同一把类锁
- 类锁和对象锁相互无效
- 锁对普通方法无效
- 判断多个线程是否使用的同一把锁,若是同一把锁,根据获取锁的顺序执行,若不是同一把锁,线程之间互不影响
笔记参考