线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。
1、方法内的变量为线程安全
“非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题(这是方法内部的变量是私有的特性造成的,所得结果也就是“线程安全”的了。
2、实例变量非线程安全
如果多个线程共同访问1个对象中的实例变量,则可能出现”非线程安全“问题。
如果对象仅有1个实例变量,则有可能出现覆盖的情况。
public class HasSelfPrivateNum { private int num = 0; public synchronized 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(); } } }
public class MyThread1 extends Thread { private HasSelfPrivateNum numRef; public MyThread1(HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } public void run() { numRef.addI("a"); } }
public class MyThread2 extends Thread{ private HasSelfPrivateNum numRef; public MyThread2 (HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } public void run() { numRef.addI("b"); } }
//内部类 class Test { public static void main(String[] args) { HasSelfPrivateNum numRef = new HasSelfPrivateNum();//只创建一个对象 MyThread1 myThread1 = new MyThread1(numRef); myThread1.start(); MyThread2 myThread2 = new MyThread2(numRef); myThread2.start(); } }
b set over! b num=200 a set over! a num=100 当然结果也有可能是先输出b,再输出a,线程有随机性 |
a set over! b set over! b num=200 a num=200 |
//内部类,这个方法里创建了两个HasSelfPrivateNum对象 class Test { public static void main(String[] args) { HasSelfPrivateNum numRef1 = new HasSelfPrivateNum(); HasSelfPrivateNum numRef2 = new HasSelfPrivateNum(); MyThread1 aThread1 = new MyThread1(numRef1); aThread1.start(); MyThread2 bThread2 = new MyThread2(numRef2); bThread2.start(); } }
a set over! b set over! b num=200 a num=100 |
结论:调用用关键字synchronized声明的方法一定是排队运行的。另外需要牢牢记住“共享”这两个字,只有共享资源的读写访问才需要同步化,如果不是共享资源,那么根本就没有同步的必要。
-----------------------------------------------------------------------------------------------------------------------------
4、脏读
虽然在赋值时进行了同步,但在取值时有可能出现一些意想不到的意外,这种情况就是脏读(dirtyRead)。发生脏读的情况是在读取实例变量时,此值已经被其他线程更改过了。
public class PublicVar { public String username = "username"; public String password = "password"; public synchronized void setValue(String username, String password) { try { this.username = username; Thread.sleep(3000); 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() { //如果加synchronized,就不会出现脏读 System.out.println("getValue method thread name=" + Thread.currentThread().getName() + ", username=" +username + ", password="+password); } }
public class MyThread1 extends Thread { private PublicVar publicVar; public MyThread1(PublicVar publicVar) { super(); this.publicVar = publicVar; } public void run() { publicVar.setValue("username2", "password2"); } }
class Test { public static void main(String[] args) throws InterruptedException { PublicVar publicVar = new PublicVar(); MyThread1 myThread1 = new MyThread1(publicVar); myThread1.start(); Thread.sleep(2000); publicVar.getValue(); } }
getValue method thread name=main, username=username2, password=password setValue method thread name=Thread-0, username=username2, password=password2 |
可见,方法setValue()和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线程已经执行了一个完整的任务,也就是说usernarne和password这两个实例变量已经同时被赋值,不存在脏读的基本环境。
脏读一定会出现操作实例变量的情况下,这就是不同线程“争抢”实例变量的结果。
-----------------------------------------------------------------------------------------------------------------
5、synchronized锁重入
关键字synchronized拥有锁重入的功能。这也证明在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的。
“可重入锁”的概念是:自己可以再次获取自己的内部锁。比如有1条线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。
可重入锁也支持在父子类继承的环境中。
public class Service { public synchronized void service1() { System.out.println("service1"); service2(); } public synchronized void service2() { System.out.println("service2"); service3(); } public synchronized void service3() { System.out.println("service3"); } }
public class MyThread extends Thread { public void run() { Service service = new Service(); service.service1(); } }
class Test { public static void main(String[] args) throws InterruptedException { MyThread myThread = new MyThread(); myThread.start(); } }
service1 service2 service3 |
----------------------------------------------------------------------------------------------------
6、出现异常,锁自动释放
当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
public class Service { public synchronized void testMethod() { if (Thread.currentThread().getName().equals("a")) { 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 beginTime=" + System.currentTimeMillis()); Integer.parseInt("a"); } else { System.out.println("Thread B run Time=" + System.currentTimeMillis()); } } } } }
public class MyThread1 extends Thread { private Service service; public MyThread1(Service service) { super(); this.service = service; } public void run() { service.testMethod(); } }
public class MyThread2 extends Thread{ private Service service; public MyThread2(Service service) { super(); this.service = service; } public void run() { service.testMethod(); } }
//内部类 class Test { public static void main(String[] args) { try { Service service = new Service(); MyThread1 aThread = new MyThread1(service); aThread.setName("a"); aThread.start(); Thread.sleep(1000); MyThread2 bThread2 = new MyThread2(service); bThread2.setName("b"); bThread2.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
Thread B run Time=1462333804804 Thread B run Time=1462333804804 Thread B run Time=1462333804804 ThreadName=a, run beginTime=1462333804804 Exception in thread "a" java.lang.NumberFormatException: For input string: "a" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) |