文章目录
声明:
本博客是本人在学习《Java 多线程编程核心技术》后整理的笔记,旨在方便复习和回顾,并非用作商业用途。
本博客已标明出处,如有侵权请告知,马上删除。
2.2 synchronized 同步语句块
2.2.9 静态同步 synchronized 方法与 synchronized(class) 代码块
关键字 synchronized 还可以应用在 static 静态方法上,如果这样写,那是对当前的 * .java 文件对应的 Class 类进行持锁
下面通过一个示例来演示 synchronized 应用在 static 静态方法上:
-
创建一个公共类
public class Service5 { synchronized public static void printA() { try { System.out.println("printA begin" + " run threadName = " + Thread.currentThread().getName()); Thread.sleep(3000); System.out.println("printA end" + " run threadName = " + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public static void printB() { System.out.println("printB begin" + " run threadName = " + Thread.currentThread().getName()); System.out.println("printB end" + " run threadName = " + Thread.currentThread().getName()); } }
-
创建两个自定义的线程类
public class MyThread12 extends Thread { @Override public void run() { super.run(); Service5.printA(); } }
public class MyThread12_2 extends Thread { @Override public void run() { super.run(); Service5.printB(); } }
-
测试类
public class MyThread12_Test { public static void main(String[] args) { MyThread12 myThread12 = new MyThread12(); myThread12.setName("A"); myThread12.start(); MyThread12_2 myThread12_2 = new MyThread12_2(); myThread12_2.setName("B"); myThread12_2.start(); } }
运行结果
printA begin run threadName = A printA end run threadName = A printB begin run threadName = B printB end run threadName = B
分析:从运行结果来看,并没有什么特别之处,都是同步的效果,和将 synchronized 关键字加到非 static 方法上使用的效果是一样的。其实还是有本质上的不同的,synchronized 关键字加到 static 静态方法上是给 Class 类上锁,而 synchronized 关键字加到非 static 静态方法上是给对象上锁。
为了验证它们不是一个锁,下面通过一个示例来演示:
-
创建一个公共类
public class Service6 { synchronized public static void printA() { try { System.out.println("printA begin" + " run threadName = " + Thread.currentThread().getName()); Thread.sleep(3000); System.out.println("printA end" + " run threadName = " + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public static void printB() { System.out.println("printB begin" + " run threadName = " + Thread.currentThread().getName()); System.out.println("printB end" + " run threadName = " + Thread.currentThread().getName()); } synchronized public void printC() { System.out.println("printC begin" + " run threadName = " + Thread.currentThread().getName()); System.out.println("printC end" + " run threadName = " + Thread.currentThread().getName()); } }
-
创建三个自定义的线程类
public class MyThread13 extends Thread { private Service6 service6; public MyThread13(Service6 service6) { this.service6 = service6; } @Override public void run() { super.run(); service6.printA(); } }
public class MyThread13_2 extends Thread { private Service6 service6; public MyThread13_2(Service6 service6) { this.service6 = service6; } @Override public void run() { super.run(); service6.printB(); } }
public class MyThread13_3 extends Thread { private Service6 service6; public MyThread13_3(Service6 service6) { this.service6 = service6; } @Override public void run() { super.run(); service6.printC(); } }
-
测试类
public class MyThread13Test { public static void main(String[] args) { Service6 service6 = new Service6(); MyThread13 myThread13 = new MyThread13(service6); myThread13.setName("A"); myThread13.start(); MyThread13_2 myThread13_2 = new MyThread13_2(service6); myThread13_2.setName("B"); myThread13_2.start(); MyThread13_3 myThread13_3 = new MyThread13_3(service6); myThread13_3.setName("C"); myThread13_3.start(); } }
运行结果
printA begin run threadName = A printC begin run threadName = C printC end run threadName = C printA end run threadName = A printB begin run threadName = B printB end run threadName = B
分析:方法 printC 为异步运行,异步的原因是持有不同的锁,PrintC 方法持有的是对象锁,printA 和 printB 方法持有的是 Class 锁
Class 锁可以对类的所有对象实例起作用,下面通过一个示例来演示:
-
创建一个公共类
public class Service7 { synchronized public static void printA() { try { System.out.println("printA begin" + " run threadName = " + Thread.currentThread().getName()); Thread.sleep(3000); System.out.println("printA end" + " run threadName = " + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public static void printB() { System.out.println("printB begin" + " run threadName = " + Thread.currentThread().getName()); System.out.println("printB end" + " run threadName = " + Thread.currentThread().getName()); } }
-
创建两个自定义的线程类
public class MyThread14 extends Thread { private Service7 service7; public MyThread14(Service7 service7) { this.service7 = service7; } @Override public void run() { super.run(); service7.printA(); } }
public class MyThread14_2 extends Thread { private Service7 service7; public MyThread14_2(Service7 service7) { this.service7 = service7; } @Override public void run() { super.run(); service7.printB(); } }
-
测试类
public class MyThread14Test { public static void main(String[] args) { Service7 service7 = new Service7(); Service7 service7_2 = new Service7(); MyThread14 myThread14 = new MyThread14(service7); myThread14.setName("A"); myThread14.start(); MyThread14_2 myThread14_2 = new MyThread14_2(service7_2); myThread14_2.setName("B"); myThread14_2.start(); } }
运行结果
printA begin run threadName = A printA end run threadName = A printB begin run threadName = B printB end run threadName = B
同步 synchronized(class) 代码块的作用其实和 synchronized static 方法的作用一样,也是对 class 类上锁。
下面通过一个示例演示:
-
创建一个公共类
public class Service8 { public static void printA() { synchronized (Service8.class) { try { System.out.println("printA begin" + " run threadName = " + Thread.currentThread().getName()); Thread.sleep(3000); System.out.println("printA end" + " run threadName = " + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void printB() { synchronized (Service8.class) { System.out.println("printB begin" + " run threadName = " + Thread.currentThread().getName()); System.out.println("printB end" + " run threadName = " + Thread.currentThread().getName()); } } }
-
创建两个自定义的线程类
public class MyThread15 extends Thread { private Service8 service8; public MyThread15(Service8 service8) { this.service8 = service8; } @Override public void run() { super.run(); service8.printA(); } }
public class MyThread15_2 extends Thread { private Service8 service8; public MyThread15_2(Service8 service8) { this.service8 = service8; } @Override public void run() { super.run(); service8.printB(); } }
-
测试类
public class MyThread15Test { public static void main(String[] args) { Service8 service8 = new Service8(); Service8 service8_2 = new Service8(); MyThread15 myThread15 = new MyThread15(service8); myThread15.setName("A"); myThread15.start(); MyThread15_2 myThread15_2 = new MyThread15_2(service8_2); myThread15_2.setName("B"); myThread15_2.start(); } }
运行结果
printA begin run threadName = A printA end run threadName = A printB begin run threadName = B printB end run threadName = B
2.2.10 数据类型 String 的常量池特性
在 JVM 中具有 String 常量池缓存的功能,下面通过一个示例来演示:
public class Test {
public static void main(String[] args) {
String a = "a";
String b = "a";
System.out.println(a == b);
}
}
将 synchronized(string) 同步块与 String 联合使用时,要注意常量池带来的一些例外,下面通过一个示例来演示:
-
创建一个公共类
public class Service9 { public void print(String stringParam) { try { synchronized (stringParam) { while (true) { System.out.println(Thread.currentThread().getName()); Thread.sleep(1000); } } } catch (InterruptedException e) { e.printStackTrace(); } } }
-
创建两个自定义的线程类
public class MyThread16 extends Thread { private Service9 service9; public MyThread16(Service9 service9) { this.service9 = service9; } @Override public void run() { super.run(); service9.print("A"); } }
public class MyThread16_2 extends Thread { private Service9 service9; public MyThread16_2(Service9 service9) { this.service9 = service9; } @Override public void run() { super.run(); service9.print("A"); } }
-
测试类
public class MyThread16Test { public static void main(String[] args) { Service9 service9 = new Service9(); MyThread16 myThread16 = new MyThread16(service9); myThread16.setName("A"); myThread16.start(); MyThread16 myThread16_2 = new MyThread16(service9); myThread16_2.setName("B"); myThread16_2.start(); } }
运行结果
A A A A A A A A ...
分析:出现这样的情况就是因为 String 的两个值都是 A,两个线程持有相同的锁,所以造成线程 B 不能执行。这就是 String 常量池所带来的问题。因此在大多数情况下,同步 synchronized 代码块都不使用 String 作为锁对象,而改用其他,比如 new Object() 实例化一个 Object 对象,但它并不放入缓存中。
下面通过一个示例来演示:
-
创建一个公共类
public class Service10 { public void print(Object object) { try { synchronized (object) { while (true) { System.out.println(Thread.currentThread().getName()); Thread.sleep(1000); } } } catch (InterruptedException e) { e.printStackTrace(); } } }
-
创建两个自定义的线程类
public class MyThread17 extends Thread { private Service10 service10; public MyThread17(Service10 service10) { this.service10 = service10; } @Override public void run() { super.run(); service10.print(new Object()); } }
public class MyThread17_2 extends Thread { private Service10 service10; public MyThread17_2(Service10 service10) { this.service10 = service10; } @Override public void run() { super.run(); service10.print(new Object()); } }
-
测试类
public class MyThread17Test { public static void main(String[] args) { Service10 service10 = new Service10(); MyThread17 myThread17 = new MyThread17(service10); myThread17.setName("A"); myThread17.start(); MyThread17_2 myThread17_2 = new MyThread17_2(service10); myThread17_2.setName("B"); myThread17_2.start(); } }
运行结果
A B B A B A B A B A B A ...
分析:交替打印的原因是持有的锁不是同一个
2.2.11 同步 synchronized 方法无限等待与解决
同步方法容易造成死循环,下面通过一个示例来演示:
-
创建一个公共类
public class Service11 { synchronized public void methodA() { System.out.println("methodA begin"); boolean isContinue = true; while (isContinue) { } System.out.println("methodA end"); } synchronized public void methodB() { System.out.println("methodB begin"); System.out.println("methodB end"); } }
-
创建两个自定义的线程类
public class MyThread18 extends Thread { private Service11 service11; public MyThread18(Service11 service11) { this.service11 = service11; } @Override public void run() { super.run(); service11.methodA(); } }
public class MyThread18_2 extends Thread { private Service11 service11; public MyThread18_2(Service11 service11) { this.service11 = service11; } @Override public void run() { super.run(); service11.methodB(); } }
-
测试类
public class MyThread18Test { public static void main(String[] args) { Service11 service11 = new Service11(); MyThread18 myThread18 = new MyThread18(service11); myThread18.start(); MyThread18_2 myThread18_2 = new MyThread18_2(service11); myThread18_2.start(); } }
运行结果
methodA begin
分析:线程 B 永远得不到运行机会,锁死了
这时就可以使用同步块来解决这样的问题了,下面通过一个示例来演示:
-
更改 Service11
public class Service11 { private Object object1 = new Object(); private Object object2 = new Object(); public void methodA() { synchronized (object1) { System.out.println("methodA begin"); boolean isContinue = true; while (isContinue) { } System.out.println("methodA end"); } } public void methodB() { synchronized (object2) { System.out.println("methodB begin"); System.out.println("methodB end"); } } }
-
再次运行,运行结果如下
methodA begin methodB begin methodB end
2.2.12 多线程的死锁
Java 线程死锁是一个经典的多线程问题,因为不同的线程都在等待不可能被释放的锁,从而导致所有任务都无法继续完成。在多线程技术中,“死锁” 是必须避免的,因为这会造成线程的 “假死”。
下面通过一个示例来演示多线程的死锁:
-
创建一个自定义的线程类
public class DealThread implements Runnable { public String username; public Object lock1 = new Object(); public Object lock2 = new Object(); public void setUsername(String username) { this.username = username; } @Override public void run() { if (username == "A") { synchronized (lock1) { try { System.out.println("username = " + username); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2) { System.out.println("执行顺序为:lock1 -> lock2"); } } } if (username == "B") { synchronized (lock2) { try { System.out.println("username = " + username); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1) { System.out.println("执行顺序为:lock2 -> lock1"); } } } } }
-
测试类
public class DealThreadTest { public static void main(String[] args) { try { DealThread dealThread = new DealThread(); dealThread.setUsername("A"); Thread thread = new Thread(dealThread); thread.start(); Thread.sleep(100); dealThread.setUsername("B"); Thread thread_2 = new Thread(dealThread); thread_2.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行结果
username = A username = B
分析:死锁是程序设计的 Bug,在设计程序时就要避免双方互相持有对方锁的情况。需要说明的是,本实验使用 synchronized 嵌套的代码结构来实现死锁,其实不使用嵌套的 synchronized 代码结构也会出现死锁,与嵌套不嵌套无任何关系,不要被代码结构所误导。只要互相等待对方释放锁就有可能出现死锁。
2.3.13 内置类与静态内置类
先来看一下简单的内置类测试
-
创建 PublicClass 类,其中包含内置类 PrivateClass
public class PublicClass { private String username; private String password; class PrivateClass { private String age; private String address; public String getAge() { return age; } public void setAge(String age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public void printPublicProperty() { System.out.println(username + " " + password); } } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
-
测试类
public class PublicClassTest { public static void main(String[] args) { PublicClass publicClass = new PublicClass(); publicClass.setUsername("usernameValue"); publicClass.setPassword("passwordValue"); System.out.println(publicClass.getUsername() + " " + publicClass.getPassword()); PublicClass.PrivateClass privateClass = publicClass.new PrivateClass(); privateClass.setAge("ageValue"); privateClass.setAddress("addressValue"); System.out.println(privateClass.getAge() + " " + privateClass.getAddress()); } }
运行结果
usernameValue passwordValue ageValue addressValue
内置类还有一种叫做静态内置类
-
创建 PublicClass 类,其中包含静态内置类 PrivateClass
public class PublicClass2 { static private String username; static private String password; static class PrivateClass { private String age; private String address; public String getAge() { return age; } public void setAge(String age) { this.age = age; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public void printPublicProperty() { System.out.println(username + " " + password); } } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
-
测试类
public class PublicClass2Test { public static void main(String[] args) { PublicClass2 publicClass2 = new PublicClass2(); publicClass2.setUsername("usernameValue"); publicClass2.setPassword("passwordValue"); System.out.println(publicClass2.getUsername() + " " + publicClass2.getPassword()); PublicClass2.PrivateClass privateClass = new PublicClass2.PrivateClass(); privateClass.setAge("ageValue"); privateClass.setAddress("addressValue"); System.out.println(privateClass.getAge() + " " + privateClass.getAddress()); } }
运行结果
usernameValue passwordValue ageValue addressValue
2.2.14 内置类与同步:实验1
本实验测试的案例是在内置类中有两个同步方法。但使用的是不同的锁,打印的结果也是异步的。
-
创建 OutClass 类,其中包含静态内置类 InnerClass
public class OutClass { static class InnerClass { public void method1() { synchronized ("其他的锁") { for (int i = 1; i <= 10; i++) { System.out.println(Thread.currentThread().getName() + " i=" + i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } public synchronized void method2() { for (int i = 11; i <= 20; i++) { System.out.println(Thread.currentThread().getName() + " i=" + i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
-
测试类
public class OutClassTest { public static void main(String[] args) { final OutClass.InnerClass innerClass = new OutClass.InnerClass(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { innerClass.method1(); } }, "A"); Thread thread2 = new Thread(new Runnable() { @Override public void run() { innerClass.method2(); } }, "B"); thread1.start(); thread2.start(); } }
运行结果
B i=11 A i=1 B i=12 A i=2 A i=3 B i=13 A i=4 B i=14 A i=5 B i=15 A i=6 B i=16 A i=7 B i=17 B i=18 A i=8 A i=9 B i=19 B i=20 A i=10
注意:由于持有不同的"对象监视器",所以打印的结果也是异步的。
2.2.15 内置类与同步:实验2
本实验测试同步代码块 synchronized(class2) 对 innerclass2 上锁后,其他线程只能以同步的方式调用 innerclass2 中的静态同步方法。
-
创建 OutClass2 类,其中包含静态内置类 InnerClass1 和 InnerClass2
public class OutClass2 { static class InnerClass1 { public void method1(Innerclass2 innerclass2) { String threadName = Thread.currentThread().getName(); synchronized (innerclass2) { System.out.println(threadName + " 进入 InnerClass1 类中的 method1 方法"); for (int i = 0; i < 10; i++) { System.out.println("i=" + i); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(threadName + " 离开 InnerClass1 类中的 method1 方法"); } } public synchronized void method2() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + " 进入 InnerClass1 类中的 method2 方法"); for (int j = 0; j < 10; j++) { System.out.println("j=" + j); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(threadName + " 离开 InnerClass1 类中的 method2 方法"); } } static class Innerclass2 { public synchronized void method1() { String threadName = Thread.currentThread().getName(); System.out.println(threadName + " 进入 InnerClass2 类中的 method1 方法"); for (int k = 0; k < 10; k++) { System.out.println("k=" + k); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(threadName + " 离开 InnerClass2 类中的 method1 方法"); } } }
-
测试类
public class OutClass2Test { public static void main(String[] args) { final OutClass2.InnerClass1 innerClass1 = new OutClass2.InnerClass1(); final OutClass2.Innerclass2 innerclass2 = new OutClass2.Innerclass2(); Thread thread1 = new Thread(new Runnable() { @Override public void run() { innerClass1.method1(innerclass2); } }, "A"); Thread thread2 = new Thread(new Runnable() { @Override public void run() { innerClass1.method2(); } }, "B"); Thread thread3 = new Thread(new Runnable() { @Override public void run() { innerclass2.method1(); } }, "C"); thread1.start(); thread2.start(); thread3.start(); } }
运行结果
A 进入 InnerClass1 类中的 method1 方法 B 进入 InnerClass1 类中的 method2 方法 i=0 j=0 i=1 j=1 j=2 i=2 j=3 i=3 j=4 i=4 j=5 i=5 j=6 i=6 j=7 i=7 j=8 i=8 i=9 j=9 A 离开 InnerClass1 类中的 method1 方法 B 离开 InnerClass1 类中的 method2 方法 C 进入 InnerClass2 类中的 method1 方法 k=0 k=1 k=2 k=3 k=4 k=5 k=6 k=7 k=8 k=9 C 离开 InnerClass2 类中的 method1 方法
2.2.16 锁对象的改变
在将任何数据类型作为同步锁时,需要注意的是,是否有多个线程同时持有锁对象,如果同时持有相同的锁对象,则这些线程之间就是同步的;如果分别获得锁对象,这些线程之间就是异步的。
-
创建一个公共类
public class Service12 { private String lock = "123"; public void testMethod() { try { synchronized (lock) { System.out.println(Thread.currentThread().getName() + " begin " + System.currentTimeMillis()); lock = "456"; Thread.sleep(2000); System.out.println(Thread.currentThread().getName() + " end " + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } } }
-
创建两个自定义的线程类
public class MyThread19 extends Thread{ private Service12 service12; public MyThread19(Service12 service12) { this.service12 = service12; } @Override public void run() { super.run(); service12.testMethod(); } }
public class MyThread19_2 extends Thread{ private Service12 service12; public MyThread19_2(Service12 service12) { this.service12 = service12; } @Override public void run() { super.run(); service12.testMethod(); } }
-
测试类
public class MyThread19Test { public static void main(String[] args) throws InterruptedException { Service12 service12 = new Service12(); MyThread19 myThread19 = new MyThread19(service12); myThread19.setName("A"); MyThread19_2 myThread19_2 = new MyThread19_2(service12); myThread19_2.setName("B"); myThread19.start(); Thread.sleep(500); myThread19_2.start(); } }
运行结果
A begin 1583841197512 B begin 1583841198012 A end 1583841199513 B end 1583841200013
分析:可以看到 A、B 之间是异步的,这是因为 500 毫秒后,线程 B 取得的锁是 “456”
继续实验,注释测试类中的 Thread.sleep(500)
public class MyThread19Test {
public static void main(String[] args) throws InterruptedException {
Service12 service12 = new Service12();
MyThread19 myThread19 = new MyThread19(service12);
myThread19.setName("A");
MyThread19_2 myThread19_2 = new MyThread19_2(service12);
myThread19_2.setName("B");
myThread19.start();
// Thread.sleep(500);
myThread19_2.start();
}
}
运行结果
A begin 1583841999930
A end 1583842001930
B begin 1583842001930
B end 1583842003931
分析:线程 A 和 B 持有的锁都是 “123”,虽然将锁改成了 “456”,但结果还是同步。