我尝试了带有Synchronization,lock.lock()和lock.tryLock()的线程竞赛程序,我发现使用同步和lock.lock()可以正常工作,但是lock.tryLock()本身并不安全。方法无法获取锁定,因此会产生意外结果
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
Resources resources = new Resources();
Thread userThread1 = new Increment(resources);
Thread userThread2 = new Decrement(resources);
userThread1.start();
userThread2.start();
userThread1.join();
userThread2.join();
System.out.println(resources.getCounter());
}
}
private static abstract class UserThread extends Thread {
protected Resources resources;
public UserThread(Resources resources) {
this.resources = resources;
}
}
private static class Increment extends UserThread {
public Increment(Resources resources) {
super(resources);
}
public void run() {
for (int i = 0; i < 10000; i++) {
resources.increemnt();
}
}
}
private static class Decrement extends UserThread {
public Decrement(Resources resources) {
super(resources);
}
public void run() {
for (int i = 0; i < 10000; i++) {
resources.decrement();
}
}
}
private static class Resources {
public ReentrantLock getLock() {
return lock;
}
private ReentrantLock lock = new ReentrantLock();
public int getCounter() {
return counter;
}
private int counter = 0;
public void increemnt() {
if (lock.tryLock()) {
try {
counter++;
} finally {
lock.unlock();
}
}
}
public void decrement() {
if (lock.tryLock()) {
try {
counter--;
} finally {
lock.unlock();
}
}
}
}
}
期望:0,0,0,0,0,0,0,0,0,0实际输出:每次运行的随机数
tryLock
是线程安全的,并且您面临的问题也不是竞争条件(即,原子地递增和递减计数器)。实际上,如果您查看tryLock
方法的文档,那么了解程序的这种行为并不难。这是文档的摘录。
如果没有其他线程持有该锁,则获取该锁,并返回立即将其值设置为true,将锁定保持计数设置为1。如果当前线程已经拥有此锁,则保持计数为加一,该方法返回true。如果锁由另一个线程,则此方法将立即返回值错误。
因此,您每10000次调用递增和递减,并期望以0结尾。但是我们不知道这10000次尝试中有多少次可以成功。那是完全不确定的,并且在不同的运行中会得到不同的结果。但是,最好的方法是使用Atomic变量执行此操作。这是它的外观。
private static class Resources {
private final LongAdder counter = new LongAdder();
public int getCounter() {
return counter.intValue();
}
public void increment() {
counter.increment();
}
public void decrement() {
counter.decrement();
}
}
tryLock
方法是线程安全的。它可靠地完成了它应该做的事情(根据javadoc)。
但是,您使用trylock
的方式导致increment
和decrement
的非线程安全实现。如果要在这种情况下使用trylock
,则需要执行以下操作:
try {
while (!lock.tryLock()) { } // Try to get the lock until you get it
counter++;
} finally {
lock.unlock();
}
但这不是一个好主意,因为您实际上正在忙于等待锁。使用Lock
的更好解决方案是:
try {
lock.lock(); // Block until the lock is acquired.
counter++;
} finally {
lock.unlock();
}
但是如果您追求的是无锁解决方案,那么您应该使用原子类型:
java.util.concurrent.atomic.LongAdder
(javadoc),java.util.concurrent.atomic.AtomicLong
(javadoc)Java以来的所有Java版本。显然,如果有很多争用,LongAdder
的效果会更好:
请注意,线程安全实际上是很难精确定义的概念。您需要从算法的[[correct行为的行为规范开始。然后,您可以说该算法的实现。
定义:当在具有一个处理器的系统上运行时,如果算法符合规范是正确的,那么当有多个处理器时,如果根据规范也是由此,我们看到:始终]正确,则它是线程安全的 。