在第一章已经出现了非线程安全的状况。“非线程安全”其实会发生在多个线程同时对同一个对象中的实例变量进行访问时发生。产生的结果就是脏读(读到被修改过的数据)。git
“线程安全”得到的实例变量是通过同步处理的,不会出现脏读的状况。github
“非线程安全”的问题存在于实例变量中,若是将实例变量私有,则不存在“非线程安全”问题,所得就是线程安全了。安全
在方法内部声明变量,是不存在“非线程安全”问题的。dom
变量所在类:异步
public class SafeVariable { public void addI(String username) { try { int num = 0; if(username.equals("a")) { num = 100; System.out.println("a set over!"); Thread.sleep(1000); } else { num = 200; System.out.println("b set over"); } System.out.println(username + " num = " + num); } catch (InterruptedException e) { e.printStackTrace(); } } }
线程代码1:async
public class Thread1 extends Thread { private SafeVariable sv ; public Thread1(SafeVariable sv) { this.sv = sv; } @Override public void run() { sv.addI("a"); } }
线程代码2:ide
public class Thread2 extends Thread { private SafeVariable sv ; public Thread2(SafeVariable sv) { this.sv = sv; } @Override public void run() { sv.addI("b"); } }
执行代码:函数
public class Main { public static void main(String[] args) { SafeVariable sv = new SafeVariable(); Thread1 thread1 = new Thread1(sv); thread1.start(); Thread2 thread2 = new Thread2(sv); thread2.start(); } }
执行结果:学习
能够看出,方法中的变量不存在非线程安全问题,永远都是线程安全的。这是方法内部的变量是私有的特性形成的。测试
若是多个线程同时访问一个对象的实例变量,则可能会出现“非线程安全问题”。
有多个实例变量可能会出现交叉的状况,若是仅有一个实例变量时可能会出现覆盖的状况。
实例变量所在类:
public class SafeVariable1 { private int num = 0; public void addI(String username) { try { if ("a".equals(username)) { num = 100; System.out.println("a set over!"); Thread.sleep(1000); } else { num = 200; System.out.println("b set over"); } System.out.println(username + " num = " + num); } catch (InterruptedException e) { e.printStackTrace(); } } }
线程代码1:
public class Thread3 extends Thread { private SafeVariable1 sv ; public Thread3(SafeVariable1 sv) { this.sv = sv; } @Override public void run() { sv.addI("a"); } }
线程代码2:
public class Thread4 extends Thread { private SafeVariable1 sv ; public Thread4(SafeVariable1 sv) { this.sv = sv; } @Override public void run() { sv.addI("b"); } }
执行代码:
public class Main { public static void main(String[] args) { SafeVariable1 sv = new SafeVariable1(); Thread3 thread3 = new Thread3(sv); thread3.start(); Thread4 thread4 = new Thread4(sv); thread4.start(); } }
执行结果:
本例中,两个线程同时访问了一个没有同步的方法,若是两个线程同时操做业务对象中的实例变量。就有可能出现“非线程安全”问题。
此处:若想解决“非线程安全问题”,只须要在addI()方法前加关键字synchronized便可实现。
实例变量所在类:
public class SafeVariable1 { private int num = 0; synchronized public void addI(String username) { try { if ("a".equals(username)) { num = 100; System.out.println("a set over!"); Thread.sleep(1000); } else { num = 200; System.out.println("b set over"); } System.out.println(username + " num = " + num); } catch (InterruptedException e) { e.printStackTrace(); } } }
执行结果:
从上述能够看出,在两(多)个线程同时访问同一个对象中的同步方法时,必定是线程安全的。
实例变量所在类:
public class SafeVariable2 { private int num = 0; synchronized public void addI(String username) { try { if("a".equals(username)) { 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(); } } }
线程代码1:
public class Thread5 extends Thread { private SafeVariable2 sv; public Thread5(SafeVariable2 sv) { this.sv = sv; } @Override public void run() { sv.addI("a"); } }
线程代码2:
public class Thread6 extends Thread { private SafeVariable2 sv; public Thread6(SafeVariable2 sv) { this.sv = sv; } @Override public void run() { sv.addI("b"); } }
执行代码:
public class Main { public static void main(String[] args) { SafeVariable2 sv1 = new SafeVariable2(); SafeVariable2 sv2 = new SafeVariable2(); Thread5 thread5 = new Thread5(sv1); thread5.start(); Thread6 thread6 = new Thread6(sv2); thread6.start(); } }
执行结果:
该示例:两个线程分别访问了一个类的两个实例,效果倒是异步的。
缘由:关键字synchronized取得的锁都是对象锁,而不是把一段代码或者方法(函数)看成锁。
因此要想实现同步效果,必须是多个线程访问同一个对象时才可以实现,而此示例中倒是两个线程访问各自的对象,建立的也是各自对象的锁,因此展示的结果天然为异步的。
同步的单词:synchronized 异步的单词:asynchronized
证实synchronized加锁的是对象。
对象类:
public class MyObject { 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(); } } }
线程代码1:
public class Thread7 extends Thread { private MyObject object; public Thread7(MyObject object) { this.object = object; } @Override public void run() { object.methodA(); } }
线程代码2:
public class Thread8 extends Thread { private MyObject object; public Thread8(MyObject object) { this.object = object; } @Override public void run() { object.methodA(); } }
执行代码:
public class Main { public static void main(String[] args) { MyObject object = new MyObject(); Thread7 thread7 = new Thread7(object); thread7.setName("A"); Thread8 thread8 = new Thread8(object); thread8.setName("B"); thread7.start(); thread8.start(); } }
执行结果:
修改后的对象类:
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声明的方法必定是排队运行的。那如今有猜测:同一个对象中的其余非synchronized声明的方法被调用时会怎么样呢?
对象类:
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(); } } public void methodB() { try { System.out.println("begin methodB threadName = " +Thread.currentThread().getName() + " begintime = " +System.currentTimeMillis()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } }
线程代码1:
public class Thread9 extends Thread { private MyObject object; public Thread9(MyObject object) { this.object = object; } @Override public void run() { object.methodA(); } }
线程代码2:
public class Thread10 extends Thread { private MyObject object; public Thread10(MyObject object) { this.object = object; } @Override public void run() { object.methodB(); } }
执行代码:
public class Main { public static void main(String[] args) { MyObject object = new MyObject(); Thread9 thread9 = new Thread9(object); thread9.setName("A"); Thread10 thread10 = new Thread10(object); thread10.setName("B"); thread9.start(); thread10.start(); } }
执行结果:
能够看出,虽然线程A先持有了object对象的锁,可是线程B仍能够彻底调用非synchronized类型的方法。
继续测试,此时将线程methodB()也加上同步锁synchronized关键字。
修改对象类:
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() + " begintime = " +System.currentTimeMillis()); Thread.sleep(5000); System.out.println("end"); } catch (InterruptedException e) { e.printStackTrace(); } } }
执行结果:
综上得出结论:
在2.1.4中经过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(1000); this.password = password; System.out.println("setValue method thread name = " + Thread.currentThread().getName() + " | username = " + username + " | password = " + password ); } catch (InterruptedException e) { e.printStackTrace(); } } public void getValue() { System.out.println("getValue method thread name = " + Thread.currentThread().getName() + " | username = " + username + " | password = " + password ); } }
线程代码:
public class Thread11 extends Thread { public PublicVar pv; public Thread11(PublicVar pv) { this.pv = pv; } @Override public void run() { pv.setValue("B","BB"); } }
执行代码:
public class Main { public static void main(String[] args) { try { PublicVar pv = new PublicVar(); Thread11 thread11 = new Thread11(pv); thread11.start(); Thread.sleep(5000);//打印结果受此值大小影响
pv.getValue(); } catch (InterruptedException e) { e.printStackTrace(); } } }
执行结果:
出现脏读的缘由在于getValue()方法不是同步的,因此能够在任意时刻进行调用。解决方法是加上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已经被同时赋值,不存在脏读的基本环境。
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程获得一个对象锁后,再次请求此对象锁时是能够再次获得该对象的锁的。这也证实了在一个synchronized方法/块内部调用本地其余synchronized方法/块时,是永远能够获得锁的。
服务类代码:
public class Myservice { 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"); } }
线程代码:
public class Thread12 extends Thread { @Override public void run() { Myservice myservice = new Myservice(); myservice.service1(); } }
执行代码:
public class Main { public static void main(String[] args) { Thread12 thread12 = new Thread12(); thread12.start(); } }
执行结果:
可重入锁概念:本身能够再次得到本身的内部锁,好比有1条线程得到了某个对象的锁,此时这个对象锁尚未释放,当其再次想要得到这个对象的锁的时候仍是能够得到的,若是不可锁重入,会致使进入死循环。
可重入锁也能够出如今继承中。
父类:
public class Father { public int i = 10; synchronized public void fatherWay() { try { i--; System.out.println("father print i = " + i); Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }
子类:
public class Son extends Father { synchronized public void sonWay() { try { while (i > 0) { i--; System.out.println("son print i = " + i); Thread.sleep(1000); this.fatherWay(); } } catch (InterruptedException e) { e.printStackTrace(); } } }
线程代码:
public class Thread13 extends Thread { @Override public void run() { Son son = new Son(); son.sonWay(); } }
执行代码:
public class Main { public static void main(String[] args) { Thread13 thread13 = new Thread13(); thread13.start(); } }
执行结果:
上述示例代表:当存在父子类继承时,子类彻底可使用锁重入调用父类的同步方法。
当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
业务类代码:
public class Service1 { synchronized public void testMethod() { if ("a".equals(Thread.currentThread().getName())) { System.out.println("ThreadName = " + Thread.currentThread().getName() + " run beginTime = " + System.currentTimeMillis()); int i = 1; while (i == 1) { if(("" + Math.random()).substring(0,8).equals("0.123456")) { System.out.println("ThreadName = " + Thread.currentThread().getName() + " run exceptionTime = " + System.currentTimeMillis()); Integer.parseInt("a"); } } } else { System.out.println("Thread B run Time = " + System.currentTimeMillis()); } } }
线程代码1:
public class Thread14 extends Thread { private Service1 service1; public Thread14(Service1 service1) { this.service1 = service1; } @Override public void run() { service1.testMethod(); } }
线程代码2:
public class Thread15 extends Thread { private Service1 service1; public Thread15(Service1 service1) { this.service1 = service1; } @Override public void run() { service1.testMethod(); } }
执行代码:
public class Main { public static void main(String[] args) { try { Service1 service1 = new Service1(); Thread14 a = new Thread14(service1); a.setName("a"); a.start(); Thread.sleep(5000); Thread15 b = new Thread15(service1); b.setName("b"); b.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
执行结果:
能够看到的是,当出现异常后,锁就被释放了,线程B可以进入方法正常打印。
同步是不能够被继承的:
父类业务类:
public class FatherService { synchronized public void fatherService() { try { System.out.println("int father 下一步 sleep begin threadName = " + Thread.currentThread().getName() + " time = " + System.currentTimeMillis()); Thread.sleep(5000); System.out.println("int father 下一步 sleep end threadName = " + Thread.currentThread().getName() + " time = " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } }
子类业务类:
public class SonService extends FatherService { @Override public void fatherService() { try { System.out.println("int son 下一步 sleep begin threadName = " + Thread.currentThread().getName() + " time = " + System.currentTimeMillis()); Thread.sleep(5000); System.out.println("int son 下一步 sleep end threadName = " + Thread.currentThread().getName() + " time = " + System.currentTimeMillis()); super.fatherService(); } catch (InterruptedException e) { e.printStackTrace(); } } }
线程代码1:
public class Thread16 extends Thread { private SonService sonService; public Thread16(SonService sonService) { this.sonService = sonService; } @Override public void run() { sonService.fatherService(); } }
线程代码2:
public class Thread17 extends Thread { private SonService sonService; public Thread17(SonService sonService) { this.sonService = sonService; } @Override public void run() { sonService.fatherService(); } }
执行代码:
public class Main { public static void main(String[] args) { SonService sonService = new SonService(); Thread16 a = new Thread16(sonService); a.setName("A"); a.start(); Thread17 b = new Thread17(sonService); b.setName("B"); b.start(); } }
执行结果:
由结果能够看到,代码的执行是非同步的,这也就说明了synchronized关键字是不能够继承的,此时为了实现同步,必须在子类的方法上加上关键字synchronized才行。
修改后的子类业务类:
public class SonService extends FatherService { @Override public synchronized void fatherService() { try { System.out.println("int son 下一步 sleep begin threadName = " + Thread.currentThread().getName() + " time = " + System.currentTimeMillis()); Thread.sleep(5000); System.out.println("int son 下一步 sleep end threadName = " + Thread.currentThread().getName() + " time = " + System.currentTimeMillis()); super.fatherService(); } catch (InterruptedException e) { e.printStackTrace(); } } }
修改后的执行结果:
源码地址:https://github.com/lilinzhiyu/threadLearning