JAVA--并发的线程安全处理(一)--线程安全性

多线程并发与线程安全相关知识整理如下:

  1. 线程怎么保证安全性
  2. 如何安全发布对象
  3. 线程安全有哪些手段
  4. JUC组件的讲解
  5. 如何提高线程的调度

一、线程怎么保证安全性。

  • 什么是线程安全性

当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。

 

  • 线程安全性的三大特征

原子性、有序性、可见性

原子性:提供互斥访问,同一时刻只能有一个线程来对它进行操作。

有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序。

可见性:一个线程对主内存的修改可以及时被其他线程观察到。

特性操作
原子性 synchronized的代码块能保证串行执行
有序性 可以由volatile(禁止指令重排序)/synchronized(一个变量最多只能有一个线程对其lock)实现
可见性可以由final(不会修改)、volatile(强制更新+读取主内存)以及synchronized(在unlock时会刷新所有已修改数据到主内存,lock时会从主内存重新加载数据)实现

 

  • 原子性-Atomic包

Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程不被阻塞。

上代码

package Atomic;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.atomic.AtomicInteger;

//线程安全
public class AtomicIntegerDemo {

    private final static int clientTotal=5000;//线程总数量
    private final static int threadTotal=200;//每次通过的线程数

    static AtomicInteger ai=new AtomicInteger(0);

    public static void main(String[] args) {
        //线程池
        ExecutorService executorService= Executors.newCachedThreadPool();
        //生成信号量
        final Semaphore semaphore=new Semaphore(threadTotal);//每次通过20个
        final CountDownLatch latch=new CountDownLatch(clientTotal);//计数器
        for(int i=0;i<clientTotal;i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();//申请/获取许可
                    //incrementAndGet()
                    //-->1.unsafe.getAndAddInt
                    //-->2.this.compareAndSwapInt(CAS)
                    //-->3.native 方法
                    //第2->3,实际是工作内存与主内存校验的过程(CAS特别重要)
                    ai.incrementAndGet();//数据加1
                    semaphore.release();//释放许可
                } catch (Exception e) {
                    System.out.println("execption:" + e);
                }
                latch.countDown();//计数器减1
            });
        }
        try {
           latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();//关闭线程
        System.out.println(ai.get());
    }
}

 

  • 原子性-锁(synchronized,lock)

  synchronized:依赖JVM,JVM会自动锁定和解除锁定

  Lock:依赖CPU指令,需要人工解锁,ReentrantLock

 

  Synchroized代码

  

package Sync;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SynchronizedDemo {

    //修饰整一个方法
    private synchronized void test1(){
        for(int i=0;i<10;i++){
            System.out.println("Test1 - "+i);
        }
    }
    //修饰某个代码块
    private void test2(){
        synchronized (this){
            for(int i=0;i<10;i++){
                System.out.println("Test2 - "+i);
            }
        }
    }

    public static void main(String[] args) {

        SynchronizedDemo demo1=new SynchronizedDemo();
        ExecutorService executorService= Executors.newCachedThreadPool();
            executorService.execute(()->{
                demo1.test1();
            });
            executorService.execute(()->{
                demo1.test2();
            });
        executorService.shutdown();
    }
}

Synchronized对应的作用域如下表

操作方法作用域
修饰某段代码大括号括起来的代码,作用于调用的对象
修饰方法整个方法,作用于调用的对象
修饰静态方法整个静态方法,作用于所有对象(从JVM原理考虑)
修饰类括号括起来的部分,作用于所有对象(从JVM原理考虑)

 

 Lock代码

package Sync;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockDemo {

    private static int count=0;
    private final static int threadPoolCount=5000;
    private final static int threadCount=200;
    private static Lock lock=new ReentrantLock();
    public static void main(String[] args) {

        ExecutorService executorService= Executors.newCachedThreadPool();//线程池
        Semaphore semaphore=new Semaphore(threadCount);
        final CountDownLatch countDownLatch = new CountDownLatch(threadPoolCount);

        for(int i=0;i<threadPoolCount;i++){
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                     add();
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            });
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        System.out.println(count);
    }

    private static void add(){
        lock.lock();//锁定
        count++;//需要同步的方法
        lock.unlock();//解锁
    }
}

 

Atomic、synchronized、Lock对比

Atomic:在竞争激烈时能维持常态,比Lock性能好,但只能更新一个值

synchronized:不可中断锁,适合竞争不激烈,可读性好,JVM会自动释放资源。

Lock:可中断锁,在竞争激烈时能维持常态,需要人工加锁与解锁。

  

  • 可见性

导致共享变量在线程间不可见的原因(线程交叉执行、重排序结合线程交叉执行、共享变量更新后的值没有在工作内存和主存及时更新)

可见性-synchronized

JAVA内存模型里面synchronized的两条规定:

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

2.线程加锁时,将清空工作内存中共享变量的值,从而触发需要使用共享变量时,必须从主内存中重新读取最新的值

 

可见性-volatile

通过加入内存屏障禁止重排序优化来实现

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

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

package Sync;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class VolatileDemo {

    private static volatile int count=0;
    private final static int threadPoolCount=5000;
    private final static int threadCount=200;
    public static void main(String[] args) {

        ExecutorService executorService= Executors.newCachedThreadPool();//线程池
        Semaphore semaphore=new Semaphore(threadCount);
        final CountDownLatch countDownLatch = new CountDownLatch(threadPoolCount);

        for(int i=0;i<threadPoolCount;i++){
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                countDownLatch.countDown();
            });
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        System.out.println(count);
    }

    private static void add(){
        //1.第一步获取count
        //2.第二步+1
        //3.把count的值,返回到主内存中
        count++;
        //计算结果显示Volatile不具有原子性
        //Volatile比较适合使用 状态标记(boolean)
        //对变量的写操作不依赖于当前值
        //该变量没有包含在具有其他变量的变量中
    }
}

 

  • 有序性

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

happens-before八大原则

程序次序规则:一个线程内,按照代码的顺序执行。

锁定规则:一个unLock的操作的发生于后面对一个锁的lock操作。

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

传递规则:A操作B,B操作C,那么可以得出A操作C。

线程启动规则:Thread对象的start()方法先行发送与此线程的每一个动作。

线程中断规则:对线程interrupt()方法的调用先行发送与中断线程的代码检测到中断事件的发生。

线程终结规则:线程中所有的操作终止检测,可以通过Thread.join()方法来结束。

对象终结规则:一个对象的初始化完成先行发送于他的finalize()方法的开始。

 

 

    原文作者:茶中酒李志杰也
    原文地址: https://www.cnblogs.com/lzj2008/p/8907140.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞