原问题
8锁问题探讨的是在多线程环境中,各线程访问资源的先后顺序。
具体问题为开启两个线程,其中线程2比线程1慢开启,线程1先进入了资源类的sendSms,然后延迟打印“发短信”,根据不同的条件,判断最终是先打印了“发短信”,还是“打电话”。
public class LockTest1 {
public static void main(String[] args) throws InterruptedException {
Phone1 d = new Phone1();
new Thread(()->{
d.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d.call();
}).start();
}
}
class Phone1{
public void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
}
其实,通过查看这8个问题的条件,可以知道他们是通过改变几个条件来产生不同的锁问题的,分别是:
- 是否为静态方法
- 是否有Synchronized修饰
- 实例化一个或是两个资源类对象
而这3个条件可以用枚举法列举出所有的情况:
1、是否为静态方法:
因为有两个方法,所以可能的情况是:两个都为静态方法、两个都为非静态方法、一个静态方法一个非静态方法,共3种。
2、是否有Synchronized修饰:
两个方法都有Synchronized修饰、两个方法都没有Synchronized修饰、一个有修饰一个没有修饰,共3种。
3、实例化一个或是两个资源类对象:
实例化了一个对象、实例化了两个对象,共2种。
注意:
- 因为这个问题要出现锁,如果没有锁的话就没有讨论的意义了,所以在第二个条件中至少要有一个方法有被Synchronized修饰,所以第二个条件只能有两种情况:两个方法都有Synchronized修饰、一个有修饰一个没有修饰。
- 条件1的一个静态方法一个非静态方法和条件2的一个有Synchronized修饰一个没有Synchronized修饰的方法有两种可能的组合情况。
因此,总共需要探讨的情况有3×2×2+2=14种。
关注点
在探讨不同线程执行资源类方法的先后顺序中,我们需要了解:
1、当线程执行了资源类的有Synchronized修饰的方法时,它会拿到一个锁,这个锁与该方法是否是静态有关:
- 如果该方法是静态方法,则拿到的是类锁
- 如果该方法是非静态方法,则拿到的是对象锁
2、拿到了锁的线程只有在该方法正常执行结束后才会释放锁,之后其他线程才有机会能拿到锁
1、两个都是静态、Synchronized修饰的方法,一个资源类对象
public class LockTest1 {
public static void main(String[] args) throws InterruptedException {
Phone1 d = new Phone1();
new Thread(()->{
d.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d.call();
}).start();
}
}
class Phone1{
public static synchronized void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
结果:
发短信
打电话
资源类的两个方法都有static、Synchronized修饰,所以线程执行任一个方法时都会拿到类锁,而线程一先进入sendSms方法,所以需要等待sendSms执行完毕后才能释放类锁。
2、两个都是静态、Synchronized修饰的方法,两个资源类对象
public class LockTest2 {
public static void main(String[] args) throws InterruptedException {
Phone2 d1 = new Phone2();
Phone2 d2 = new Phone2();
new Thread(()->{
d1.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d2.call();
}).start();
}
}
class Phone2{
public static synchronized void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
结果:
发短信
打电话
与第1种情况类似,因为进入两个方法拿到的都是类锁,所以结果打印的先后顺序只与这个类有关,与是这个类的哪个对象调用的方法无关。
3、两个都是非静态、Synchronized修饰的方法,一个资源类对象
public class LockTest3 {
public static void main(String[] args) throws InterruptedException {
Phone3 d = new Phone3();
new Thread(()->{
d.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d.call();
}).start();
}
}
class Phone3{
public synchronized void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
结果
发短信
打电话
资源类的两个方法是非静态的,并且都有Synchronized修饰,所以线程执行任一个方法后都会拿到对象锁,而两个线程执行的都是同一个资源类对象的方法,所以当线程一执行sendSms方法时拿到了对象锁,此时线程二必须等待线程一释放对象锁后,才有可能拿到对象锁。
4、两个都是非静态、Synchronized修饰的方法,两个资源类对象
public class LockTest4 {
public static void main(String[] args) throws InterruptedException {
Phone4 d1 = new Phone4();
Phone4 d2 = new Phone4();
new Thread(()->{
d1.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d2.call();
}).start();
}
}
class Phone4{
public synchronized void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
结果:
打电话
发短信
与第3种情况类似,但由于是调用两个资源类对象的方法,所以两个线程拿到的对象锁是不同的,两者并不冲突,线程二不用等待,又sendSms方法打印“发短信”的时间较长,所以程序运行结果是先打印了“打电话”,再打印“打短信”。
5、一个静态、Synchronized修饰的方法,一个非静态、Synchronized修饰的方法,一个资源类对象
ew Phone5();
new Thread(()->{
d.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d.call();
}).start();
}
}
class Phone5{
public static synchronized void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
结果:
打电话
发短信
执行sendSms方法拿到了类锁,执行call拿到了对象锁,所以线程二不用等待,最终先打印了“打电话”。
6、一个静态、Synchronized修饰的方法,一个非静态、Synchronized修饰的方法,两个资源类对象
public class LockTest6 {
public static void main(String[] args) throws InterruptedException {
Phone6 d1 = new Phone6();
Phone6 d2 = new Phone6();
new Thread(()->{
d1.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d2.call();
}).start();
}
}
class Phone6{
public static synchronized void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
结果:
打电话
发短信
与第5种情况类似,线程二不用等待,并且与是哪个对象调用的方法无关。
7、一个静态、Synchronized修饰的方法,一个静态、无Synchronized修饰的方法,一个资源类对象
public class LockTest7 {
public static void main(String[] args) throws InterruptedException {
Phone7 d = new Phone7();
new Thread(()->{
d.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d.call();
}).start();
}
}
class Phone7{
public static synchronized void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static void call(){
System.out.println("打电话");
}
}
结果:
打电话
发短信
call方法没有Synchronized修饰,只是普通方法,所以线程二不用等待,直接执行call方法。
8、一个静态、Synchronized修饰的方法,一个静态、无Synchronized修饰的方法,两个资源类对象
public class LockTest8 {
public static void main(String[] args) throws InterruptedException {
Phone8 d1 = new Phone8();
Phone8 d2 = new Phone8();
new Thread(()->{
d1.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d2.call();
}).start();
}
}
class Phone8{
public static synchronized void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static void call(){
System.out.println("打电话");
}
}
结果:
打电话
发短信
与第7种情况类似。
9、一个非静态、Synchronized修饰的方法,一个非静态、无Synchronized修饰的方法,一个资源类对象
public class LockTest9 {
public static void main(String[] args) throws InterruptedException {
Phone9 d = new Phone9();
new Thread(()->{
d.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d.call();
}).start();
}
}
class Phone9{
public synchronized void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
}
结果:
打电话
发短信
sendSms方法有Synchronized修饰,所以执行他会拿到对象锁,而call方法只是普通的方法,所以线程二不用等待,直接执行call方法。
10、一个非静态、Synchronized修饰的方法,一个非静态、无Synchronized修饰的方法,两个资源类对象
public class LockTest10 {
public static void main(String[] args) throws InterruptedException {
Phone10 d1 = new Phone10();
Phone10 d2 = new Phone10();
new Thread(()->{
d1.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d2.call();
}).start();
}
}
class Phone10{
public synchronized void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
}
结果:
打电话
发短信
与第9种情况类似。
11、一个静态、Synchronized修饰的方法,一个非静态、无Synchronized修饰的方法,一个资源类对象
public class LockTest11 {
public static void main(String[] args) throws InterruptedException {
Phone11 d = new Phone11();
new Thread(()->{
d.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d.call();
}).start();
}
}
class Phone11{
public static synchronized void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
}
结果:
打电话
发短信
call方法是普通方法,所以线程二不用等待,直接执行call方法。
12、一个静态、Synchronized修饰的方法,一个非静态、无Synchronized修饰的方法,两个资源类对象
public class LockTest12 {
public static void main(String[] args) throws InterruptedException {
Phone12 d1 = new Phone12();
Phone12 d2 = new Phone12();
new Thread(()->{
d1.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d2.call();
}).start();
}
}
class Phone12{
public static synchronized void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public void call(){
System.out.println("打电话");
}
}
结果:
打电话
发短信
与第11种情况类似。
13、一个静态、无Synchronized修饰的方法,一个非静态、Synchronized修饰的方法,一个资源类对象
public class LockTest13 {
public static void main(String[] args) throws InterruptedException {
Phone13 d = new Phone13();
new Thread(()->{
d.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d.call();
}).start();
}
}
class Phone13{
public static void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
结果:
打电话
发短信
sendSms方法是普通的方法,而call方法有Synchronized修饰,所以两个线程不用相互等待,线程二会直接执行call方法。
14、一个静态、无Synchronized修饰的方法,一个非静态、Synchronized修饰的方法,两个资源类对象
public class LockTest14 {
public static void main(String[] args) throws InterruptedException {
Phone14 d1 = new Phone14();
Phone14 d2 = new Phone14();
new Thread(()->{
d1.sendSms();
}).start();
//加大第一个匿名对象先执行sendSms方法的概率
Thread.sleep(100);
new Thread(()->{
d2.call();
}).start();
}
}
class Phone14{
public static void sendSms() {
try {
//延长打印“发短信”的时间
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
结果:
打电话
发短信
与第13种情况类似。