Java的线程安全问题

  • 2019 年 12 月 16 日
  • 笔记

Java面试时,总会被问到简单聊一聊线程安全问题,这时候就要考验,求职者对Java原理的掌握程度了,

乍一看,线程安全是啥啊,直接说,由于多线程环境,导致数据不一致等问题,就是线程安全问题,这可能只能打5分

Java的线程安全,要从Java的内存模型说起,

Java程序是多线程的,每个线程对于变量的操作,按照变量类型来分可能分两种,一种是线程私有的局部变量,一种是线程共享的全局变量;

局部变量只有当前线程可以操作,其他线程根本访问不到,所以不会出现线程的安全问题.

全局变量有可能被多个线程操作,这里的操作可能包括:

线程A依赖这个变量值做判断;

线程B,线程C都有可能修改这个变量值;

而线程对共享变量的操作,实际上操作的是内存变量的一个副本,这里有涉及到了JMM定义的一系列对于全局内存和工作内存的几种操作;

当然这些操作如果不特殊处理的话,就会导致一个线程的操作覆盖其他线程的操作,因为读取数据和写数据,总会有时间间隔,这期间有可能变量值已经修改。

不过,Java提供了一些列的同步机制来保证线程安全,包括:阻塞同步和非阻塞同步,其实就是悲观锁和乐观锁的概念。

阻塞同步:提供一种锁机制,所有对变量的操作都在锁的基础上进行操作,保证了同一时刻,对共享变量的操作只有一个;

非阻塞同步:提供一种乐观锁的机制,线程对变量操作不阻塞,直接操作,如果遇到竞争再进行应对;

对于保证Java线程的安全性,总结了几点:可见性、原子性、有序性;

可见性典型的就是volatile,这是Java提供的最轻量级的同步机制,volatile修饰的关键字,只能保证可见性,也就是其他线程对变量的修改,当前线程可以立即看到,但对于变量的写,不能保证原子性,所以还需要进行其他处理,如:多线程的话需加锁或者保证只有一个线程会写变量;

原子性,Java提供了锁或者CAS,锁就是synchronize,保证同时只有一个线程可以操作,保证了操作的原子性;CAS(Conmpare And Swap)原理是先比较工作内存和共享内存变量的值,再进行替换;利用底层指令来保证整个操作的原子性,不过存在ABA问题(很多乐观锁方案,都增加额外标志来避免ABA问题,如Zookeeper的版本号);

有序性,这是由于Java虚拟机有指令重排的优化,在同一线程内的代码,执行顺序有可能会改变,不过对于volatile和synchronize修饰的代码,会禁止指令重排,这种由于指令重排导致的问题,也有可能产生线程安全问题;

因此,总结Java线程安全问题就是由于多线程环境和Java虚拟机导致某些变量未按照我们实际期望的运行而带来的数据不一致问题,我们应该采用Java的同步机制来避免。