1.方法内的变量为线程安全
"非线程安全"问题存在于"实例变量"中,如果是方法内部的私有变量,则不存在"非线程安全"问题,所得结果也就是"线程安全"了。下面我们来编写一个"线程安全"的例子:
1.1 HasSelfPrivate类如下:
package edu.ymm.about_thread; public class HasSelfPrivate { public void add1(String username) { try { int num = 0; //这就是方法类的变量 if(username.equals("a")) { num = 100; System.out.println("a set over"); Thread.sleep(2000); //用来等待b }else { num = 200; System.out.println("b set over"); } System.out.println(username + "num = " + num); } catch (InterruptedException e) { e.printStackTrace(); } } }
1.2 创建一个ThreadA类:
package edu.ymm.about_thread; public class ThreadA extends Thread { private HasSelfPrivate numself; public ThreadA(HasSelfPrivate numself) { super(); this.numself = numself; } @Override public void run() { super.run(); numself.add1("a "); } }
1.3 创建一个ThreadB类:
package edu.ymm.about_thread; public class ThreadB extends Thread { private HasSelfPrivate numself; public ThreadB(HasSelfPrivate numself) { super(); this.numself = numself; } @Override public void run() { super.run(); numself.add1("b "); } }
1.4 来测试一下这个例子:
package edu.ymm.about_thread; public class Test { public static void main(String[] args) { HasSelfPrivate hPrivate = new HasSelfPrivate(); ThreadA threadA = new ThreadA(hPrivate); threadA.start(); ThreadB threadB =new ThreadB(hPrivate); threadB.start(); } }
执行结果如下:
可见,方法中的变量不存在非线程安全问题,永远是线程安全的。原因就是方法内部的变量是私有的。
2.实例变量非线程安全
(1):"非线程安全"其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”,也就是取到的数据其实是被更改过的。而“线程安全”就是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。
(2):用线程访问的对象中如果有多个实例变量,则运行的结果有可能出现交叉情况。
(3):如果对象仅有1个实例变量,则可能出现覆盖的情况。
下面举一个例子:
2.1 HasSelfPrivate类如下:
package edu.ymm.about_thread1; public class HasSelfPrivate { private int num = 0; public void add1(String username) { try { if(username.equals("a")) { num = 100; System.out.println("a set over"); Thread.sleep(3000); //用来等待b }else { num = 200; System.out.println("b set over"); } System.out.println(username + "num = " + num); } catch (InterruptedException e) { e.printStackTrace(); } } }
2.2 创建一个ThreadA类:
package edu.ymm.about_thread1; public class ThreadA extends Thread { private HasSelfPrivate numself; public ThreadA(HasSelfPrivate numself) { super(); this.numself = numself; } @Override public void run() { super.run(); numself.add1("a "); } }
2.3创建一个ThreadB类:
package edu.ymm.about_thread1; public class ThreadB extends Thread { private HasSelfPrivate numself; public ThreadB(HasSelfPrivate numself) { super(); this.numself = numself; } @Override public void run() { super.run(); numself.add1("b "); } }
2.4来测试一下这个例子:
package edu.ymm.about_thread1; public class Test { public static void main(String[] args) { HasSelfPrivate hPrivate = new HasSelfPrivate(); ThreadA threadA = new ThreadA(hPrivate); threadA.start(); ThreadB threadB =new ThreadB(hPrivate); threadB.start(); } }
结果如下:
上述就是所谓的“非线程安全”的一个例子。a的值不是我们预期的100了。要解决这种现象,只需要在a和b共同访问的方法前加上“synchronized”就可以了。
更新这个类后:
package edu.ymm.about_thread1; public class HasSelfPrivate { private int num = 0; synchronized public void add1(String username) { try { if(username.equals("a")) { num = 100; System.out.println("a set over"); Thread.sleep(2000); //用来等待b }else { num = 200; System.out.println("b set over"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { e.printStackTrace(); } } }
再执行结果为:
所以,在两个线程访问同一个对象中的同步方法(synchronized)时一定是线程安全的。
3.多个对象多个锁
我们来改动一点:将测试中产生两个实例对象。
3.1 HasSelfPrivate类如下:
package edu.ymm.about_thread2; public class HasSelfPrivate { private int num = 0; synchronized public void add1(String username) { try { if(username.equals("a")) { num = 100; System.out.println("a set over"); Thread.sleep(2000); //用来等待b }else { num = 200; System.out.println("b set over"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { e.printStackTrace(); } } }
上面代码中有同步方方法add1,说明此方法应该被顺序调用。
3.2 创建一个ThreadA类:
package edu.ymm.about_thread2; public class ThreadA extends Thread { private HasSelfPrivate numself; public ThreadA(HasSelfPrivate numself) { super(); this.numself = numself; } @Override public void run() { super.run(); numself.add1("a"); } }
3.3创建一个ThreadB类:
package edu.ymm.about_thread2; public class ThreadB extends Thread { private HasSelfPrivate numself; public ThreadB(HasSelfPrivate numself) { super(); this.numself = numself; } @Override public void run() { super.run(); numself.add1("b"); } }
3.4来测试一下这个例子:
package edu.ymm.about_thread2; public class Test { public static void main(String[] args) { HasSelfPrivate hPrivate1 = new HasSelfPrivate(); HasSelfPrivate hPrivate2 = new HasSelfPrivate(); ThreadA threadA = new ThreadA(hPrivate1); threadA.start(); ThreadB threadB =new ThreadB(hPrivate2); threadB.start(); } }
执行结果如下:
上述示例是两个线程分别访问一个类的两个不同实例的相同名称的同步方法,效果却是异步的方式运行的。本示例由于创建了2个业务对象,在系统中产生了两个锁,所以运行结果是异步的,打印的效果就是先打印b再打印a。
关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,所以在上面的示例中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问的是同一个对象。
但是如果多个线程访问多个对象,则JVM会创建多个锁。