java 中的线程同步方式有以下几种方式: 1. synchronized 关键字 — 内置锁 2. volatile 类型变量 3. java.util.concurrent.atomic 定义的原子变量 4. 显式锁 — java.util.concurrent.locks.ReentrantLock 如果在多线程并发环境中对于共享的变量没有使用上述某个合适的同步机制,那么程序就有可能出现错误。
最常见的线程安全类是无状态类,所谓的“无状态类”指的就是类中不包含任何成员,也不包含其他任何类中成员的引用,他仅由若干个成员方法构成,所有的临时状态都存储在线程栈上的局部变量中,线程栈在线程之间是不可以被共享的,因此这样的类在使用中是绝对安全的,调用者无需再考虑任何同步手段。
原子操作是线程安全的,原子操作意味着从操作的开始到操作的结束都不会被线程调度机制打断,也就是说它能够保证线程在某段时间对资源的独占,并且整段时间内操作是不可分割的。 java 提供了 java.util.concurrent.atomic 包用来实现原子操作,如 AtomicInteger 类提供了创建各种锁所常用的 比较并交换操作,这个操作是原子性的。 需要注意的是,自增操作并不是一个原子操作,AtomicInteger 提供了原子性的自增运算 incrementAndGet。
上面提到了加锁机制,AtomicInteger 可以通过原子操作实现加锁,同时,java 提供了一种“内置锁”机制,也就是 synchronized 关键字:
synchronized (lock) {
// 访问或修改由锁保护的共享状态
}
提到加锁,就要说说死锁,众所周知,对一个锁变量两次获取锁就会引发死锁,因为他想要得到的锁已经被自己持有,他只能等待持有者释放,而他自己因为等待而无暇释放已经占有的锁。 synchronized 关键字通过可重入的方式解决了这个问题,每个线程如果在已经持有内置锁的情况下请求同一把锁,他将正常的进入被锁的代码。
使用加锁的机制来进行线程同步,最大的问题就是线程活跃性,如何保证系统的性能? 此前我的一篇博客中提到,在 java 通过原生的 org.rabbit.client 包来操作 rabbitmq 时,多个线程同时使用一个共享的 connection 创建 channel,由于这一过程被 synchronized 加锁,致使同一时间有大量线程在等待锁的释放,而造成整个系统耗时过长,请求失败率接近 50%