八锁即锁的八个问题
一、正常情况下线程的执行顺序
package demo_02;
// 八锁问题
// 正常线程执行情况
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm = new PrintMessage();
new Thread(()->{pm.printA();},"A").start();
new Thread(()->{pm.printB();},"B").start();
}
}
class PrintMessage{
public synchronized void printA(){
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
}
public synchronized void printB(){
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
}
}
执行结果:
可以看到这里是先执行的printA,在执行的printB,可是原因是什么呢?因为先调用的printA方法,再调用的printB方法吗?答案是否定的
二、睡眠A线程3秒,线程的执行顺序
有人可能会觉得是因为先调用的printA方法再调用的printB方法的问题,因此我们让printA方法睡三秒
package demo_02;
// 八锁问题
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm = new PrintMessage();
new Thread(()->{
try {
pm.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm.printB();},"B").start();
}
}
class PrintMessage{
public synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
}
public synchronized void printB(){
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
}
}
执行结果:
不管执行多少次都是先打印A再打印B,注意这里并不是因为先调用的printA方法,再调用printB方法的问题,这样理解根本没有理解到关键。
这里出现先打印A再打印B的原因是因为大家没有理解synchronized锁,锁的是什么?这个地方锁的是PrintMessage这个类的调用者pm对象,所以在A线程屌用printA方法时,会对pm对象加锁,B线程要等待A线程释放pm锁之后才可以获得pm对象。
三、普通方法和synchronized方法执行问题
package demo_02;
// 八锁问题
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm = new PrintMessage();
new Thread(()->{
try {
pm.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm.printC();},"C").start();
}
}
class PrintMessage{
public synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
}
public void printC(){
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "C");
}
}
执行结果:
这里为了让结果看起来更清晰,我将A线程睡了三秒。这里先打印C的原因是在A线程拿到了pm对象的锁的时候,C线程可以直接对pm对象进行操作,因为printC方法并不是同步方法,不需要进行同步操作,A线程有没有锁对C线程调用printC方法都没有影响。
四、两个线程不同的对象执行顺序
为了获取观察效果,在这里同样让线程A睡3秒
package demo_02;
// 八锁问题
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm1 = new PrintMessage();
PrintMessage pm2 = new PrintMessage();
new Thread(()->{
try {
pm1.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm2.printB();},"B").start();
}
}
class PrintMessage{
public synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
}
public synchronized void printB(){
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
}
}
执行结果:
这里虽然是两个同步方法,但是synchronized锁的对象不同,因此线程A和线程B调用的printA和printB方法分别是不对对象的方法,两个线程并不冲突,因为A线程睡眠的3秒,所以先打印B再打印A。
五、静态同步方法的执行顺序
将同步方法更改为static静态方法,看一看执行顺序有什么不同。
static修饰的方法会在类第一次载入的时候被执行。
package demo_02;
// 八锁问题
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm = new PrintMessage();
new Thread(()->{
try {
pm.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm.printB();},"B").start();
}
}
class PrintMessage{
public static synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
}
public static synchronized void printB(){
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
}
}
执行结果:
这里虽然和前面的正常情况的输出结果一样,但是其内在原理却不相同。首先我们需要理解一下static关键字,static声明的静态方法,会在类被加载的时候就执行。可以理解成static方法在对象被new的时候就已经执行,执行后的方法会放在class文件中(java文件编译后产生的class文件),class又被称为该类的模板,一个类在运行过程只只会有一个模板这里synchronized锁,锁的对象是一个模板,而不是一个类对象,虽然输出结果一样但是锁的对象并不相同。
可能很多同学还不是太懂于是就有了第六个锁的问题
六、两个对象,两个静态同步方法
如果之前大家可能还没有想通,我们创建两个PrintMessage的对象,分别调用打印方法,按道理如果A线程睡眠3秒,那B线程就会先打印,然后在打印A线程的输出方法,但是如果是静态同步方法呢?
package demo_02;
// 八锁问题
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm1 = new PrintMessage();
PrintMessage pm2 = new PrintMessage();
new Thread(()->{
try {
pm1.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm2.printB();},"B").start();
}
}
class PrintMessage{
public static synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
}
public static synchronized void printB(){
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
}
}
执行结果:
七、一个静态同步方和一个普通方法,一个对象的执行顺序
package demo_02;
// 八锁问题
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm = new PrintMessage();
new Thread(()->{
try {
pm.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm.printB();},"B").start();
}
}
class PrintMessage{
public static synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
}
public void printB(){
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
}
}
执行结果:
相信大家如果认真看了前面的问题,就知道这个问题的原因了,因为普通方法并不会获取锁资源,所以线程B的普通方法可以直接执行,线程A睡眠3秒后再执行。
八、两个对象,一个静态同步方法,一个普通同步方法,执行顺序
这个地方的关键是要明白锁的对象锁的是什么?
package demo_02;
// 八锁问题
import java.util.concurrent.TimeUnit;
public class Test_01 {
public static void main(String[] args) {
PrintMessage pm1 = new PrintMessage();
PrintMessage pm2 = new PrintMessage();
new Thread(()->{
try {
pm1.printA();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{pm2.printB();},"B").start();
}
}
class PrintMessage{
public static synchronized void printA() throws InterruptedException {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "A");
}
public synchronized void printB(){
System.out.println(Thread.currentThread().getName()+" 输出了 => " + "B");
}
}
执行结果:
首先静态同步方法锁的是类模板(class文件),而普通同步方法锁的是PrintMessage的对象,所以两个锁的对象是不同的,两个锁并不冲突,自己锁自己的,线程A睡眠3秒后输出,线程B正常输出,所以先输出B线程再输出A线程。
该部分内容是笔者参考狂神的学习视频,做的一些总结以及自己的思考,如果有纰漏的地方,希望大家多多指出,一起学习!