根据Brian Gortz定义的线程安全是:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。
在代码上的要求是,代码本身封装了所有必要的正确性保障手段,令调用者无需关心多线程的问题,更无需自己采取任何措施来保证多线程的正确调用。
Java代码可以实现线程安全,JVM也提供了同步和锁机制。
多线程问题
在多线程并发编程中,我们通常会遇到以下三个问题:原子性问题,可见性问题,有序性问题。
- 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
- 可见性:是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
- 有序性:即程序执行的顺序按照代码的先后顺序执行。
Java的线程安全程度
按照安全程度由强至弱排序,可以分为5类:不可变、绝对线程安全、相对线程安全、线程兼容、线程对立。
- 不可变,基本数据类型加final修饰;对象的行为不会对本身的状态产生任何影响,就保障数据不可变。不可变的对象一定是线程安全的,是最简单的实现。
- 绝对线程安全,调用者不需要任何额外的同步措施,访问对象的方法都被synchronized修饰为同步方法。但也并非使用时完全不考虑同步,同时效率较低。
- 相对线程安全,保证对这个对象单独的操作是线程安全的,但是特殊情况下也需要加额外的手段保证线程安全。java.util.concurrent包下都是支持此安全程度的API。
- 线程兼容,对象不是线程安全的,需要调用端使用同步手段来实现线程安全。
- 线程对立,无法支持线程安全,Java语言基本上不存在这类情况。
互斥同步实现线程安全
互斥同步(Mutual Exclusion & Synchronization),就是在多个线程并发访问共享数据时,使用互斥来保证共享数据在同一个时刻只被一个或一些线程使用。
Java中有两个最主要方法:synchronized、ReentrantLock。
临界区(Critical Section)、互斥量(Mutex)和信号量(Semaphore)都是主要的互斥实现方式。
- synchronized
Java中最基本的互斥同步手段是synchronized关键字,该关键字经过编译后,会在同步块前后分别形成monitorenter和monitorexit这两个字节码指令。monitorenter和monitorexit都需要一个reference类型的参数来指明要锁定和解锁的对象。
执行monitorenter时,首先要尝试获取对象的锁。成功获取对象的锁,则把锁的计数器加1。获取失败则当前线程阻塞等待,直到对象锁被释放。
执行monitorexit时就就对象的锁减1,当计数器为0时,锁就被释放。
synchronized是映射到操作系统的原生线程完成,是较重的操作,在用户态转换到核心态时,需要耗费大量时间。后期的JDK对此进行了优化,加入自旋等待策略。
synchronized可以直接指定reference参数。
如果synchronized修饰的是实例方法,reference参数就是this。
如果修饰的是类方法,reference就是此类的class对象。
- ReentrantLock
除了synchronized之外,还可以用java.util.concurrent包中的重入锁(ReentrantLock)来实现同步。是API层面的互斥锁,用lock unlock 配合try finally完成。加入了等待可中断、公平锁、锁绑定多个条件等高级功能,但并非性能更强。
非阻塞同步实现线程安全
非阻塞同步(Non-Blocking Synchronization),基于冲突检测的乐观并发策略实现线程安全:先进行操作,如果没有其他线程争用共享数据,那操作成功;如果有竞争,产生了冲突,那就再采取其他的补偿措施。
这是性能更好的实现方式,但是这个需要硬件指令集支持:
- 测试并设置(Test and Set)
- 获取并增加(Fetch and Increment)
- 交换(Swap)
- 比较并交换(Compare and Swap,CAS)
- 加载链接、条件存储(Load Linked、Store Conditional,LL、SC)
其中CAS由sun.misc.Unsafe类的compareAndSwapInt和compareAndSwapLong等几个方法包装提供,由JVM特殊处理。java.util.concurrent包的整数原子类就是包装了Unsafe类的CAS。
CAS需要有3个操作数:内存位置(变量的内存地址V)、旧预期值(A)、新值(B)。CAS执行时,当且仅当V符合A时,用B更新V,否则不进行更新,但无论是否更新V,都会返回V的旧值,上述处理过程是原子操作。
代码实现线程安全
让一个方法不涉及共享数据,那它自然就无需任何同步措施。这里有两类是天生线程安全的,可重入代码和线程本地存储。
- 可重入代码(Reentrant Code),可以任意环境执行一段代码并保证返回结果是可以预测的。在代码中不依赖存储在堆上的数据和公用的系统资源、状态量由参数传入、不调用非可重入代码。
- 线程本地存储(Thread Local Storage),当出现有与其他代码共享数据需求的,如果能保证在同一线程中执行,则可以将数据放在线程的本地存储java.lang.ThreadLocal中,这个对象只有当前线程可用。如果是多个线程访问此数据,那么可以使用volatile声明为易变的。
声明:本文部分素材转载自互联网,如有侵权立即删除 。
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!
8. 精力有限,不少源码未能详细测试(解密),不能分辨部分源码是病毒还是误报,所以没有进行任何修改,大家使用前请进行甄别
丞旭猿论坛
暂无评论内容