线程八锁:
synchronized关键字作用在不同地方锁加的位置分别不同的八种实现方式,也是在面试过程中常被问到的
由于synchronized是作用在普通的三个同步方法上,在 main 方法中,我们创建了三个线程来执行这些方法。
由于thread0 和thread2 这两个方法是调用的同一个example0 对象,它们将使用相同的 锁对象( this ) ,所以他们会同步执行,需要等待一个执行完之后才会去执行另外一个。
而thread1 调用的是另外一个example1 对象,因为synchronized是作用在this上的,所以他不会互斥,也就不需要等待,可以异步执行。
/**
* @author zhengfuping
* @version 1.0
* @description: 两个线程分别访问同一个对象的两个普通方法。
* 由于synchronized加在非静态方法上,默认锁的是当前对象this,因此两个方法会互斥执行。
* 在执行的时候第一个方法持有了锁第二个调用的方法无法去获取,
* 需要等待第一个方法释放锁之后才会去持有锁执行下去。
* @date 2023/7/20 13:55
*/
public class SynchronizedExample1 {
public synchronized void method0(){
System.out.println("线程 "+ Thread.currentThread().getName() + " 正在执行 method0.");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getName() + " 完成执行 method0.");
}
public synchronized void method1(){
System.out.println("线程 "+ Thread.currentThread().getName() + " 正在执行 method1.");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getName() + " 完成执行 method1.");
}
public synchronized void method2(){
System.out.println("线程 "+ Thread.currentThread().getName() + " 正在执行 method2.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getName() + " 完成执行 method2.");
}
public static void main(String[] args) {
SynchronizedExample1 example0 = new SynchronizedExample1();
SynchronizedExample1 example1 = new SynchronizedExample1();
Thread thread0 = new Thread(() -> { //马上执行
example0.method0();
});
Thread thread1 = new Thread(() -> { //马上执行
example1.method1();
});
Thread thread2 = new Thread(() -> { //等待thread0 执行完执行
example0.method2();
});
thread0.start();
thread1.start();
thread2.start();
}
}
由于synchronized 是作用在静态同步方法上,而静态方法默认使用的锁的是类的Class对象,所以当通过不同的方式去调用这个类的method0、method1、method2方法时,都是需要获得的是同一个Class对象锁,所以三个线程会以互斥的方式执行方法,当一个线程抢占到锁时,其他的线程需要等待这个线程释放Class锁才能去抢占获取。
/**
* @author zhengfuping
* @version 1.0
* @description: 三个线程分别访问同一个类的三个静态同步方法。由于静态方法默认使用的锁对象是类的Class对象,因此这三个方法会互斥执行。
* @date 2023/7/20 14:19
*/
public class SynchronizedExample2 {
public static synchronized void method0(){
System.out.println("线程 "+ Thread.currentThread().getName() + " 正在执行 method0.");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getName() + " 完成执行 method0.");
}
public static synchronized void method1(){
System.out.println("线程 "+ Thread.currentThread().getName() + " 正在执行 method1.");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getName() + " 完成执行 method1.");
}
public static synchronized void method2(){
System.out.println("线程 "+ Thread.currentThread().getName() + " 正在执行 method2.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getName() + " 完成执行 method2.");
}
public static void main(String[] args) {
SynchronizedExample2 example1 = new SynchronizedExample2();
SynchronizedExample2 example2 = new SynchronizedExample2();
Thread thread0 = new Thread(() -> { //获取到锁
SynchronizedExample2.method0();
});
Thread thread1 = new Thread(() -> { //等待thread0 释放
example1.method1();
});
Thread thread2 = new Thread(() -> { //等待thread1 释放
example2.method2();
});
thread0.start();
thread1.start();
thread2.start();
}
}
如果一个类有在静态同步方法和普通同步方法上加锁会出现几种情况:
0(静态)、1(静态)、2(普通)
- a线程获取method0方法的锁,b线程再去请求method1方法时,就需要等待a线程释放锁才能够调用执行(因为他们两个都是静态同步方法,所以锁的是Class对象),而如果c线程通过
new 对象
去调用method2方法时就无需等待,因为method2方法是普通同步方法属于this对象,不会被锁住。0(静态)、1(普通)、2(普通)
- a线程获取method0方法的锁,b线程再去请求method1方法时,不需要等待可以直接获取,同上;因为method0方法的锁锁的是Class对象,不能锁住普通同步方法,而此时c线程想去获取method3方法时,**如果和b线程是同一个new
的对象,则需要等待b线程释放锁。**如果是重新new 的对象则无需等待,因为b线程锁是加在b线程的this对象,不能锁住其他对象基本就是遵循一个原则:如果是静态方法的对象锁的是Class对象,会让这个类的全部静态同步方法产生排斥;如果是普通同步方法锁的是this当前对象,会让当前对象的方法调用时产生排除
/**
* @author zhengfuping
* @version 1.0
* @description: 当a线程访问对象的method2普通同步方法,b线程访问类的method0静态同步方法。由于锁对象不同,因此这两个方法不会互斥执行。
* 但如果在来一个c线程如果是访问的是类的另外一个method1静态同步方法时,因为method0是静态方法锁的是Class对象,所以c
* 线程需要等待等待b线程执行完才能够去执行
* @date 2023/7/20 15:22
*/
public class SynchronizedExample3 {
public static synchronized void method0(){
System.out.println("线程 "+ Thread.currentThread().getName() + " 正在执行 method0.");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getName() + " 完成执行 method0.");
}
public static synchronized void method1(){
System.out.println("线程 "+ Thread.currentThread().getName() + " 正在执行 method1.");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getName() + " 完成执行 method1.");
}
public synchronized void method2(){
System.out.println("线程 "+ Thread.currentThread().getName() + " 正在执行 method2.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getName() + " 完成执行 method2.");
}
public static void main(String[] args) {
SynchronizedExample3 example1 = new SynchronizedExample3();
SynchronizedExample3 example2 = new SynchronizedExample3();
Thread thread0 = new Thread(() -> {
SynchronizedExample3.method0();
});
Thread thread1 = new Thread(() -> {
example1.method1();
});
Thread thread2 = new Thread(() -> {
example2.method2();
});
thread0.start();
thread1.start();
thread2.start();
}
}
和两个普通方法一样,如果是同一个对象的话就会产生互斥,因为他们锁的都是当前自己的实例对象,调用时就会互斥;如果不是相同对象就不会产生互斥。
/**
* @author zhengfuping
* @version 1.0
* @description: 同一个对象的普通方法和普通代码块之间也是互斥的(和两个普通方法一样),因为他们使用的是同一个对象 ,
* 如果是不同的对象的话就不会互斥
* @date 2023/7/20 16:43
*/
public class SynchronizedExample4 {
public synchronized void method0(){
System.out.println("线程 "+ Thread.currentThread().getName() + " 正在执行 method0.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getName() + " 完成执行 method0.");
}
public void synchronizedBlock1(){
synchronized (this){
System.out.println("进入同步代码块1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("退出同步代码块1");
}
}
public void synchronizedBlock2(){
synchronized (this){
System.out.println("进入同步代码块2");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("退出同步代码块2");
}
}
public static void main(String[] args) {
SynchronizedExample4 example1 = new SynchronizedExample4();
SynchronizedExample4 example2 = new SynchronizedExample4();
Thread thread0 = new Thread(example1::method0);
Thread thread1 = new Thread(example1::synchronizedBlock1);
Thread thread2 = new Thread(example2::synchronizedBlock2);
thread0.start();
thread1.start();
thread2.start();
}
}
在调用这个Class类的静态同步方法或者静态同步代码块时,不管是通过什么方式调用都会产生互斥,都需要等待前一个获取锁的线程释放锁之后才可以去获取到锁。因为锁是加在Class类上的,所以都需要等待同步执行完
/**
* @author zhengfuping
* @version 1.0
* @description: 静态同步和静态同步代码块也是互斥的,因为他们的锁是加在Class上,所以只要调用的是这个Class类的静态同步方法或者同步代码块,
* 都会产生互斥,需要等待一个执行完之后才可以执行另外一个。
* @date 2023/7/20 16:55
*/
public class SynchronizedExample5 {
public static synchronized void method0(){
System.out.println("线程 "+ Thread.currentThread().getName() + " 正在执行 method0.");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程 " + Thread.currentThread().getName() + " 完成执行 method0.");
}
public static void synchronizedBlock1(){
// 静态同步代码块
synchronized (SynchronizedExample5.class){
System.out.println("进入同步代码块1");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("退出同步代码块1");
}
}
public static void synchronizedBlock2(){
synchronized (SynchronizedExample5.class){
System.out.println("进入同步代码块2");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("退出同步代码块2");
}
}
public static void main(String[] args) {
SynchronizedExample5 example1 = new SynchronizedExample5();
Thread thread0 = new Thread(()->{ //获取锁
example1.method0();
});
Thread thread1 = new Thread(SynchronizedExample5::synchronizedBlock1); //等待
Thread thread2 = new Thread(SynchronizedExample5::synchronizedBlock2); //等待
thread0.start();
thread1.start();
thread2.start();
}
}
原因就在于加锁的位置不同
/**
* @author zhengfuping
* @version 1.0
* @description: 普通同步方法(同步代码块)如果是相同实例对象的则会发生互斥,如果是不同实例对象调用则不会发生互斥
* 静态同步方法(同步代码块)则不管是什么方式调用都会发生互斥
* @date 2023/7/20 17:11
*/
public class SynchronizedExample6 {
public synchronized void synchronizedMethod1(){
// 普通同步方法
System.out.println("进入普通同步方法1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("退出普通同步方法1");
}
public static synchronized void synchronizedMethod2(){
// 普通同步方法
System.out.println("进入静态同步方法2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("退出静态同步方法2");
}
public void synchronizedBlock1(){
synchronized (SynchronizedExample5.class){
System.out.println("进入同步代码块");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("退出同步代码块");
}
}
public static void synchronizedBlock2(){
synchronized (SynchronizedExample5.class){
System.out.println("进入静态同步代码块2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("退出静态同步代码块2");
}
}
public static void main(String[] args) {
SynchronizedExample6 example61 = new SynchronizedExample6();
SynchronizedExample6 example62 = new SynchronizedExample6();
Thread thread1 = new Thread(example61::synchronizedBlock1);
Thread thread2 = new Thread(example62::synchronizedMethod1);
Thread thread3 = new Thread(SynchronizedExample6::synchronizedBlock2);
Thread thread4 = new Thread(SynchronizedExample6::synchronizedMethod2);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}