线程如何实现安全的?

      首先线程安全的对象具有以下特征:对象本身已经封装了所有必要的正确性保障手段,对象的使用者不用考虑多线程的问题。

java的线程安全定义有哪些?

不可变:为int,float等基础类型前边加final是该对象的值不可变,在Map,类等对象前加final,是对象的引用不可变。String类型的数据本身是不可变的。

绝对的线程安全:比如Vector,类中属性大多都加了synchronized,但是还达不到绝对的线程安全。

相对的线程安全:大部分的线程安全类都是这样的比如,hashTable、Vector等

线程兼容:指的是对象本身是线程不安全的,但是调用端正确的调用手段可以达到线程安全的目的,比如Arraylist,HashMap等。

线程对立:指的是无论采取什么并发措施,都无法达到线程安全的目的,比如Thread类中的suspend()和resume(),可能会导致死锁。

线程安全如何实现?

  1.阻塞同步(同步方法)

  (1) synchronized 关键字是常见的阻塞手段,synchronized是java提供的原子性内置锁,并且成为监视器锁。synchronized编译之后会在代码块前后添加minitorenter和monitorexit指令,成功之后monitorenter会将锁计数器加一,monitorexit指令会将锁计数器减一,为0则释放锁,synchronized指令对同一个线程是可重入的:线程执行完之前,会阻塞后边的线程,适合锁少量的同步代码块

(2)java.util.concurrent中的可重入锁ReentrantLock与synchronized功能相似,添加了更多功能,主要是3点:

        一是等待可中断(持有锁的线程长时间不释放的时候,等待的线程可执行其它操作),

  二是可实现公平锁(构造参数的boolean值代表是否公平锁)(可根据等待锁的时间顺序依次获取锁,syncharonized是非公平锁)

  三是可指定解锁条件(ReentrantLock可绑定多个Condition,只需要lock.newCondition()即可)

两个锁对比,synchronized在多线程高并发的情况下,性能下降的非常严重,ReentranLock是最佳选择,synchronized有很多要优化的地方。

   2.非阻塞同步

这种乐观锁的同步策略需要将操作和冲突检测放在一个指令集里边

比较并交换(Compare And Swap)

CAS指令需要三个参数(V、A、B)V是内存地址,A是旧值,B是新值,当且仅当V符合A的值时候,cpu会将新值B更新到地址V,这是连续的原子操作

JDK1.5之后,在Java的sun.misc.Unsafe提供CAS操作,如:CompareAndSwapInt()、compareAndSwapLong()等

CAS的漏洞:“ABA问题”,原值A,地址目前也是A,但是无法确认A是否是被修改过的A,还是原来的A

   3.无同步方案

一些代码天生就是线程安全的,不需要进行线程安全的操作:如可重入代码和线程本地存储

 

同步代码块

例如我们来重写incrementCounter():

public void incrementCounter() {
    // 其他无需同步的操作
    synchronized(this) {
        counter += 1; 
    }
}

例子很简单,但它展示了创建同步代码块的原理。假设该方法有一些额外的、不需同步的业务,那我们只需用synchronized块包住需要同步的代码即可。

与同步方法不同,同步代码块需要指定内在锁锁定的对象,通常用this即可。

当然同步方法和同步代码块,可有效解决线程间的变量可视度问题,即便如此,普通类中的值有可能被CPU缓存。因此,即使使用同步技术,在变量被改变后,其他线程也有可能无法获取最新的变量值。

我们使用volatile防止这种情况出现

public class Counter {
    private volatile int counter;
    // 标准的构造方法/getter
}

volatile关键字告诉JVM和编译器,把counter变量存放在主内存中。这样有效保证JVM每次都会从/向主内存读取/写入counter变量,而非CPU缓存。

使用volatile,确保了对某个线程可见的所有变量,均是从主内存读取而来。
public class User {
    private String name;
    private volatile int age;
    // 标准的构造方法/getter
}

以上例子里,JVM不仅会向主内存写入age变量,还会写入name变量到主内存。这样保证了两个变量的最新数值都保存在主内存中,更新变量的结果对其他线程均可见。

相似的,如果某个线程读取了volatile变量中的值,所有对该线程可见的变量,都会从主内存中读取数值。

volatile提供的这个特性,被称为一变量volatile,全员走内存(full volatile visibility guarantee)。




 
posted @   远乡人  阅读(237)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示
主题色彩