非线程安全 – 在多个线程对同一对象中的实例变量进行并发访问时产生产生的后果就是脏读,取到的数据被修改过了。
线程安全 – 就是获得的值都是同步处理过的。不会出现脏读
需要注意的是:非线程安全问题只是存在于“实例变量”(多指类里的属性字段)中,如果访问的数据是一个方法内部的私有变量(临时变量或方法内部声明的变量),那就没有安全问题了。
-synchronized 同步方法
两个线程同时访问一个同一个对象中的方法时,加锁 synchronized 之后让方法变成同步的才能是线程安全的。
- 多个对象多个锁
HasSelfPrivateNum类:
public class HasSelfPrivateNum {
private int num=0;
synchronized 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){
e.printStackTrace();
}
}
}
线程类ThreadA和ThreadB:
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");
}
}
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");
}
}
Run类,执行Main方法:
public class Run {
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!
b num=200
a num=100
上面两个线程分别访问同一个类的两个不同实例的相同名称的同步方法,但是效果是以异步的方式运行的,也就是说应该是两个a或者两个b挨着输出,但是这里的结果就是相当于a,b之间没有锁。其实 a,b之间确实没有锁。
原因是:示例创建了2个业务对象numRef1和numRef2,在系统中产生了2个锁,所以运行结果是异步的,关键字 synchronized取得的锁是对象锁(numRef1、2标识),而不是把一段代码或方法(函数)当做锁,如果多个线程访问的是多个对象,则JVM会创建多个锁,互不影响。 也就是说在a,b之间是不同的锁,a访问的时候自然看不见b的锁。
但是如果在静态方法上加synchronized关键字,表示锁定类级别的锁,独占class类,这时候多个线程访问的是相同的锁。打印就是:
a set over! // a,b都是连着输出
a num=100
b set over!
b num=200
或者
b set over!
b num=200
a set over!
a num=100
-锁对象
重新改造下上面业务类:分别给一个类的两个方法加锁
HasSelfPrivateNum类:
public class HasSelfPrivateNum {
private int num=0;
synchronized public void funA(){
try{
System.out.println("a set over!");
Thread.sleep(2000);
System.out.println("a end!");
}catch (InterruptedException e){
e.printStackTrace();
}
}
public void funB(){ // 前面没有加锁
try{
System.out.println("B set over!");
Thread.sleep(2000);
System.out.println("B end!");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
在线程A,B中分别在run()方法里调用 funA 和 funcB
main 调用变成:
public static void main(String[] args){
HasSelfPrivateNum numRef=new HasSelfPrivateNum();// 创建1个业务对象!!!
ThreadA athread=new ThreadA(numRef); // 两个线程类用同一个业务对象实例化
ThreadB bthread=new ThreadB(numRef);
athread.start();
bthread.start();
}
打印:
a set over
b set over
b end
a end
结果是异步的,说明线程A虽然先持有了numRef对象的锁,但是线程B完全可以调用非加锁的方法!
如果在funB前面加锁,打印的就是同步的,因为这个跟上面不同,这次两个线程是用同一个对象实例化的,两个线程拥有的是同一个numRef对象的锁 。 所以A在先拥有numRef对象锁的时候,B是不能访问funB的,尽管A没有访问funB。这说明锁确实锁的是对象,而跟方法前面声明没有关系。