- 线程安全
概念:保证多线程环境下共享的,可修改的状态的正确性。
即如果状态不是共享的,或是不可修改的,也就不存在线程安全问题。
保证线程安全的两个方法:
- 封装:通过封装,将对象内部状态隐藏起来,保护起来。
- 不可变:final和immutable对象。
线程安全需要保证几个基本特性:
- 原子性:相关操作不会中途被其他线程干扰,一般通过同步机制实现。
- 可见性:一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile 就是负责保证可见性的。
- 有序性:保证线程内串行语义,避免指令重排等。
- ReentrantLock和Synchronized
ReentrantLock是Lock的实现类,是一个互斥的同步器,在多线程高竞争条件下,ReentrantLock比synchronized有更加优异的性能表现。
用法比较
- Lock使用起来比较灵活,但是必须有释放锁的配合动作
- Lock必须手动获取与释放锁,而synchronized不需要手动释放和开启锁
- Lock只适用于代码块锁,而synchronized可用于修饰方法、代码块等
特性比较
ReentrantLock的优势体现在:
- 具备尝试非阻塞地获取锁的特性:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁
- 能被中断地获取锁的特性:与synchronized不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放
- 超时获取锁的特性:在指定的时间范围内获取锁;如果截止时间到了仍然无法获取锁,则返回
注意事项
在使用ReentrantLock类的时,要注意三点:
- 在finally中释放锁,目的是保证在获取锁之后,最终能够被释放
- 不要将获取锁的过程写在try块内,因为如果在获取锁时发生了异常,异常抛出的同时,也会导致锁无故被释放。
- ReentrantLock提供了一个newCondition的方法,以便用户在同一锁的情况下可以根据不同的情况执行等待或唤醒的动作。
关于公平性
这里所谓的公平性是指在竞争场景中,当公平性为真时,会倾向于将锁赋予等待时间最久的线程。公平性是减少线程“饥饿”(个别线程长期等待锁,但始终无法获取)情况发生的一个办法。
如果使用synchronized,根本无法进行公平性的选择,这也是主流操作系统线程调度的选择。通用场景中,公平性未必有想象中的那么重要,Java默认的调度策略很少会导致 “饥饿”发生。与此同时,若要保证公平性则会引入额外开销,自然会导致一定的吞吐量下降。