本文是我学习Java多线程以及高并发知识的第一本书的学习笔记,
书名是<<Java多线程编程核心技术>>,作者是大佬企业高级项目经理
高洪岩前辈,在此向他致敬。我将配合开发文档以及本书和其他的博客
奉献着的文章来学习,同时做一些简单的总结。有些基础的东西我就不
细分析了,建议以前接触过其他语言多线程或者没有系统学习过多线程
的开发者来看。另外需要注意的是,博客中给出的一些英文文档我就简单
翻译了,重要的部分会详细翻译,要不太浪费时间了,这个是我想提高
自己的英文阅读水平和文档查看能力,想要积攒内功的人可以用有谷歌
翻译自己看文档细读。(中文文档建议只参考,毕竟你懂得...)
详细代码见:github代码地址
本节内容:
1) 出现异常,锁自动释放
2) 同步不具有继承性
3) synchronized同步语句块
synchronized方法的弊端
synchronized代码块间的同步性
将任意对象作为对象监视器
synchronized(非this对象x)同步代码块也可以解决"脏读"问题
7) 出现异常,锁自动释放
当一个线程执行的代码出现异常时,其所持有的锁会自动释放
举个例子:
package chapter02.section01.thread_2_1_7.project_1_throwExceptionNoLock;
public class Service {
synchronized public void testMethod() {
if(Thread.currentThread().getName().equals("a")) {
System.out.println("ThreadName=" + Thread.currentThread().getName()
+ " run beginTime=" + System.currentTimeMillis());
int i = 1;
while(i == 1) {
if(("" + Math.random()).substring(0, 8).equals("0.123456")) {
System.out.println("ThreadName="
+ Thread.currentThread().getName()
+ " run exceptionTime="
+ System.currentTimeMillis());
//将字符串类型数据转化成十进制Integer,这里会报出NumberFormatException异常
Integer.parseInt("a");
}
}
} else {
System.out.println("Thread B run Time="
+ System.currentTimeMillis());
}
}
}
package chapter02.section01.thread_2_1_7.project_1_throwExceptionNoLock;
public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
package chapter02.section01.thread_2_1_7.project_1_throwExceptionNoLock;
public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
package chapter02.section01.thread_2_1_7.project_1_throwExceptionNoLock;
public class Test {
public static void main(String[] args) {
try {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
Thread.sleep(5000);
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
/*
result:
ThreadName=a run beginTime=1539601209770
ThreadName=a run exceptionTime=1539601210053
Exception in thread "a" java.lang.NumberFormatException: For input string: "a"
at java.base/java.lang.NumberFormatException.forInputString(Unknown Source)
at java.base/java.lang.Integer.parseInt(Unknown Source)
at java.base/java.lang.Integer.parseInt(Unknown Source)
at chapter02.section01.thread_2_1_7.project_1_throwExceptionNoLock.Service.testMethod(Service.java:16)
at chapter02.section01.thread_2_1_7.project_1_throwExceptionNoLock.ThreadA.run(ThreadA.java:13)
Thread B run Time=1539601214778
*/
结果分析:
线程a出现异常并释放锁,线程b进入方法正常打印,实验的结论就是出现异常的锁被自动释放了。
8) 同步不具有继承性
同步不可以继承
举个例子:
package chapter02.section01.thread_2_1_8.project_1_synNotExtends;
public class Main {
synchronized public void serviceMethod() {
try {
System.out.println("int main 下一步 sleep begin threadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("int main 下一步 sleep end threadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
package chapter02.section01.thread_2_1_8.project_1_synNotExtends;
public class Sub extends Main{
@Override
public void serviceMethod() {
// synchronized public void serviceMethod() {
try {
System.out.println("int sub 下一步 sleep begin threadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("int sub 下一步 sleep end threadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
super.serviceMethod();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
package chapter02.section01.thread_2_1_8.project_1_synNotExtends;
public class MyThreadA extends Thread{
private Sub sub;
public MyThreadA(Sub sub) {
super();
this.sub = sub;
}
@Override
public void run() {
sub.serviceMethod();
}
}
package chapter02.section01.thread_2_1_8.project_1_synNotExtends;
public class MyThreadB extends Thread {
private Sub sub;
public MyThreadB(Sub sub) {
super();
this.sub = sub;
}
@Override
public void run() {
sub.serviceMethod();
}
}
package chapter02.section01.thread_2_1_8.project_1_synNotExtends;
public class Test {
public static void main(String[] args) {
Sub subRef = new Sub();
MyThreadA a = new MyThreadA(subRef);
a.setName("A");
a.start();
MyThreadA b = new MyThreadA(subRef);
b.setName("B");
b.start();
}
}
/*
Sub子类重写继承父类的serviceMethod()方法加synchronized关键字
result:
int sub 下一步 sleep begin threadName=A time=1539603860854
int sub 下一步 sleep end threadName=A time=1539603865855
int main 下一步 sleep begin threadName=A time=1539603865855
int main 下一步 sleep end threadName=A time=1539603870855
int sub 下一步 sleep begin threadName=B time=1539603870855
int sub 下一步 sleep end threadName=B time=1539603875856
int main 下一步 sleep begin threadName=B time=1539603875856
int main 下一步 sleep end threadName=B time=1539603880856
Sub子类重写继承父类的serviceMethod()方法不加synchronized关键字
result:
int sub 下一步 sleep begin threadName=A time=1539604138579
int sub 下一步 sleep begin threadName=B time=1539604138579
int sub 下一步 sleep end threadName=B time=1539604143580
int sub 下一步 sleep end threadName=A time=1539604143580
int main 下一步 sleep begin threadName=B time=1539604143580
int main 下一步 sleep end threadName=B time=1539604148581
int main 下一步 sleep begin threadName=A time=1539604148581
int main 下一步 sleep end threadName=A time=1539604153581
*/
结果分析:
同步方法是不能被继承的,我们子类重写的方法不加synchronized关键字,是异步执行。加
上之后是同步调用。
2. synchronized同步语句块
使用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一
个长时间的任务,那么B线程则必须等待比较长时间才能进入这个方法。这种情况下可以使用
synchronized同步语句块来解决。synchronized方法是对当前对象进行加锁,而synchronized
代码块是对某一个对象进行加锁。
当两个并发线程访问同一个对象object中的synchronized(this)同步代码块时,一段时间内
只能有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
1) synchronized方法的弊端
举个例子:
package chapter02.section02.thread_2_2_1.project_1_t5;
public class CommonUtils {
public static long beginTime1;
public static long endTime1;
public static long beginTime2;
public static long endTime2;
}
package chapter02.section02.thread_2_2_1.project_1_t5;
public class Task {
private String getData1;
private String getData2;
// public void doLongTimeTask() {
synchronized public void doLongTimeTask() {
try {
System.out.println("begin task");
Thread.sleep(3000);
// String privateGetData1 = "长时间处理任务后从远程返回的值1threadName="
// + Thread.currentThread().getName();
// String privateGetData2 = "长时间处理任务后从远程返回的值2threadName="
// + Thread.currentThread().getName();
// synchronized(this) {
// getData1 = privateGetData1;
// getData2 = privateGetData2;
// }
getData1 = "长时间处理任务后从远程返回的值1threadName="
+ Thread.currentThread().getName();
getData2 = "长时间处理任务后从远程返回的值2threadName="
+ Thread.currentThread().getName();
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
package chapter02.section02.thread_2_2_1.project_1_t5;
public class MyThread1 extends Thread{
private Task task;
public MyThread1(Task task) {
this.task = task;
}
@Override
public void run() {
super.run();
CommonUtils.beginTime1 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime1 = System.currentTimeMillis();
}
}
package chapter02.section02.thread_2_2_1.project_1_t5;
public class MyThread2 extends Thread{
private Task task;
public MyThread2(Task task) {
this.task = task;
}
@Override
public void run() {
super.run();
CommonUtils.beginTime2 = System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime2 = System.currentTimeMillis();
}
}
package chapter02.section02.thread_2_2_1.project_1_t5;
public class Run {
public static void main(String[] args) {
Task task = new Task();
MyThread1 thread1 = new MyThread1(task);
thread1.start();
MyThread2 thread2 = new MyThread2(task);
thread2.start();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long beginTime = CommonUtils.beginTime1;
if(CommonUtils.beginTime2 < CommonUtils.beginTime1) {
beginTime = CommonUtils.beginTime2;
}
long endTime = CommonUtils.endTime1;
if(CommonUtils.endTime2 > CommonUtils.endTime1) {
endTime = CommonUtils.endTime2;
}
System.out.println("耗时: " + ((endTime - beginTime) / 1000));
}
}
/*
带有注释的result:
begin task
长时间处理任务后从远程返回的值1threadName=Thread-0
长时间处理任务后从远程返回的值2threadName=Thread-0
end task
begin task
长时间处理任务后从远程返回的值1threadName=Thread-1
长时间处理任务后从远程返回的值2threadName=Thread-1
end task
耗时: 6
不带注释的result:
begin task
begin task
长时间处理任务后从远程返回的值1threadName=Thread-1
长时间处理任务后从远程返回的值2threadName=Thread-0
长时间处理任务后从远程返回的值1threadName=Thread-0
长时间处理任务后从远程返回的值2threadName=Thread-0
end task
end task
耗时: 3
*/
结果分析:
当一个线程访问object的一个synchronized同步代码块时,另一个线程仍然可以访问该
object对象中的非synchronized(this)同步代码块。
2) synchronized代码块间的同步性
在使用synchronized(this)代码块时,需要注意的是: 当一个线程访问object的一个
synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized
(this)同步代码块的访问将被阻塞,这说明synchronized使用的是一个"对象监视器"
举个例子:
package chapter02.section02.thrread_2_2_5.project_1_doubleSynBlockOneTwo;
public class ObjectService {
public void serviceMethodA() {
try {
synchronized (this) {
System.out.println("A begin time=" + System.currentTimeMillis());
Thread.sleep(2000);
System.out.println("A end end=" + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void serviceMethodB() {
synchronized (this) {
System.out.println("B begin time=" + System.currentTimeMillis());
System.out.println("B end end=" + System.currentTimeMillis());
}
}
}
package chapter02.section02.thrread_2_2_5.project_1_doubleSynBlockOneTwo;
public class ThreadA extends Thread {
private ObjectService service;
public ThreadA(ObjectService service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.serviceMethodA();
}
}
package chapter02.section02.thrread_2_2_5.project_1_doubleSynBlockOneTwo;
public class ThreadB extends Thread {
private ObjectService service;
public ThreadB(ObjectService service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.serviceMethodA();
}
}
package chapter02.section02.thrread_2_2_5.project_1_doubleSynBlockOneTwo;
public class Run {
public static void main(String[] args) {
ObjectService service = new ObjectService();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
}
}
/*
result:
A begin time=1539609986341
A end end=1539609988343
A begin time=1539609988343
A end end=1539609990343
*/
3) 验证同步synchronized(this)代码块是锁定当前对象的。
package chapter02.section02.thread_2_2_6.project_1_t8;
public class Task {
// synchronized public void otherMethod() {
public void otherMethod() {
System.out.println("------------------run--otherMethod");
}
public void doLongTimeTask() {
synchronized(this) {
for(int i = 0; i < 10000; i++) {
System.out.println("synchronized threadName="
+ Thread.currentThread().getName() + " i="
+ (i + 1));
}
}
}
}
package chapter02.section02.thread_2_2_6.project_1_t8;
public class MyThread1 extends Thread {
private Task task;
public MyThread1(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
task.doLongTimeTask();
}
}
package chapter02.section02.thread_2_2_6.project_1_t8;
public class MyThread2 extends Thread {
private Task task;
public MyThread2(Task task) {
super();
this.task = task;
}
@Override
public void run() {
super.run();
task.otherMethod();
}
}
package chapter02.section02.thread_2_2_6.project_1_t8;
public class Run {
public static void main(String[] args) throws InterruptedException {
Task task = new Task();
MyThread1 thread1 = new MyThread1(task);
thread1.start();
Thread.sleep(10);
MyThread2 thread2 = new MyThread2(task);
thread2.start();
}
}
/*
有synchronized方法otherMethod()的result:
.......................................
synchronized threadName=Thread-0 i=9998
synchronized threadName=Thread-0 i=9999
synchronized threadName=Thread-0 i=10000
------------------run--otherMethod
没有synchronized方法otherMethod()的result:
.......................................
synchronized threadName=Thread-0 i=602
synchronized threadName=Thread-0 i=603
------------------run--otherMethod
synchronized threadName=Thread-0 i=604
synchronized threadName=Thread-0 i=605
......................................
*/
4) 将任意对象作为对象监视器
多个线程调用同一个对象中的不同名称的synchronized同步方法或synchronized(this)
同步代码块时,调用的效果就是按顺序执行,也就是同步的、阻塞的。
两者分别的作用:
(1) synchronized同步方法
1) 对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态
2) 同一时间只有一个线程可以执行synchronized同步方法中的代码
(2) synchronized同步代码块
1) 对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态
2) 同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码
Java还支持对"任意对象作为"对象监视器"来实现同步的功能。这个"任意对象"大多数
是实例变量及方法的参数。使用格式为synchronized(非this对象)
(3) synchronized(非this对象x)
1) 在多个线程持有"对象监视器"为同一对象的前提下,同一时间只有一个线程可以执
行synchronized(非this对象x)同步代码块中的代码
2) 当持有"对象监视器"为同一个对象的前提下,同一时间只有一个线程可以执行
synchronized(非this对象x)同步代码块中的代码。
举个例子验证第一点:
package chapter02.section02.thread_2_2_7.project_1_synBlockString;
public class Service {
private String usernameParam;
private String passwordParam;
private String anyString = new String();
public void setUsernamePassword(String username,
String password) {
try {
// String anyString = new String();
synchronized(anyString) {
System.out.println("线程名称为: " + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "进入同步块");
usernameParam = username;
Thread.sleep(3000);
passwordParam = password;
System.out.println("线程名称为: " + Thread.currentThread().getName()
+ "在" + System.currentTimeMillis() + "离开同步快");
}
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
package chapter02.section02.thread_2_2_7.project_1_synBlockString;
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.setUsernamePassword("a", "aa");
}
}
package chapter02.section02.thread_2_2_7.project_1_synBlockString;
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.setUsernamePassword("b", "b");
}
}
package chapter02.section02.thread_2_2_7.project_1_synBlockString;
public class Run {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
}
}
/*
当anyString是setUsernamePassword中的局部变量的时候
异步执行
result:
线程名称为: A在1539617319796进入同步块
线程名称为: B在1539617319797进入同步块
线程名称为: A在1539617322796离开同步快
线程名称为: B在1539617322797离开同步快
当anyString是Service类中的实例变量的时候
同步执行
result:
线程名称为: A在1539656559451进入同步块
线程名称为: A在1539656562451离开同步快
线程名称为: B在1539656562451进入同步块
线程名称为: B在1539656565452离开同步快
*/
结果分析:
当anyString是实例变量的时候,synchronized(anyString)是锁同一对象,因此
需要排队执行,而anyString是在方法中的时候,是局部变量,每个线程都有自己的
局部变量anyString,因此锁不同,异步执行。
锁非this对象具有一定的优点: 如果在一个类中有很多个synchronized方法,这时虽
然能实现同步,但会受到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象
,则synchronized(非this对象)代码块中的代码块中的程序与同步方法是异步的,不与
其他锁this同步方法争抢this锁,则大大提高运行效率。
验证使用"synchronized(非this对象x)同步代码块"格式时,持有不同的对象监视器是
异步的效果。
举个例子:
package chapter02.section02.thread_2_2_7.project_1_synBlockString2;
public class Service {
private String anyString = new String();
public void a() {
try {
synchronized(anyString) {
System.out.println("a begin");
Thread.sleep(3000);
System.out.println("a end");
}
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
synchronized public void b() {
System.out.println("b begin");
System.out.println("b end");
}
}
package chapter02.section02.thread_2_2_7.project_1_synBlockString2;
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.a();
}
}
package chapter02.section02.thread_2_2_7.project_1_synBlockString2;
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
super.run();
service.b();
}
}
package chapter02.section02.thread_2_2_7.project_1_synBlockString2;
public class Run {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
}
}
/*
result:
a begin
b begin
b end
a end
*/
结果分析:
由于对象监视器不同,所以运行结果就是异步的
5) synchronized(非this对象x)同步代码块也可以解决"脏读"问题
实验: 验证多个线程调用同一个方法是随机的
package chapter02.section02.thread_2_2_7.project_3_syn_Out_asyn;
import java.util.ArrayList;
import java.util.List;
public class MyList {
private List<String> list = new ArrayList<>();
synchronized public void add(String username) {
System.out.println("ThreadName=" + Thread.currentThread().getName()
+ "执行了add方法!");
list.add(username);
System.out.println("ThreadName=" + Thread.currentThread().getName()
+ "退出了add方法!");
}
synchronized public int getSize() {
System.out.println("TheadName=" + Thread.currentThread().getName()
+ "执行了getSize方法!");
int sizeValue = list.size();
System.out.println("ThreadName=" + Thread.currentThread().getName()
+ "退出了getSize方法!");
return sizeValue;
}
}
package chapter02.section02.thread_2_2_7.project_3_syn_Out_asyn;
public class MyThreadA extends Thread{
private MyList list;
public MyThreadA(MyList list) {
super();
this.list = list;
}
@Override
public void run() {
for(int i = 0; i < 100000; i++) {
list.add("threadA" + (i + 1));
}
}
}
package chapter02.section02.thread_2_2_7.project_3_syn_Out_asyn;
public class MyThreadB extends Thread{
private MyList list;
public MyThreadB(MyList list) {
super();
this.list = list;
}
@Override
public void run() {
for(int i = 0; i < 100000; i++) {
list.add("threadB" + (i + 1));
}
}
}
package chapter02.section02.thread_2_2_7.project_3_syn_Out_asyn;
public class Test {
public static void main(String[] args) {
MyList mylist = new MyList();
MyThreadA a = new MyThreadA(mylist);
a.setName("A");
a.start();
MyThreadB b = new MyThreadB(mylist);
b.setName("B");
b.start();
}
}
/*
result:
ThreadName=B退出了add方法!
ThreadName=A执行了add方法!
ThreadName=A退出了add方法!
ThreadName=B执行了add方法!
ThreadName=B退出了add方法!
ThreadName=A执行了add方法!
ThreadName=A退出了add方法!
ThreadName=B执行了add方法!
ThreadName=B退出了add方法!
*/
结果分析:
同步快中的代码是同步打印的,当前线程的"执行"和"退出"是成对出现的。但线程A和线程
B的执行却是异步的,这就有可能出现脏读的环境。由于线程执行方法的顺序不确定,所以当
A和B两个线程执行带有分支判断的方法时,就会出现逻辑上的错误,有可能出现脏读。
实验: 重现脏读
package chapter02.section02.thread_2_2_7.project_4_t9;
import java.util.ArrayList;
import java.util.List;
public class MyOneList {
private List<String> list = new ArrayList<>();
synchronized public void add(String data) {
list.add(data);
}
synchronized public int getSize() {
return list.size();
}
}
package chapter02.section02.thread_2_2_7.project_4_t9;
public class MyService {
public MyOneList addServiceMethod(MyOneList list, String data) {
try {
// synchronized (list) {
// if(list.getSize() < 1) {
// Thread.sleep(2000); //模拟从远程花费2秒取回数据
// list.add(data);
// }
// }
if(list.getSize() < 1) {
Thread.sleep(2000);
list.add(data);
}
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
return list;
}
}
package chapter02.section02.thread_2_2_7.project_4_t9;
public class MyThread1 extends Thread{
private MyOneList list;
public MyThread1(MyOneList list) {
super();
this.list = list;
}
@Override
public void run() {
MyService msRef = new MyService();
msRef.addServiceMethod(list, "A");
}
}
package chapter02.section02.thread_2_2_7.project_4_t9;
public class MyThread2 extends Thread{
private MyOneList list;
public MyThread2(MyOneList list) {
super();
this.list = list;
}
@Override
public void run() {
MyService msRef = new MyService();
msRef.addServiceMethod(list, "B");
}
}
package chapter02.section02.thread_2_2_7.project_4_t9;
public class Run {
public static void main(String[] args) throws InterruptedException {
MyOneList list = new MyOneList();
MyThread1 thread1 = new MyThread1(list);
thread1.setName("A");
thread1.start();
MyThread2 thread2 = new MyThread2(list);
thread2.setName("B");
thread2.start();
Thread.sleep(6000);
System.out.println("listSize=" + list.getSize());
}
}
/*
不添加synchronized同步语句块出现脏读:
listSize=2
添加synchronized同步语句块正常执行:
listSize=1
*/
结果分析:
脏读出现的原因是两个线程以异步的方式返回list参数的size()大小。解决办法就是同步化。