我对线程的经验不多,但是我用Atomics编写了一个漂亮的非阻塞顺序Id生成器....它让我在测试中获得了非常显着的性能提升。现在我想知道为什么有人会使用synchronized,因为速度要慢得多......现代64位多核硬件是否有原因?其他人现在问我有关Atomics的事。我希望能够告诉他们永远不要使用该关键字,除非他们部署到古代硬件。
答案 0 :(得分:2)
因为你不能仅仅使用原子来做多个动作(从技术上来说,你可以,因为你可以使用原子来实现“锁定”,但我认为这不是重点)。你也不能使用原子进行阻塞等待(你可以做一个忙碌的等待,但这几乎总是一个坏主意)。
这是OP的练习:编写一个程序,使用多个线程将带时间戳的日志消息写入同一文件,消息必须以时间戳顺序显示在文件中。仅使用原子实现此功能,但无需重新发明ReentrantLock / synchronized。
答案 1 :(得分:2)
现在我想知道为什么有人会使用synchronized,因为它太慢了......
也许是因为速度不是一切。
事实上,如果你客观地看到在真实应用程序中使用“漂亮”生成器的整体性能优势,我怀疑你会发现它太小而无关紧要。分析应用程序会告诉你。
然后是你的基准测试是否真正有效的问题;即你是否正在做所需的事情,以避免误导效果,如JVM热身异常,优化异常等。而且你是否(实际上)测量了争用和无竞争的案例。
是否有可行的用例,其中Java synchronized关键字优于Atomics?
这很容易。
您需要对一个或多个数据结构进行独占访问以执行一系列操作或本质上不是线程安全的操作的情况。 AtomicXxx
类型不支持此类内容。
我希望能够告诉他们永远不要使用该关键字,除非他们部署到古代硬件。
不要告诉他们。这是不正确的。事实上,如果您不熟悉Java线程,我建议您在开始为人们提供建议之前阅读Goetz等人的“Java Concurrency in Practice”。
答案 2 :(得分:1)
取决于你在做什么 - 如果你只需要原子为你提供的功能,那么是的,就没有必要自己做同样的工作(使用synchronized关键字)。但是,许多多线程应用程序比仅需要原子地增加数字要复杂得多。
例如,您可能需要完成一项工作单元,您可以在内存中修改多个数据结构,所有这些都必须不受干扰地发生 - 您可以使用同步函数或块。
答案 3 :(得分:0)
据我所知,synchronized关键字实际上是一个中等重量级的递归(重入)锁。
例如,以下(可怕的)代码不会死锁:
public static Object lock = new Object();
int recurCount = 0;
public int fLocktorial(int n) {
synchronized(lock) {
recurCount++;
if (n <= 0)
return 1;
return n * fLocktorial(n-1);
}
}
实现这一点需要在锁中维护额外的状态和逻辑,这可能导致其在原子和其他原语上的性能降低。但是,它确实允许您在函数内任意获取锁,而不必担心调用者是否已获得锁。在这种情况下,使用Atomics天真地实现的锁定将会死锁。
此外,如果在锁内进行大量处理,则synchronized可能会产生性能优势。获取锁只会导致性能下降一次,而原子会强制每次操作进行核心同步。这会冲洗处理器管道,从而影响性能。
答案 4 :(得分:0)
从概念上讲,受锁定保护的关键部分会将状态从一个有效状态转换为另一个有效状态。
int x, y; // invariant: x==y
void inc()
synchronized(lock)
x++;
y++;
void dec()
...
我们可以将状态封装在对象中,并以原子方式更改对象。
class State
final int x, y;
State(int x, y) { ... }
volatile State state;
void inc()
do
State s = state;
State s2 = new State(s.x+1, s.y+1);
while( ! compareAndSet( "state", s, s2) ) // use Unsafe or something
这样更好吗?不必要。它很有吸引力,当状态变得更复杂时它更简单;但在大多数情况下它可能会更慢。