线程安全

一、类的线程安全定义

(Doung Lee)如果多线程下使用这个类,不管多线程如何使用和调度这个类,这个类总是表示出正确的行为,这个类就是线程安全的。

类的线程安全表现为:

  操作的原子性

  内存的可见性

不做正确的同步,在多个线程之间共享状态的时候,就会出现线程不安全。

二、怎么才能做到类的线程安全

1.栈封闭

所有的变量都是在方法内部声明的这些变量都处于栈封闭状态(这样的话,就是安全的,因为方法是在栈针里面的,栈针又是线程级别的)

2.无状态

没有任何成员变量的类就叫无状态的类。

3.让类不可变

让状态不可变,两种方式:

1). final关键字,对于一个类,所有的成员变量应该是私有的,同样的只要有可能,所有的成员变量应该加上final关键字,但是加上final,要注意如果成员变量又是一个对象时,这个对象所对应的类也要是不可变,才能保证整个类是不可变的。

我们需要注意到的是,当成员变量被final修饰的时候,就只能有get()方法,没有set()方法;(例子:比如ConcurrentHashMap中的Node类)

看一个代码事例:

public class ImmutableFinalRef {
    
    private final int a;
    private final int b;
    private final User user;//这里,就不能保证线程安全啦
    
    public ImmutableFinalRef(int a, int b) {
        super();
        this.a = a;
        this.b = b;
        this.user = new User();
    }

    public int getA() {
        return a;
    }

    public int getB() {
        return b;
    }
    
    public User getUser() {
        return user;
    }

    public static class User{
        private int age;

        public User(int age) {
            super();
            this.age = age;
        }

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }
        
    }
    
    public static void main(String[] args) {
        ImmutableFinalRef ref = new ImmutableFinalRef(12,23);
        User u = ref.getUser();
        //u.setAge(35);
    }
}
View Code

看到上面的代码的时候,我们注意到在成员变量的对象,还是会被赋值的。只要下面的方式才行:

package com.xiangxue.ch7.safeclass;


/**
 *@author Mark老师   享学课堂 https://enjoy.ke.qq.com 
 *
 *类说明:看起来不可变的类,实际是可变的
 */
public class ImmutableFinalRef {
    
    private final int a;
    private final int b;
    private final User user;//这里,就不能保证线程安全啦
    
    public ImmutableFinalRef(int a, int b) {
        super();
        this.a = a;
        this.b = b;
        this.user = new User();
    }

    public int getA() {
        return a;
    }

    public int getB() {
        return b;
    }
    
    public User getUser() {
        return user;
    }

    public static class User{
        private final int age;

        public User(int age) {
            super();
            this.age = age;
        }

        public int getAge() {
            return age;
        }

    }
    
    public static void main(String[] args) {
        ImmutableFinalRef ref = new ImmutableFinalRef(12,23);
        User u = ref.getUser();
        //u.setAge(35);
    }
}
View Code

2). 根本就不提供任何可供修改成员变量的地方,同时成员变量也不作为方法的返回值。

看例子。其实在成员变量加上final,是最好的

public class ImmutetableToo {
    private List<Integer> list =  new ArrayList<>(3);
    
    public ImmutetableToo() {
        list.add(1);
        list.add(2);
        list.add(3);
    }
    
    public boolean isContains(int i) {
        return list.contains(i);
    }

}
View Code

4.使用不可变的类

不可变类(Immutable Objects):当类的实例一经创建,其内容便不可改变,即无法修改其成员变量。

Java 中八个基本类型的包装类和 String 类都属于不可变类,而其他的大多数类都属于可变类。

5.volatile

保证类的可见性,最适合一个线程写,多个线程读的情景,(比如ConcurrentHashMap)

6.安全的发布

这个时候我们可以和 3 中的2 进行对比

public class UnsafePublish {
    //要么用线程的容器替换
    //要么发布出去的时候,提供副本,深度拷贝
    private List<Integer> list =  new ArrayList<>(3);
    
    public UnsafePublish() {
        list.add(1);
        list.add(2);
        list.add(3);
    }
    
    //讲list不安全的发布出去了
    public List<Integer> getList() {
        return list;
    }

    //也是安全的,加了锁--------------------------------
    public synchronized int getList(int index) {
        return list.get(index);
    }
    
    public synchronized void set(int index,int val) {
        list.set(index,val);
    }    
    
}
View Code

7.使用TheadLocal

原文地址:https://www.cnblogs.com/lys-lyy/p/11107117.html