引入
今天我们将通过几个场景来深刻的理解几个问题。
如下:
- 什么是锁?
- 是谁的锁?
- 锁的是谁?
场景1
import java.util.concurrent.TimeUnit;
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
phone.sendMsm();
},"A").start();
//等待1秒钟
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{ //资源类
public synchronized void sendMsm(){
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
简单说明一下:一个资源类里面有两个同步方法(发短信 和 打电话),然后主线程创建了一个资源类对象,并开了两个线程去分别执行这个对象的两个同步方法。
执行结果:
发短信
打电话
分析:
线程A先拿到了 phone 这个对象的锁,所以他先执行。
这里 不 要认为是主线程先调用了A线程所以他先执行。
不理解的话,我们再看一个场景
场景2
import java.util.concurrent.TimeUnit;
@SuppressWarnings("ALL")
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendMsm();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//等待1秒钟
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
public synchronized void sendMsm() throws InterruptedException {
//等待3秒钟
TimeUnit.SECONDS.sleep(3);
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
我们让 “发短信”方法 持续 3 秒钟。
执行结果:
发短信
打电话
分析:
结果还是先 发短信 后 打电话,就像我 刚刚说的一样,发短信是持续3秒,并不影响他先得到锁,因为主线程过了一秒才去调用线程B,所以线程A 99.9%的几率是先拿到锁的。
synchronized 锁的对象是方法的调用者。
线程A和B 都是调用的同一个对象(phone)的方法,所以他们都是去竞争同一把锁。谁先拿到锁谁先执行,没拿到就得等。
看下一个场景:
如果没有锁竞争关系,那么应该就是拼的速度了。
场景3
import java.util.concurrent.TimeUnit;
@SuppressWarnings("ALL")
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendMsm();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
//等待1秒钟
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
phone.hello();
},"B").start();
}
}
class Phone{
public synchronized void sendMsm() throws InterruptedException {
//等待3秒钟
TimeUnit.SECONDS.sleep(3);
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
public void hello(){
System.out.println("Hello World!");
}
}
这次我们让线程B调用 对象phone的 hello()方法,注意:hello()方法不是同步方法!!
执行结果:
Hello World!
发短信
分析:
因为 hello()方法不是同步方法,所以压根就没有锁竞争这一说,虽然 线程B 比 线程A 晚启动了一秒钟,但 “发短信” 要等三秒钟才打印,这样一来,Hello World!还会比 “发短信” 先打印 2 秒钟。
同样没有锁竞争的场景
如下:
场景4
import java.util.concurrent.TimeUnit;
@SuppressWarnings("ALL")
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(() -> {
try {
phone.sendMsm();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
//等待1秒钟
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
phone1.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendMsm() throws InterruptedException {
//等待3秒钟
TimeUnit.SECONDS.sleep(3);
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
这次我们让 线程A 调用对象phone的“发短信”方法,而让 线程B 调用对象phone1的“打电话”方法。
执行结果:
打电话
发短信
分析:
这场景和上一个场景类似,虽然 “打电话”方法也是同步方法,但它是属于 对象 phone1 的方法,和对象 phone 的 “发短信” 方法 没有 锁竞争 关系。
还是那句话:
synchronized 锁的对象是方法的调用者。
显然,这次的这两个方法不是同一个调用者。而是两个,对象phone 和 对象phone1。
对象可以锁,那么类可不可以锁?
可以的,看如下场景:
场景5
import java.util.concurrent.TimeUnit;
@SuppressWarnings("ALL")
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
Phone phone1 = new Phone();
new Thread(() -> {
try {
phone.sendMsm();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
//等待1秒钟
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
phone1.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsm() throws InterruptedException {
//等待3秒钟
TimeUnit.SECONDS.sleep(3);
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
}
基本没什么变化,就是把 Phone 资源类 里的两个方法都加上了 static ,让其变成了 静态方法。
执行结果:
发短信
打电话
分析:
被 static 关键字修饰的,会在类加载的时候被执行,他们会和类信息一样,被存到 方法区,是独一无二的存在。
所以不管方法的调用者是不是同一个对象,他们都会存在 锁竞争关系,因为他们都是同一个类的静态方法。所以,这里的锁,锁的是 Class。
判断 锁竞争关系的关键,就是去看两个同步方法是不是去拿同一把锁。
场景6
import java.util.concurrent.TimeUnit;
@SuppressWarnings("ALL")
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendMsm();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
//等待1秒钟
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
phone.hello();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsm() throws InterruptedException {
//等待3秒钟
TimeUnit.SECONDS.sleep(3);
System.out.println("发短信");
}
public static synchronized void call() {
System.out.println("打电话");
}
public static void hello(){
System.out.println("Hello World!");
}
}
这次我们又加上了 hello() 方法,我们把它定义成了静态方法,同样不是同步方法。
执行结果:
Hello World!
发短信
分析:
非同步方法,根本没有锁竞争这一说,同一个类(Class)也没用。
这个场景其实挺没劲的,都老生常谈的事儿了。
那我们在来看下一个场景:
场景7
import java.util.concurrent.TimeUnit;
@SuppressWarnings("ALL")
public class test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
new Thread(() -> {
try {
phone.sendMsm();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "A").start();
//等待1秒钟
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsm() throws InterruptedException {
//等待3秒钟
TimeUnit.SECONDS.sleep(3);
System.out.println("发短信");
}
public synchronized void call() {
System.out.println("打电话");
}
}
这次我们把 线程B 调用的 call()方法 定义成了 普通同步方法,由同一个对象phone 去调用这两个方法。
大家猜猜结果会是神马?
同一个 对象phone 调用的,都是同步方法。结果难道是…
好了,不误导大家了。
执行结果:
打电话
发短信
分析:
还是那句话,看看他们是不是竞争同一把锁。
首先,“发短信” 是静态方法,他应该是要去找 Class锁,而 “打电话” 是非静态方法,所以他找的是对象锁。显然,他们竞争的不是同一把锁,所以没有竞争关系。
换句话说,静态同步块锁的是Class,而非静态的同步块 锁的是调用者。