线程的安全性

267 阅读3分钟

共享的问题

import lombok.extern.slf4j.Slf4j;

/**
 * @Author blackcat
 * @version: 1.0
 * @description: 共享资源例子
 * <p>
  * 结果可能是正数、负数、零。
 * <p>
 * i++ 而言(i 为静态变量),实际会产生如下的 JVM 字节码指令:
 * getstatic i // 获取静态变量i的值
 * iconst_1 // 准备常量1
 * iadd // 自增
 * putstatic i // 将修改后的值存入静态变量i
 * <p>
 * i-- 也类似
 * getstatic i // 获取静态变量i的值
 * iconst_1 // 准备常量1
 * isub // 自减
 * putstatic i // 将修改后的值存入静态变量i
 * <p>
 */

@Slf4j
public class Counter {

    static int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter++;
            }
        }, "add");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter++;
            }
        }, "sub");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.info("result:{}", counter);
    }
}

临界区

一个程序运行多个线程本身是没有问题

问题出现在多个线程访问共享资源

​ 多个线程读共享资源其实也没有问题

​ 在多个线程对共享资源读写操作发生指令交错,就会出现问题

一段代码块如果存在对共享资源的多线程读写操作,称这段代码块为临界区

竞态条件

当两个线程竞争同一个资源,如果对资源的访问顺序敏感,就称存在竞态条件。

计算的正确性取决于多线程的交替执行的时许时,就会发生竞态条件。

解决

为了避免临界区的竞态条件发生,有多种手段可以达到目的。 阻塞式的解决方案:synchronized,Lock 非阻塞式的解决方案:原子变量

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author blackcat
 * @version: 1.0
 * @description:避免临界区的竞态条件的方案
 */
@Slf4j
public class CounterSafe {

    static int counter = 0;

    static int lockCounter = 0;

    static final Object lock = new Object();

    static AtomicInteger atomicCounter = new AtomicInteger(0);

    static final ReentrantLock reentrantLock = new ReentrantLock();
    
     static volatile int volatileCounter = 0;

    public static void main(String[] args) throws InterruptedException {
        sync();
        lock();
        atomicCounter();
        volatileCounter();
    }

    
    private static void volatileCounter() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                volatileCounter++;
            }
        }, "add");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                volatileCounter--;
            }
        }, "sub");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.info("volatile result:{}", atomicCounter.get());
    }

    private static void atomicCounter() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                atomicCounter.incrementAndGet();
            }
        }, "add");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                atomicCounter.decrementAndGet();
            }
        }, "sub");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.info("atomic result:{}", atomicCounter.get());
    }

    private static void lock() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                reentrantLock.lock();
                try {
                    lockCounter++;
                } finally {
                    reentrantLock.unlock();
                }
            }
        }, "add");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                reentrantLock.lock();
                try {
                    lockCounter--;
                } finally {
                    reentrantLock.unlock();
                }
            }
        }, "sub");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.info("lock result:{}", lockCounter);
    }


    private static void sync() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (lock) {
                    counter++;
                }
            }
        }, "add");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (lock) {
                    counter--;
                }
            }
        }, "sub");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.info("sync result:{}", counter);
    }
}

变量的线程安全分析

成员变量和静态变量是否线程安全

​ 如果它们没有共享,则线程安全

​ 如果它们被共享了,如果只有读操作,则线程安全

​ 如果有读写操作,则这段代码是临界区,需要考虑线程安全.

局部变量是否线程安全

​ 局部变量是线程安全的

​ 但局部变量的引用对象则未必

​ 如果该对象没有逃离方法的作用访问,它是线程安全的

​ 如果该对象逃离方法的作用范围,需要考虑线程安全

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

/**
 * @Author blackcat
 * @version: 1.0
 * @description:共享list对象
 */
public class ThreadUnsafe {


    static List<String> list = new ArrayList<>();

    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;

    public static void main(String[] args) {
        ThreadUnsafe unsafe = new ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                unsafe.method1(LOOP_NUMBER);
            }, "Thread" + i).start();
        }
    }


    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);
    }
}
//Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, //Size: 0
import java.util.ArrayList;
import java.util.List;

/**
 * @Author blackcat
 * @version: 1.0
 * @description: 将list改成局部变量
 */ 
public class ThreadSafe {


    public final void method1(int loopNumber) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }

    private void method2(List<String> list) {
        list.add("1");
    }

    private void method3(List<String> list) {
        list.remove(0);
    }


    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;


    public static void main(String[] args) {
        ThreadSafe safe = new ThreadSafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                safe.method1(LOOP_NUMBER);
            }, "Thread" + i).start();
        }
    }
}

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

/**
 * @Author blackcat
 * @version: 1.0
 * @description: 把method2,3 从private 改成public
 */
public class PublicThreadSafe {

    public final void method1(int loopNumber) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }

    public void method2(List<String> list) {
        list.add("1");
    }

    public void method3(List<String> list) {
        list.remove(0);
    }
}


import java.util.List;

/**
 * @Author blackcat
 * @version: 1.0
 * @description: 重写method2 导致list溢出
 */
public class PublicThreadUnsafe extends PublicThreadSafe {

    @Override
    public void method2(List<String> list) {
        new Thread(() -> {
            list.add("1");
        }).start();
    }

    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 100;

    public static void main(String[] args) {
        PublicThreadSafe safe = new PublicThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                safe.method1(LOOP_NUMBER);
            }, "Thread" + i).start();
        }

        System.out.println("111");
    }
}

常见线程安全的类

String

Integer

StringBuffer

Random

Vector

Hashtable

这里的线程安全始值调用实例单个方法是线程安全的,每个方法是原子,多个方法组合不是原子

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

/**
 * @Author blackcat
 * @version: 1.0
 * @description:单个方法是原子,组合不是原子的
 */
@Slf4j
public class VectorThread {

    static Vector vector = new Vector();


    public static void main(String[] args) throws InterruptedException {
        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(() -> {
        //        if(vector.isEmpty()) {
                    vector.add("key");
        //        }
            }, ""+i);
            list.add(thread);
        }
        list.stream().forEach(t -> t.start());
        list.stream().forEach(t -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        log.info("size:{}", vector.size());
    }
}

不可变类线程安全性

内部属性不能改变,只存在读取。

public class Immutable {

    private int value = 0;

    public Immutable(int value) {
        this.value = value;
    }

    public int getValue() {
        return this.value;
    }
}

如果你需要对Immutable类的实例进行操作,如添加一个类似于加法的操作,我们不能对这个实例直接进行操作,只能创建一个新的实例来实现,下面是一个对value变量进行加法操作的示例:

public class Immutable{
    private int value = 0;

    public Immutable(int value){
        this.value = value;
    }

    public int getValue(){
        return this.value;
    }

    public Immutable add(int valueToAdd){
        return new Immutable(this.value + valueToAdd);
    }
}

引用不是线程安全的

即使一个对象是线程安全的不可变对象,指向这个对象的引用也可能不是线程安全的。

public void Calculator{
    private Immutable currentValue = null;

    public Immutable getValue(){
        return currentValue;
    }

    public void setValue(Immutable newValue){
        this.currentValue = newValue;
    }

    public void add(int newValue){
        this.currentValue = this.currentValue.add(newValue);
    }
}

Calculator类持有一个指向Immutable实例的引用。注意,通过setValue()方法和add()方法可能会改变这个引用,因此,即使Calculator类内部使用了一个不可变对象,但Calculator类本身还是可变的,多个线程访问Calculator实例时仍可通过setValue()add()方法改变它的状态,因此Calculator类不是线程安全的。