java一些收藏

线程安全

2022-01-28  本文已影响0人  程序员札记

对于下面这段代码,输出是什么?

package com.conrrentcy.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ThreadSafeProblem {

    private final static Object locker = new Object();
    private static int count = 0;
    private static final Logger log = LoggerFactory
            .getLogger(ThreadSafeProblem.class);

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {

            for (int i = 0; i < 5000; i++) {
                count++;
            }

        }, "t1");

        Thread t2 = new Thread(() -> {

            for (int i = 0; i < 5000; i++) {
                count --;
            }

        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.info(" count  value is  {}", count);

    }

}

以上的结果可能是整数也可能是负数也可能是0。因为java对静态变量的自增、自减并不是原子性的。要彻底理解,必须从字节码角度来看。
对于静态count++ 而言 ,会产生如下JVM的指令

getstatic count //获取静态变量的值
iconst_1  //准备常量1
iadd      //自增
putstatic count // 将修改后的在值放入count

对于静态count-- 而言 ,会产生如下JVM的指令

getstatic count //获取静态变量的值
iconst_1  //准备常量1
isub      //自减
putstatic count // 将修改后的在值放入count

java内存模型中,主存的每个线程的内存需要同步,主存是真正进行运算的地方, 主存运算完的值需要同步回线程内存。 在多个线程的情况下,如果主存的值可以被多个线程修改,那么当 同步回去时,就有极大可能和单独一个线程修改的值不同。这就产生了线程安全问题。

image.png

如果指令按照线程顺序运行,就不会有任何问题

image.png

如果出现交错,就可能有两种情况

临界区

多线程的的问题主要是对共享资源发生读写指令交错,就会出现问题。
一个代码块如果存在对共享资源的多线程读写操作,称之为临界区。

竞态条件

多个线程在临界区运行,由于代码执行序列导致结果无法预测,称之为竞态条件。

互斥

避免竞态条件的发生,可以用以下方式:

这章主要介绍synchronize 关键字的阻塞方法,就是对象锁。

package com.conrrentcy.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ThreadSafeProblem {

    private final static Object locker = new Object();
    private static int count = 0;
    private static final Logger log = LoggerFactory
            .getLogger(ThreadSafeProblem.class);

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() -> {

            for (int i = 0; i < 5000; i++) {
                synchronized (locker) {
                    count++;
                }
            }

        }, "t1");

        Thread t2 = new Thread(() -> {

            for (int i = 0; i < 5000; i++) {
                synchronized (locker) {
                    count--;
                }
            }

        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.info(" count  value is  {}", count);

    }

}

下面代码,运行结果每次都为0。


image.png

思考

这三种情况各是什么行为?
synchronized 加在对象 和 方法上有什么区别?

变量的线程安全分析

成员变量和静态变量

package com.conrrentcy.thread;

import java.util.ArrayList;
import java.util.List;

public class ThreadSafeReference {
    public static void main(String[] args) throws InterruptedException {
        ThreadUnSafeList tl = new ThreadUnSafeList();   
        for(int i=0; i<2;i++){
            
            new Thread(()->{
                tl.method1(200);
            },"t"+i).start();
        }
        
        ThreadSafeList tl2 =  new ThreadSafeList();
        for(int i=0;i <2;i++){
            new Thread(()->{
                tl2.method1(200);
            },"t"+i).start();
        }
    }
 
}

class ThreadUnSafeList {
     private List  list = new ArrayList();
     
     public void method1(int loopNumber){
         for( int i=0; i<loopNumber;i++){
             method2();
             method3();
         }
     }
     private void method2(){
         list.add("1");
     }
     
     private void method3(){
         list.remove(0);
     }
     
}

class ThreadSafeList {
     
     
     public void method1(int loopNumber){
         List  list = new ArrayList();
         for( int i=0; i<loopNumber;i++){
             method2(list);
             method3(list);
         }
     }
     private void method2(List list){
         list.add("1");
     }
     
     private void method3(List list){
         list.remove(0);
     }
     
}

比较一下ThreadUnSafeList 和 ThreadSafeList 有啥不同?下面是ThreadUnSafeList 的内存模型


image.png

局部变量

public  static void test(){
    int  i=0;
    i++;
}

image.png

下面是刚才代码ThreadSafeList 的内存模型

image.png

但是,光是局部变量也不能完全保证线程安全,需要保证在同一个线程内部没有起另外一个线程, 下面的代码就不是线程安全的,主要是在method3, 又另外起了一个线程。导致list 虽然对是一个local 变量,但是在method3, 内部就不再是一个线程变量了。

package com.conrrentcy.thread;

import java.util.ArrayList;
import java.util.List;

public class ThreadExpose {

    public static void main(String[] args) {
        ThreadSafeList2 tlc= new ThreadSafeListChild();
        for(int i=0;i <2;i++){
            new Thread(()->{
                tlc.method1(200);
            },"t"+i).start();
        }

    }
    

}
class ThreadSafeList2 {
     
     
     public void method1(int loopNumber){
         List  list = new ArrayList();
         for( int i=0; i<loopNumber;i++){
             method2(list);
             method3(list);
         }
     }
     public void method2(List list){
         list.add("1");
     }
     
     public void method3(List list){
         list.remove(0);
     }
     
}

class ThreadSafeListChild extends ThreadSafeList2{
     public void method3(List list){
         new Thread(()->{
         list.remove(0);
         },"tchild").start();
     }
}

class modifier - private, final 能够加强线程安全

上一篇下一篇

猜你喜欢

热点阅读