线程安全性原子性、可见性、有序性

原子性----锁(synchronized)

jdk提供锁主要分两种:

1、synchionized:依赖JVM,java关键字,作用对象的作用范围内

修饰代码块:大括号括起来的代码,作用于调用的对象,当被修饰的代码和修饰的方法一样时,那么修饰代码块和修饰方法是等同的

public void test1(){
    synchronized (this){
        for (int i = 0; i < 10; i++) {
            System.out.println("test1:"+i);
        }
    }
}

修饰方法:整个方法,作用于调用的对象,同步方法

public synchronized void test2(){
    for (int i = 0; i < 10; i++) {
        System.out.println("test2:"+i);
    }
}

注:如果当前类是父类,子类继承了父类之后,要调用test2的时候,那么它是带不上synchronized关键字的,因为synchronized不是方法的一部分,如果子类也想使用synchronized的话,那么需要显示声明synchronized

修饰静态方法:整个静态方法,作用于所有对象

public static synchronized void test2(){
    for (int i = 0; i < 10; i++) {
        System.out.println("test2:"+i);
    }
}

修饰类:括号括起来的部分,作用于所有对象

public static void test1(){
    synchronized (SynchionizedExample2.class){
        for (int i = 0; i < 10; i++) {
            System.out.println("test1:"+i);
        }
    }
}

注:在一个方法里面如果一个synchronized是被一个类修饰的话,那么它和一个被synchronized修饰的一个static方法的表象是一致的

计数问题,添加synchronized,使之成为线程安全

代码实例:

public class CountExample4 {
    //请求总数
    private static int clientTotal=5000;

    //同时并发请求的线程数
    private static int threadTotal=200;

    private static int count=0;


    public static LongAdder longAdder=new LongAdder();
//    public static int count=0;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore=new Semaphore(threadTotal);
        final CountDownLatch countDownLatch=new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(new Runnable() {
                public void run() {
                    try {
                        semaphore.acquire();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    add();
                    semaphore.release();
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        executorService.shutdown();
//        System.out.println("count:"+count);
        System.out.println("count:"+count);
    }

    private static synchronized void add() {
        count++;
    }
//private static void add() {
//    count++;
//}


}

2、lock:jdk提供的代码层面的锁,依赖特殊的CPU指令,代码实现,如:ReentrantLock

synchronized和lock和Atomic之间原子性比较

synchronized:不可中断锁,适合竞争不激烈,可读性好,竞争激烈时性能下降特别快

lock:可中断锁,只要调用unlock即可,多样同步化,竞争激烈时能维持常态

Atomic:竞争激烈能维持常态,比Lock性能好,缺点:只能同步一个值

线程安全性—可见性

定义:一个线程对主内存的修改,可以及时的被其它线程观察到

导致共享变量在线程间不可见的原因

线程交叉执行

重排序结合线程交叉执行

共享变量更新后的值没有在工作内存和主内存之间及时更新

jvm提供synchronized和volatile两种可见性

JMM关于synchronized的两条规定

线程解锁前,必须把共享变量的最新值刷到主内存

线程加锁前,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值(注意:加锁和解锁是同一把锁)

volatile是通过内存屏障和禁止重排序优化来实现可见性

对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量的值刷新到主内存,

对volatile变量读操作时,会在读操作前加入一条loat屏障指令,从主内存中读取共享变量

volatile在读取变量时,会强迫从主内存中读取数据,当该变量发生变化时,又会强迫最新变量刷新到注内存

演示实例

public class CountExample4 {
    //请求总数
    private static int clientTotal=5000;

    //同时并发请求的线程数
    private static int threadTotal=200;

    private static volatile int count=0;


    public static LongAdder longAdder=new LongAdder();
//    public static int count=0;

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore=new Semaphore(threadTotal);
        final CountDownLatch countDownLatch=new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            executorService.execute(new Runnable() {
                public void run() {
                    try {
                        semaphore.acquire();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    add();
                    semaphore.release();
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        executorService.shutdown();
//        System.out.println("count:"+count);
        System.out.println("count:"+count);
    }


    private static void  add() {
        count++;
    }
}

注:从结果可以看出无法保证线程安全,原因是因为1、从内存中取出count值这时候的count值时最新的,2、执行加1操作和重新写回主内存,问题是如果有两个进程同时运行++操作,两个进程都读取了第一步,同时进行了+1操作并写回主存,尽管在第一步同时拿到了最新值,但是它们同时把+1后的值写回到注内存,这样就丢掉了一次+1操作,这样就会发现执行后的代码比5000小,有时候小很多,因此确定使用volatile不是线程安全的,同时也说明了volatile不具有原子性

volatile使用场景

具备两个条件

对变量的写操作不依赖于当前值

该变量不包含在具有其它变量的不变的式子中,volatile特别适合状态标记量

例子:

        volatile boolean inited=false;
        //线程1
        context=loadContext();
        inited=true;
        //线程2
        while(!inited){
          sleep();
        }
        doSomeStringWithConfig(context);

线程安全性—有序性

Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性

volatile、synchronized、lock

synchronized、lock顺序执行保证有序性

有序性—happens-before原则

程序次序规则:一个线程内,按照代码顺序,书写在前面操作先行发生于书写在后面的操作

锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作

volatile变量操作:对一个变量的写操作先行发生于后面对这个变量的读操作

传递规则:如果操作A先行发生于操作B,而操作B先行发生于操作C,则可以得出操作A先行发生于操作C

线程启动规则:Thread对象的start操作先行发生于此线程的每一个动作

线程终端规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生

线程终结规则:线程中所有的操作都先行于线程的终止检测我们可以通过Thread.join()方法结束、Thread.isAlive的返回值手段检测到线程已经终止执行

对象终结规则:一个对象的初始化完成先行发生于它的finalize方法的开始

原子性:Atomic包、CAS算法、synchronized、lock

可见性:volatile、synchronized

有序性:happends-before原则

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值