“非线程安全”其实会在多个线程对同一个对象中的实例变量进行并发访问的时候产生,产生的后果是脏读,也就是取到的数据是被更改过的。而“线程安全”就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。
1、方法内的变量是线程安全的
“非线程安全”问题存在于“实例变量中”,如果是方法内的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”了。
package selfThread; /** * Created by liping.sang on 2017/1/17 0017. */ public class HasSelfPrivateNum { public void addI(String username){ try{ int num=0; 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(); } } }
package selfThread; /** * Created by liping.sang on 2017/1/17 0017. */ public class ThreadA extends Thread { private HasSelfPrivateNum numRef; public ThreadA( HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } public void run(){ super.run(); numRef.addI("a"); } }
package selfThread; /** * Created by Administrator on 2017/1/17 0017. */ public class ThreadB extends Thread{ private HasSelfPrivateNum numRef; public ThreadB(HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } public void run(){ super.run(); numRef.addI("b"); } }
package selfThread; /** * Created by liping.sang on 2017/1/17 0017. */ 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(); } }
b set over b num = 200 a set over a num = 100
可见,方法中的变量不存在非线性安全的问题,永远都是非线程安全的,这是方法内部的变量是私有的特性造成的。
2、实例变量非线程安全
如果多个线程共同访问一个对象中的实例变量,则有可能出现“非线程安全”问题。
package service; 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) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package extthread; import service.HasSelfPrivateNum; 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"); } }
package extthread; import service.HasSelfPrivateNum; 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"); } }
package test; import service.HasSelfPrivateNum; import extthread.ThreadA; import extthread.ThreadB; 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 ! b num = 200 a num =200
此时是两个线程同时访问一个没有同步的方法,如果两个线程同时操作对象中的实例变量,则有可能出现“非线程安全”问题,
只要在public void addI(String username)方法前加关键字synchronized即可 。
在两个线程访问同一个对象中的同步方法时一定是线程安全的。
3、多个对象多个锁
package service; 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) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package extthread; import service.HasSelfPrivateNum; 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"); } }
package extthread; import service.HasSelfPrivateNum; 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"); } }
package test; import service.HasSelfPrivateNum; import extthread.ThreadA; import extthread.ThreadB; 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
两个线程分别访问同一个类的两个不同实例的相同名称的同步方法 ,效果却是以异步的方式运行,上面,创建了两个业务对象,在系统中产生了两个锁,所以运行结果是异步的,结果是先打印b在打印a,
虽然在HasSelfPrivateNum中使用了synchronized关键字但却是异步的交叉的,这是因为关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当做锁,所以在示例中,哪个线程先止血带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象,但如果多个线程访问的是多个对象,则JVM会创建多个锁。上面示例中创建了两个类的对象,所以就会产生2个锁。
4、synchronized方法与锁对象
package extobject; public class MyObject { synchronized public void methodA() { try { System.out.println("begin methodA threadName=" + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } }
package extthread; import extobject.MyObject; public class ThreadA extends Thread { private MyObject object; public ThreadA(MyObject object) { super(); this.object = object; } @Override public void run() { super.run(); object.methodA(); } }
package extthread; import extobject.MyObject; public class ThreadB extends Thread { private MyObject object; public ThreadB(MyObject object) { super(); this.object = object; } @Override public void run() { super.run(); object.methodA(); } }
package test.run; import extobject.MyObject; import extthread.ThreadA; import extthread.ThreadB; 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 methodB threadName = B end end
如果修改MyObject
package extobject; public class MyObject { synchronized public void methodA() { try { System.out.println("begin methodA threadName=" + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public void methodB() { try { System.out.println("begin methodB threadName=" + Thread.currentThread().getName()+"begin time=" +System.currentTimeMills()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } }
在ThreadB中修改run方法中修改为Object.methodB();
1)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法
2)A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的synchronized类型的方法则需等待,也就是同步。
5、脏读
在上面的例子中为了避免数据出现交叉的情况,使用synchronized关键字来同步,虽然在赋值时进行了同步,但是在取值时可能出现一些想不到的意外,这种情况就是脏读,发送脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。
package entity; 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() { System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); } }
package extthread; import entity.PublicVar; 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"); } }
package test; import entity.PublicVar; import extthread.ThreadA; public class Test { 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 method thread name=main username=B password=AA getValue method thread name=main username=B password=BB
出现脏读是因为public void getValue()方法不是同步的,所以可以在任意的时候进行调用,解决办法方然就是加上同步synchronized关键字。
通过上述我们可以知道脏读是通过synchronized关键字解决的,而且还要知道:当A线程进入anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更精确地讲,是获得了对象的锁,所以在其他线程必须等A线程执行网之后才能调用X方法,但B线程可以随意调用其他非synchronized同步方法。
当A线程调用anyObject对象加入synchronized关键字修饰的X方法时,A线程就获得了X方法所在的对象的锁,所以其他线程必须等A线程执行完毕后才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程将X方法执行完。也就是释放对象锁后才可以调用,这时A线程已经执行了一个完整的 任务,也就是说username和password这两个实例变量已经同时被赋值,不存在脏读的基本环境。
脏读一定会出现操作实例变量的情况下,这就是不同线程“争抢”实例变量的结果。
6、synchronized锁重入
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的,这也证明在一个synchronized方法/块的内部调用奔雷的其他synchronized方法/块时,是永远可以得到锁的。
package myservice; 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"); } }
package extthread; import myservice.Service; public class MyThread extends Thread { @Override public void run() { Service service = new Service(); service.service1(); } }
package test; import extthread.MyThread; public class Run { public static void main(String[] args) { MyThread t = new MyThread(); t.start(); } }
结果:
service1
service2
service3
可重入锁的概念是:自己可以再次获得自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象锁的时候还是可以获取的,如果不可重入的话,会造成死锁。
可重入锁也支持在父子类继承的环境中。
package extthread; import myservice.Main; import myservice.Sub; public class MyThread extends Thread { @Override public void run() { Sub sub = new Sub(); sub.operateISubMethod(); } }
package myservice; public class Main { public int i = 10; synchronized public void operateIMainMethod() { try { i--; System.out.println("main print i=" + i); Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
package myservice; public class Sub extends Main { synchronized public void operateISubMethod() { try { while (i > 0) { i--; System.out.println("sub print i=" + i); Thread.sleep(100); this.operateIMainMethod(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
package test; import extthread.MyThread; public class Run { public static void main(String[] args) { MyThread t = new MyThread(); t.start(); } }
结果: sub print i=9 main print i=8 sub print i=7 main print i=6 sub print i=5 main print i=4 sub print i=3 main print i=2 sub print i=1 main print i=0
7、出现异常时,锁自动释放