所谓“非线程安全”其实会发生在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的结果就是“脏读”,也就是取到的数据其实时被哼该过的,而“线程安全”就是获得的实体变量的值时经过同步处理的,不会出现脏读的现象。
方法内的变量为线程安全
方法内的变量为线程安全,“非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全问题”。
public class HasSelfPrivateNum {
public void addI(String username){
try{
int num = 0;
if(username.equals("a")){
num = 100;
System.err.println("a set over !");
} else{
num = 200;
System.err.println("b set over !");
}
System.err.println( username + "num = "+ num);
}catch(Exception e){
e.printStackTrace();
}
}
}
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");
}
}
public class Run {
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 !
bnum = 200
anum = 100
可见方法中的变量不存在非线程安全的问题,永远都是线程安全的。这是方法内部的变量是私有的特性造成的。
实例变量非线程安全
多个线程访问同一个对象的实例变量则有可能出现“非线程安全”问题。用线程访问对象中的多个实例变量,则运行的结果有可能出现交叉的情况,多次运行以上代码其实可以发现,这是很正常的。如果对象仅有一个实例变量,则有可能会出现覆盖的情况。
public class HasSelfPrivateNum {
private int num = 0;
public void addI(String username){
try{
if(username.equals("a")){
num = 100;
System.err.println("a set over !");
Thread.sleep(3000);
} else{
num = 200;
System.err.println("b set over !");
}
System.err.println( username + "num = "+ num);
}catch(Exception e){
e.printStackTrace();
}
}
}
结果:
a set over !
b set over !
bnum = 200
anum = 200
如果避免这种情况,仅仅是需要在addI方法声明时加上synchronized。
多个对象多个锁
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 !
bnum = 200
anum = 100
关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,所以上面的实例中,那个线程先执行带synchronized关键字的方法,那个线程就持有该方法所属对象的锁Lock,其他线程只能呈等待状态,前提时多个线程访问的是同一个对象,但如果多个线程访问多个对象,则JVM会创建多个锁。就比如上面代码,就创建了2个锁。
synchronized方法与锁对象
public class MyObject {
public void methodA(){
try{
System.err.println("begin methodA threadName = " + Thread.currentThread().getName());
Thread.sleep(5000);
System.err.println("end" + Thread.currentThread().getName());
}catch(Exception e){
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private MyObject object;
public ThreadA (MyObject object){
super();
this.object = object;
}
public void run(){
super.run();
object.methodA();
}
}
public class ThreadB extends Thread{
private MyObject object;
public ThreadB (MyObject object){
this.object = object;
}
@Override
public void run(){
//super.run();
object.methodA();
}
}
public class Run {
public static void main(String [] args){
MyObject object = new MyObject();
ThreadA a = new ThreadA(object);
a.setName("A");
ThreadB b = new ThreadB(object);
b.setName("B");
a.start();
b.start();
}
}
结果:
begin methodA threadName = A
begin methodA threadName = B
endA
endB
加上synchronized关键字之后的执行结果为:
begin methodA threadName = A
endA
begin methodA threadName = B
endB
或者
begin methodA threadName = B
endB
begin methodA threadName = A
endA
其实我们只需要记住,只有共享资源的读写访问才需要同步化,如果不是共享资源,也就没有同步的必要。
下面我们看一下非synchronized方法:
在MyObject类中添加一下方法,同时线程B调用methodB方法:
public void methodB(){
try{
System.err.println("begin methodB threadName = " + Thread.currentThread().getName() + "begin time : "+ System.currentTimeMillis());
Thread.sleep(5000);
System.err.println("end" + Thread.currentThread().getName());
}catch(Exception e){
e.printStackTrace();
}
}
运行结果如下:
begin methodA threadName = A
begin methodB threadName = Bbegin time : 1543912125335
endB
end1543912130336
由运行结果我们可以看到,多线程访问同一个对象时,在没有竞争到该对象锁的时候,是可以调用该实例的非 同步方法的
将methodB方法加锁之后,运行结果如下:
begin methodB threadName = Bbegin time : 1543912570712
endB
begin methodA threadName = A
end1543912580714
结论:(1)A线程先持有object对象的锁,B线程可以异步的访问调用object对象中的非synchronized类型的方法。
(2)B线程先持有object对象的Lock锁,A线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。