多线程安全三大特性:
(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有了优化,如何使用需要看实际情况自己衡量。