Java多线程学习笔记9之对象及变量的并发访问
本文是我学习Java多线程以及高并发知识的第一本书的学习笔记,
书名是<<Java多线程编程核心技术>>,作者是大佬企业高级项目经理
高洪岩前辈,在此向他致敬。我将配合开发文档以及本书和其他的博客
奉献着的文章来学习,同时做一些简单的总结。有些基础的东西我就不
细分析了,建议以前接触过其他语言多线程或者没有系统学习过多线程
的开发者来看。另外需要注意的是,博客中给出的一些英文文档我就简单
翻译了,重要的部分会详细翻译,要不太浪费时间了,这个是我想提高
自己的英文阅读水平和文档查看能力,想要积攒内功的人可以用有谷歌
翻译自己看文档细读。(中文文档建议只参考,毕竟你懂得...)
详细代码见:github代码地址
本节内容:
1) 多线程死锁条件
2) 成员内部类和静态内部类同步
3) 锁对象改变和锁对象属性的改变
1.多线程的死锁
Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放
的锁,从而导致所有的任务都无法继续完成。在多线程技术中,"死锁"是必须避免的,
因为这会造成线程的"假死"
Java死锁产生的四个必要条件:
1) 互斥使用
即当资源被一个线程使用(占有)时,别的线程不能使用
2) 不可抢占
资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放
3) 请求和保持
即当资源的请求者在请求其他的资源的同时保持对原有资源的持有
4) 循环等待
即存在一个等待队列: p1占有p2的资源,p2占有p3的资源,p3占有p1的资源。这样
就形成了一个等待环路。
当上述四个条件都成立,便形成死锁。
举个例子:
package chapter02.section02.thread_2_2_12.project_1_deadLockTest;
public class DealThread implements Runnable{
public String username;
public Object lock1 = new Object();
public Object lock2 = new Object();
public void setFlag(String username) {
this.username = username;
}
@Override
public void run() {
if(username.equals("a")) {
synchronized(lock1) {
try {
System.out.println("username = " + username);
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
synchronized(lock2) {
System.out.println("按lock1->lock2代码顺序执行了");
}
}
}
if(username.equals("b")) {
synchronized(lock2) {
try {
System.out.println("username = " + username);
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
synchronized(lock1) {
System.out.println("按lock2->lock2代码顺序执行了");
}
}
}
}
}
package chapter02.section02.thread_2_2_12.project_1_deadLockTest;
public class Run {
public static void main(String args[]) {
try {
DealThread t1 = new DealThread();
t1.setFlag("a");
Thread thread1 = new Thread(t1);
thread1.start();
Thread.sleep(100);
t1.setFlag("b");
Thread thread2 = new Thread(t1);
thread2.start();
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
/*
result:
username = a
username = b
*/
结果分析:
可以看到死锁发生了
可以使用JDk自带的工具来检测是否有死锁的现象
可以看到运行的线程的tid值是8376
可以使用jstack命令来查看Java线程的调用堆栈的,可以用来分析线程问题(如死锁)
运行Jstack -l 8376查看死锁的具体情况
可以看到Thread-0和Thread-1拥有的锁以及正在等待要持有的锁,发现Thread-1要申
请的锁与Thread-0持有的锁是一样的,这是造成死锁的原因,是根据死锁的第二个条件
来判断的。
本实验使用synchronized嵌套的代码结构来实现死锁,其实不使用synchronized代码结
构也会出现死锁,与嵌套不嵌套没有任何的关系,只要互相等待对方释放锁就有可能出现
死锁
2. 内置类(成员内部类)与静态内置类
成员内部类:
内部类中最常见的就是成员内部类,也称为普通内部类。作为外部类的一个成员存在,
与外部类的属性、方法并列。
举例:
package chapter02.section02.thread_2_2_13.project_1_innerClass;
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;
}
}
package chapter02.section02.thread_2_2_13.project_1_innerClass;
//import chapter02.section02.thread_2_2_13.project_1_innerClass.PublicClass.PrivateClass;
public class Run {
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());
}
}
/*
result:
usernameValue passwordValue
ageValue addressValue
*/
静态内部类:
静态内部类可以使用public,protected,private修饰.静态内部类中可以定义静
态和非静态的成员
举个例子:
package chapter02.section02.thread_2_2_13.project_2_innerStaticClass;
public class PublicClass {
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;
}
}
package chapter02.section02.thread_2_2_13.project_2_innerStaticClass;
public class Run {
public static void main(String[] args) {
PublicClass publicClass = new PublicClass();
publicClass.setUsername("usernameValue");
publicClass.setPassword("passwordValue");
System.out.println(publicClass.getUsername() + " "
+ publicClass.getPassword());
/**
* 静态内部类,它是不需要依赖于外部类的,并且它
* 不能使用外部类的非static成员变量或方法
*/
PublicClass.PrivateClass privateClass = new PublicClass.PrivateClass();
privateClass.setAge("ageValue");
privateClass.setAddress("addressValue");
privateClass.setAddress("addressValue");
}
}
/*
result:
usernameValue passwordValue
ageValue addressValue
*/
(1) 内置类与同步实验1
实验中在内置类中有两个同步方法,但使用的却是不同的锁,打印的结果是异步的。
package chapter02.section02.thread_2_2_14.project_1_innerTest1;
public class OutClass {
static class Inner{
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) {
// TODO: handle exception
e.printStackTrace();
}
}
}
}
public synchronized void method2() {
for(int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + " i="
+ i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
}
}
package chapter02.section02.thread_2_2_14.project_1_innerTest1;
import chapter02.section02.thread_2_2_14.project_1_innerTest1.OutClass.Inner;
public class Run {
public static void main(String[] args) {
final Inner inner = new Inner();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
inner.method1();
}
}, "A");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
inner.method2();
}
}, "B");
t1.start();
t2.start();
}
}
/*
可以看到结果是异步执行的,因为线程持有不同的"对象监视器"
result:
......
B i=8
A i=8
A i=9
B i=9
A i=10
......
*/
(2) 内置类与同步实验2
测试同步代码块synchronized(class2)对class2上锁后,其他线程只能以同步的方式
调用class2中的synchronized同步方法
package chapter02.section02.thread_2_2_15.project_1_innerTest2;
public class OutClass {
static class InnerClass1{
public void method1(InnerClass2 class2) {
String threadName = Thread.currentThread().getName();
synchronized(class2) {
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) {
}
}
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) {
}
}
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) {
}
}
System.out.println(threadName + " 离开InnerClass2类中的method1方法");
}
}
}
package chapter02.section02.thread_2_2_15.project_1_innerTest2;
import chapter02.section02.thread_2_2_15.project_1_innerTest2.OutClass.InnerClass1;
import chapter02.section02.thread_2_2_15.project_1_innerTest2.OutClass.InnerClass2;
public class Run {
public static void main(String[] args) {
final InnerClass1 in1 = new InnerClass1();
final InnerClass2 in2 = new InnerClass2();
Thread t1 = new Thread(new Runnable() {
public void run() {
in1.method1(in2);
}
}, "T1");
Thread t2 = new Thread(new Runnable() {
public void run() {
in1.method2();
}
}, "T2");
Thread t3 = new Thread(new Runnable() {
public void run() {
in2.method1();
}
}, "T3");
t1.start();
t2.start();
t3.start();
}
}
/*
result:
T1 进入InnerClass1类中的method1方法
i=0
T2 进入InnerClass1类中的method2方法
j=0
j=1
i=1
i=2
j=2
i=3
j=3
j=4
i=4
j=5
i=5
i=6
j=6
j=7
i=7
j=8
i=8
i=9
j=9
T2 离开InnerClass1类中的method2方法
T1 离开InnerClass1类中的method1方法
T3 进入InnerClass2类中的method1方法
k=0
k=1
k=2
k=3
k=4
k=5
k=6
k=7
k=8
k=9
T3 离开InnerClass2类中的method1方法
*/
3. 锁对象的改变
在将任何类型数据作为同步锁时,需要注意的是,是否有多个线程同时持有锁对象,如
果同时持有相同的锁对象,则这些线程之间就是同步的;如果分别获得锁对象,这些线程
之间就是异步的。
举例:
package chapter02.section02.thread_2_2_16.project_1_setNewStringTwoLock;
public class MyService {
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) {
// TODO: handle exception
e.printStackTrace();
}
}
}
package chapter02.section02.thread_2_2_16.project_1_setNewStringTwoLock;
public class ThreadA extends Thread {
private MyService service;
public ThreadA(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
package chapter02.section02.thread_2_2_16.project_1_setNewStringTwoLock;
public class ThreadB extends Thread{
private MyService service;
public ThreadB(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
service.testMethod();
}
}
package chapter02.section02.thread_2_2_16.project_1_setNewStringTwoLock;
public class Run1 {
public static void main(String[] args) throws InterruptedException{
MyService service = new MyService();
ThreadA a = new ThreadA(service);
a.setName("A");
ThreadB b = new ThreadB(service);
b.setName("B");
a.start();
Thread.sleep(50);
b.start();
}
}
/*
A begin 1539932674955
B begin 1539932675007
A end 1539932676958
B end 1539932677008
*/
package chapter02.section02.thread_2_2_16.project_1_setNewStringTwoLock;
public class Run2 {
public static void main(String[] args) throws InterruptedException{
MyService service = new MyService();
ThreadA a = new ThreadA(service);
a.setName("A");
ThreadB b = new ThreadB(service);
b.setName("B");
a.start();
b.start();
}
}
/*
result:
A begin 1539932865275
A end 1539932867283
B begin 1539932867283
B end 1539932869294
*/
结果分析:
Run1类中运行结果是异步执行,因为50ms之后线程B取得的锁是"456"
Run2类中运行结果是同步执行,因为线程A、B持有的锁都是"123",
虽然将锁改成了"456",但结果还是同步的,因为A、B共同争抢的是"123"
注: 只有对象不变,即使对象的属性被改变,运行的结果还是同步的
举例:
package chapter02.section02.thread_2_2_16.project_2_setNewPropertiesLockOne;
public class Userinfo {
private String username;
private String password;
public Userinfo() {
super();
}
public Userinfo(String username, String password) {
super();
this.username = username;
this.password = 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;
}
}
package chapter02.section02.thread_2_2_16.project_2_setNewPropertiesLockOne;
public class MyService {
public void serviceMethodA(Userinfo userinfo) {
synchronized (userinfo) {
try {
System.out.println(Thread.currentThread().getName());
userinfo.setUsername("abcabcabc"); //对象属性改变
Thread.sleep(3000);
System.out.println("end! time=" + System.currentTimeMillis());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
package chapter02.section02.thread_2_2_16.project_2_setNewPropertiesLockOne;
public class ThreadA extends Thread {
private MyService service;
private Userinfo userinfo;
public ThreadA(MyService service, Userinfo userinfo) {
super();
this.service = service;
this.userinfo = userinfo;
}
@Override
public void run() {
service.serviceMethodA(userinfo);
}
}
package chapter02.section02.thread_2_2_16.project_2_setNewPropertiesLockOne;
public class ThreadB extends Thread{
private MyService service;
private Userinfo userinfo;
public ThreadB(MyService service, Userinfo userinfo) {
super();
this.service = service;
this.userinfo = userinfo;
}
@Override
public void run() {
service.serviceMethodA(userinfo);
}
}
package chapter02.section02.thread_2_2_16.project_2_setNewPropertiesLockOne;
public class Run {
public static void main(String[] args) {
try {
MyService service = new MyService();
Userinfo userinfo = new Userinfo();
ThreadA a = new ThreadA(service, userinfo);
a.setName("a");
a.start();
Thread.sleep(50);
ThreadB b = new ThreadB(service, userinfo);
b.setName("b");
b.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/*
可以看到对象不变,属性变化时同步效果
时间相差3000毫秒
result:
a
end! time=1539933771256
b
end! time=1539933774256
*/