4、线程安全的理解

线程安全定义:
  《Java Concurrency In Practice》(并发编程实践)中有一个相对恰当的定义:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象就是线程安全的。
线程安全的等级:
  在多个线程间存在共享数据访问的前提下,java中线程安全的等级大致分为5类:
  1、不可变
  java中不可变的对象一定是线程安全的,无论方法的实现者还是调用方,都无需再采取任何的线程安全保障措施。比如,由final修饰的变量。
  String可以修改值,为什么说是线程安全的?
  因为string修改值跟这里说的不可变,说的不是同一个位置。比如:
  String a = "123";
  a = "456";
  这里如果有多个线程对a进行修改操作,那的确是线程不安全的,因为这里的a并不是我们所说的“不可变”对象,如果声明为final,则一旦赋值后就是我们这里说的不可变对象了。通常我们说的string是线程安全的,是指string的底层实现中,char[]是final的,每次我们对其进行substring,replace,concat等操作的时候,看上去虽然返回了一个修改了的字符串,但实际这个字符串是经过new了一个stringbuffer进行修改后生成的,不是原来的那个。
  所以,这里的string线程安全应该理解为:多个线程执行a.substring(0,1),返回的都是一样的,无需进行同步操作。
  题外话:为什么string被设计成不可变的呢?
  个人觉得原因有2:1、底层char数组不可变,是为了性能问题。因为string太常用了,如果是普通对象,没有string常量池,会导致新生代不停的产生大量的string对象,引发频繁的gc操作,从而降低jvm性能。2、类设计为final的,避免被继承重写,是为了防止被继承后重写的代码质量不高,导致产生严重的性能或者其它问题(这个太基础了,大家还是别动的好,估计当时的jdk开发人员就这么想的)。
  2、绝对线程安全
  绝对线程安全跟不可变就是我们上边所给的线程安全的定义所描述的内容。但实际上,一个类要达到这个要求是很难的,甚至是不切实际的。jdk api中标注自己是线程安全的类中的大多数,都不是绝对安全的,而是相对安全。例如,我们说java.util.Vector是线程安全的,但以下代码会报错:
public class A {
    private static Vector<Integer> vector = new Vector<Integer>();
    public static void main(String[] args) {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println("添加");
                vector.add(i);
            }

            Thread removeThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < vector.size(); i++) {
                        System.out.println("removeThread删除");
                        vector.remove(i);
                    }
                }
            });

            Thread printThread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < vector.size(); i++) {
                        System.out.println("printThread获取");
                        System.out.println((vector.get(i)));
                    }
                }
            });

            removeThread.start();
            printThread.start();
            //不要同时产生过多的线程,否则会导致操作系统假死
            while (Thread.activeCount() > 20);
        }
    }
}
  尽管get,remove等方法都是线程安全的,但仍会报错:
java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 0
    at java.util.Vector.get(Vector.java:744)
    at Test$2.run(Test.java:29)
    at java.lang.Thread.run(Thread.java:722)
Exception in thread "Thread-14857" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 0
    at java.util.Vector.get(Vector.java:744)
    at Test$2.run(Test.java:29)
    at java.lang.Thread.run(Thread.java:722)
  这就引申出一个问题:单个的方法 synchronized 了并不代表组合(compound)的方法调用就跟synchronized一样线程安全。
  3、相对线程安全
  相对安全指的是对某个对象的单个操作是线程安全的,但对于某些特定顺序的组合操作,就可能需要在调用端使用额外的同步手段来保证调用的正确性。java中大部分的所谓线程安全类都是这个类型的,例如Vector,HashTable等。2中的两个线程的操作只需要都加上相同的互斥锁即可实现线程安全。
  4、线程兼容
  线程兼容指的是对象本身不是线程安全的,但可以通过在调用端正确的使用同步手段来保证在并发环境中可以安全的使用。我们平时说的某个类是线程不安全的,绝大多数时候就是说的这种情况,例如ArrayList跟HashMap等。
  5、线程对立
  线程对立指的是无论调用端是否采取了同步措施,都无法在多线程环境中保证数据的安全。java中这种代码是很少的,而且通常是有害的,应尽量避免。
  一个例子就是Thread类的suspend()跟resume()方法。两个线程同时持有一个对象,一个尝试去中断线程,一个尝试去恢复线程,无论调用时是否进行了同步,目标线程都存在死锁的风险。也正是这个原因,这俩方法已经被jdk废弃了,,,。
 
原文地址:https://www.cnblogs.com/nevermorewang/p/9540787.html