【java多线程编程核心技术】-- 第二章:对象及变量的并发访问(上)
第二章总结
本章主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题,多线程中的同步问题是学习多线程的重中之重。
第二章知识点
线程安全和非线程安全
非线程安全会在多个线程对同一个对象的实例变量进行进行并发访问时产生,产生的后果就是“脏读”,也就是取到的数据是被更改过的;线程安全就是获得的实例变量的值是经过同步处理的,不会出现脏读的现象。
synchronized同步方法
方法内的变量是线程安全的
方法中的变量不存在线程安全问题,因为方法内部的变量是私有的,永远都是线程安全的。
线程A:
public class ThreadA extends Thread {
private HasSelfPrivateNum numRef;
public ThreadA(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("a");
}
}
线程B:
public class ThreadB extends Thread {
private HasSelfPrivateNum numRef;
public ThreadB(HasSelfPrivateNum numRef) {
super();
this.numRef = numRef;
}
@Override
public void run() {
super.run();
numRef.addI("b");
}
}
HasSelfPrivateNum类:
public class HasSelfPrivateNum {
public void addI(String username) {
private int num = 0; //方法内的变量是线程安全的
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
main方法:
public static void main(String[] args) {
HasSelfPrivateNum numRef = new HasSelfPrivateNum();
ThreadA athread = new ThreadA(numRef);
athread.start();
ThreadB bthread = new ThreadB(numRef);
bthread.start();
//输出结果为:说明方法内的变量是线程安全的
/*
a set over!
b set over!
a num=100
b num=200
*/
}
实例变量非线程安全
两个线程同时操作业务对象中的实例变量时,可能会出现非线程安全
上面的HasSelfPrivateNum类改为:
public class HasSelfPrivateNum {
private int num = 0; //变量申明为实例变量
public void addI(String username) { //此处未同步,会发生"非线程安全"问题
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果为
a set over!
b set over!
b num=200
a num=200
将HasSelfPrivateNum类修改为
public class HasSelfPrivateNum {
private int num = 0; //变量申明为实例变量
synchronized public void addI(String username) { //此处加了synchronized关键字
try {
if (username.equals("a")) {
num = 100;
System.out.println("a set over!");
Thread.sleep(2000);
} else {
num = 200;
System.out.println("b set over!");
}
System.out.println(username + " num=" + num);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果为
a set over!
a num=100
b set over!
b num=200
多个对象多个锁
main方法修改为:
public static void main(String[] args) {
HasSelfPrivateNum numRef1 = new HasSelfPrivateNum();
HasSelfPrivateNum numRef2 = new HasSelfPrivateNum();
ThreadA athread = new ThreadA(numRef1);
athread.start();
ThreadB bthread = new ThreadB(numRef2);
bthread.start();
//输出结果为:说明方法内的变量是线程安全的
/*
a set over!
b set over!
a num=100
b num=200
*/
}
运行结果为
a set over!
b set over!
b num=200
a num=100
//两个线程分别访问同一个类中的两个不同实例的相同名称的同步方法,
//效果是以异步的方式运行的
程序运行结果解析:关键字synchronized取得的锁都是对象锁,而不是把一段代码方法(函数)当作锁,所以在上面的示例中,哪个线程先执行带有synchronized关键字的方法,哪个线程就持久该方法所属对象的锁Lock,那么其它线程只能呈等待状态,前提是多个线程访问的是同一个对象。
synchronized方法与锁对象
验证线程锁的是对象:
MyObject类:有同步方法和非同步方法
线程A:
public class ThreadA extends Thread {
private MyObject myObject;
public ThreadB(MyObject myObject) {
super();
this.myObject= myObject;
}
@Override
public void run() {
super.run();
myObject.methodA();
}
}
线程B:
public class ThreadB extends Thread {
private MyObject myObject;
public ThreadB(MyObject myObject) {
super();
this.myObject= myObject;
}
@Override
public void run() {
super.run();
myObject.methodA();
}
}
main方法:
public static void main(String[] args) {
MyObject myObject= new MyObject();
ThreadA athread = new ThreadA(myObject);
athread.setName("A");
ThreadB bthread = new ThreadB(myObject);
bthread .setName("B");
athread.start();
bthread.start();
//输出结果如下:
/*
begin methodA threadName=A
end endTime = 1403574891268
begin methodB threadName=B
end endTime = 1403574896268
*/
}
结论:调用关键字synchronized声明的方法一定是排队运行的,但是需要记住“共享”这两个字,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么就根本没有同步的必要。
那么其它方法被调用时是什么效果:
线程B改为:
public class ThreadB extends Thread {
private MyObject myObject;
public ThreadB(MyObject myObject) {
super();
this.myObject= myObject;
}
@Override
public void run() {
super.run();
myObject.methodB();
}
}
结果为:
begin methodA threadName=A
begin methodB threadName=B begin time = 1403574891268
end
end endTime = 1403574896268
结论:
1.A线程先持有Object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
2.A线程先持有Object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。
脏读
发生脏读的情况是在读取实例变量时,此值已经被其它线程更改过了
public class PublicVar {
public String username = "A";
public String password = "AA";
synchronized public void setValue(String username, String password) {
try {
this.username = username;
Thread.sleep(5000);
this.password = password;
System.out.println("setValue method thread name="
+ Thread.currentThread().getName() + " username="
+ username + " password=" + password);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// synchronized public void getValue() {
public void getValue() {//未加synchronized关键字,造成脏读
System.out.println("getValue method thread name="
+ Thread.currentThread().getName() + " username=" + username
+ " password=" + password);
}
}
线程A:
public class ThreadA extends Thread {
private PublicVar publicVar;
public ThreadA(PublicVar publicVar) {
super();
this.publicVar = publicVar;
}
@Override
public void run() {
super.run();
publicVar.setValue("B", "BB");
}
}
main主函数:
public static void main(String[] args) {
try {
PublicVar publicVarRef = new PublicVar();
ThreadA thread = new ThreadA(publicVarRef);
thread.start();
Thread.sleep(200);// 打印结果受此值大小影响
publicVarRef.getValue();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
输出结果:
未在getValue()方法前加入synchronized,输出结果:
getValue method thread name=main username=B password=AA
setValue method thread name=Thread-0 username=B password=BB
出现了脏读的情况
在方法getValue()前加入加入syncchronized关键字后
加入synchronized关键字后,输出结果:
setValue method thread name=Thread-0 username=B password=BB
getValue method thread name=main username=B password=BB
结论:
1.当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确的讲就是获得了对象的锁,所以其他线程必须等A线程执行完毕后才可以调用X方法,但B线程可以随意调用其它的非synchronized方法。
2.当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确的讲就是获得了对象的锁,所以其他线程必须等A线程执行完毕后才可以调用X方法,B线程如果调用了声明了synchronized关键字的非X方法时,必须等待A线程将X方法执行完,也就是释放对象锁后才可以调用。
synchronized锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。
Service类
public class Service {
synchronized public void service1(){
System.out.println("service1");
service2();
}
synchronized public void service2(){
System.out.println("service2");
service3();
}
synchronized public void service3(){
System.out.println("service3");
}
}
MyThread类
public class MyThread extends Thread {
@Override
public void run() {
Service service = new Service();
service .service1();
}
}
main主函数:
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
输出结果:
service1
service2
service3
说明可以再次获取自己的内部锁,如果锁不可重入的话,就会造成死锁。
出现异常时锁自动释放
当一个线程出现异常时,其所持有的锁会自动释放
同步不具有继承性
子类继承了父类时,但是同步的方法不会继承,如果想要实现子类的方法也有同步的作用,必须也在子类上加上synchronized关键字实现方法同步。