多线程安全三大特性:
(1)原子性:
指一系列操作要么一起执行完成,要么一起不执行。例如i++操作其实并不是原子的,线程需要先获取到i的值然后在线程内存中对i的值进行+1再刷新到主内存中,在这个期间可能有别的线程对i的值进行了修改,这样得出的结果就是错误的,所以我们需要同步锁Synchronized(Volatile并不是原子性)。
(2)可见性:
线程之间变量相互可见。假设有一个全局变量i的值为0,同时有线程A和线程B对一个全局变量i执行++操作那么在JMM(JAVA内存模型)中由A线程获取全局变量i的值后在线程内存中对i执行++操作(由于线程互相之间不可见此时对B线程来说i还是0),此时B线程同时进行与A线程一样的操作,在最后刷新到主内存中那么i的值就为1而不是2。这就是线程之间互相不可见造成的线程不安全。
线程安全情况下
线程不安全情况下
(3)有序性:
即程序执行的顺序按照代码的先后顺序执行。在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
synchronized:
java中自带的同步锁,可修饰方法或用来同步代码块
public class ThreadDemo implements Runnable { static int a=100; Object obj=new Object(); @Override public void run() { test3(); } /** * Synchronzed修饰方法 此时相当于使用Synchronized(this){...}包裹了方法中的所有代码 */ public synchronized void test1() { System.out.println(); } /** * synchronized 代码块选择一个对象作为对象锁 只有获取到该对象锁的线程才可以访问代码块 */ public void test2() { synchronized(obj){ System.out.println(); } } /** * synchronized 修饰静态方法 相当于synchronized(ThreadDemo.class) 以类字节码文件作为对象锁 所有该类的对象想访问代码都必须获取同一个锁 */ public synchronized static void test3() { for (int i = 0; i < 50; i++) { try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } a--; System.out.println(Thread.currentThread().getName()+"----"+a); } } } 复制代码
1.修饰方法:
等于synchronized(this){...},一个对象实例中同时只能有一条线程访问该方法,如果该对象实例中有其它修饰了synchronized的方法,那么它们将共用一把锁。
2.同步代码块:
语法:synchronized(obj){...},一个对象实例中同时只能有一条线程访问该同步代码块,如果该对象实例中有其它使用了这个obj对象锁修饰的代码块,那么它们将共用一把锁。
3.修饰静态方法:
等于synchronized(xxx.class){...},所有该class的实例对象共用同一把锁,以class字节码文件作为对象锁。
代码如下:
public class ThreadDemo implements Runnable { private static int a=0; @Override public void run() { add(); } public static void main(String[] args) { Thread t1=new Thread(new ThreadDemo(),"线程1"); Thread t2=new Thread(new ThreadDemo(),"线程2"); t1.start(); t2.start(); } public synchronized static void add() { for (int i = 0; i < 50; i++) { try { Thread.sleep(300); /**非线程安全情况下让线程休眠堆积 重新调度 产生竟态条件**/ } catch (InterruptedException e) { e.printStackTrace(); } a++; System.out.println(Thread.currentThread().getName()+":----"+a); } } }复制代码
运行结果:
线程1:----1 线程1:----2 线程1:----3 线程1:----4 …… 线程1:----40 线程1:----41 线程1:----42 线程1:----43 线程1:----44 线程1:----45 线程1:----46 线程1:----47 线程1:----48 线程1:----49 线程1:----50 线程2:----51 线程2:----52 线程2:----53 线程2:----54 线程2:----55 线程2:----56 线程2:----57 线程2:----58 线程2:----59 …… 线程2:----96 线程2:----97 线程2:----98 线程2:----99 线程2:----100 复制代码
synchronized在使用时需要注意性能问题,应自己衡量好性能与线程安全之间的平衡。复制代码
volatile:
java自带的关键字,用来修饰变量,被声明的变量对所有线程可见同时禁止重排序,但不保证原子性,假设有多条线程同时对变量值进行修改还是会出现线程不安全。
修改上述代码:
public class ThreadDemo implements Runnable { private volatile static int a=0; @Override public void run() { add(); } public static void main(String[] args) { Thread t1=new Thread(new ThreadDemo(),"线程1"); Thread t2=new Thread(new ThreadDemo(),"线程2"); t1.start(); t2.start(); } public static void add() { for (int i = 0; i < 50; i++) { try { Thread.sleep(300); /**非线程安全情况下让线程休眠堆积 重新调度 产生竟态条件**/ } catch (InterruptedException e) { e.printStackTrace(); } a++; System.out.println(Thread.currentThread().getName()+":----"+a); } } }复制代码
运行结果:
线程2:----1 线程1:----2 线程2:----4 线程1:----4 线程2:----5 线程1:----6 线程2:----7 线程1:----8 线程2:----9 线程1:----10 线程1:----11 线程2:----12 线程1:----13 线程2:----14 线程1:----16 线程2:----16 线程1:----18 线程2:----18 线程1:----19 线程2:----20 线程1:----21 线程2:----22 线程1:----23 线程2:----23 线程1:----24 线程2:----25 线程1:----26 线程2:----27 线程2:----29 线程1:----29 线程1:----30 线程2:----31 线程1:----32 线程2:----33 线程1:----34 线程2:----35 线程1:----36 线程2:----37 线程1:----38 线程2:----39 线程1:----40 线程2:----41 线程1:----42 线程2:----43 线程1:----44 线程2:----45 线程1:----46 线程2:----47 线程1:----48 线程2:----49 线程1:----50 线程2:----51 线程1:----52 线程2:----53 线程1:----54 线程2:----55 线程1:----56 线程2:----57 线程1:----58 线程2:----59 线程1:----60 线程2:----61 线程1:----62 线程2:----63 线程1:----64 线程2:----65 线程1:----66 线程2:----67 线程1:----68 线程2:----69 线程1:----70 线程2:----71 线程1:----72 线程2:----73 线程1:----74 线程2:----75 线程1:----76 线程2:----77 线程1:----78 线程2:----79 线程1:----80 线程2:----81 线程1:----82 线程2:----83 线程1:----84 线程2:----85 线程1:----86 线程2:----87 线程1:----88 线程2:----89 线程1:----90 线程2:----91 线程1:----92 线程2:----93 线程1:----94 线程2:----95 线程2:----97 线程1:----97 线程1:----98 线程2:----99复制代码
线程1和线程2两个线程还是会得出相同的值,这就是因为同时有2个线程修改了值。
总结:
volatile只保证线程之间的可见性和禁止重排序,但是不能保证原子性。synchronized可以保证原子性但是synchronized多了会影响性能,jdk1.6后对synchronized有了优化,如何使用需要看实际情况自己衡量。