线程安全

线程安全测试:

@Slf4j
public class ConcurrencyTest {
    
    public static int clientTotal = 5000;//请求总数
    
    public static int threadTotal = 200;//允许并发线程数
    
    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(()->{
                try {
                    semaphore.acquire();//判断进程是否允许被执行
                    add();
                    semaphore.release();
                }catch (Exception e){
                    log.error("Exception--- " + e.getMessage());
                }
                countDownLatch.countDown();
                
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}" + count);
    }
    
    private static void add(){
        count++;
    }
}

 线程安全性:

        原子性:同一时刻只能有一个线程来操作。

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

        有序性:

 

原子性 Atomic包:

 

public static AtomicInteger count = new AtomicInteger(0);//

private static void add(){
        count.incrementAndGet();
}

 

 

 

 incrementAndGet 实现:

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

 

unsafe.getAndAddInt(this, valueOffset, 1) 实现:
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); Java底层方法
当前值和底层的值相同,则更新值。
CAS:compareAndSwap

AtomicLong是作用是对长整形进行原子操作,显而易见,在java1.8中新加入了一个新的原子类LongAdder,该类也可以保证Long类型操作的原子性,相对于AtomicLong,
LongAdder有着更高的性能和更好的表现,可以完全替代AtomicLong的来进行原子操作。

在32位操作系统中,64位的long 和 double 变量由于会被JVM当作两个分离的32位来进行操作,所以不具有原子性。而使用AtomicLong能让long的操作保持原子型。

 AtomicStampReference : CAS的ABA问题

原子性对比:

synchronized:不可中断锁,适合竞争不激烈,可读性好;

Lock:可中断锁,多样化同步,竞争激烈时能维持常态;

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

 

可见性:

  • 线程交叉执行
  • 重排序结合线程交叉执行;
  • 共享变量更新后的值没有在工作内存与主存间及时更新。

可见性 - synchronized

  •  线程解锁前,必须把共享变量的最新值刷新到主内存;
  • 线程加锁时,将清空工作内存中共享变量的值,使用共享变量时需要从主内存中重新读取最新的值。

可见性 - volatile

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

  • 对volatile变量写操作时,会在操作后加入一条store屏障指令,将本地内存的共享变量值刷新到主内存;
  • 对volatile变量读操作时,会在操作前加入一条load屏障指令,从主内存总读取共享变量。

volatile不具有原子性,适合做为状态标记量。

 

发布对象

     使一个对象能够被当前范围之外的代码所使用

 

对象溢出

      一种错误的发布,当一个对象还没有构造完成时,就使他被其他线程所见。

 

线程不安全,当两个线程同时拿到Null的时候,就会出现问题。

加锁的情况下也有可能出现线程不安全:

 因为JVM和cpu的优化,发生了指令重排

1.memory = allocate() 分配对象的内存空间;

2.instance = memory 设置instance指向刚分配的内存;

3.ctorInstance() 初始化对象

 

 使用volatile关键字来禁止指令重排

 

使用枚举

 

安全发布对象的方法

  • 在静态初始化函数中初始化一个对象引用;
  • 将对象的引用保存到volatile类型域或者AtomicReference对象中;
  • 将对象的引用保存到某个正确构造对象的final类型域中;
  • 将对象的引用保存到一个由锁保护的域中。

 

final关键字:

  • 修饰类:不能被继承;
  • 修饰方法:1.锁定方法不能被继承类修改;2.效率()
  • 修饰变量:基本数据类型变量,引用数据类型变量

final如果修饰引用类型的变量时,只是不允许指向另一个对象,但是变量的值可以修改。

 

不可变对象

 

 

线程封闭

  • Ad-hoc线程封闭:程序控制实现,最糟糕
  • 堆栈封闭:局部变量,无并发问题;
  • ThreadLocal 线程封闭:非常好的方法。

 

String类:String对象是不可改变的(引用类型)。字符串一旦创建,内容不能再改变。

当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。

 

 Java8的DateTimeFormatter是线程安全的,而SimpleDateFormat并不是线程安全。

 

同步容器

  • ArrayList:Vector 、Stack
  • HashMap:HashTable
  • Collections.synchronizedXXX(List,Set,Map)
posted @ 2019-08-06 14:31  kangjie  阅读(216)  评论(0编辑  收藏  举报