以下是一个简单的工作程序,它使用两个线程来打印计数器:
public class SynchronizedCounter implements Runnable {
private static int i = 0;
public void increment() { i++; }
public int getValue() { return i; }
@Override
public void run() {
for( int i = 0; i < 5; i++ ) {
synchronized( this ) {
increment();
System.out.println( Thread.currentThread().getName() + " counter: " + this.getValue() );
}
}
}
public static void main( String[] args ) {
ExecutorService executorService = Executors.newFixedThreadPool( 2 );
SynchronizedCounter synchronizedCounter = new SynchronizedCounter();
executorService.submit( synchronizedCounter );
executorService.submit( synchronizedCounter );
executorService.shutdown();
}
}
输出符合预期-两个线程按顺序显示计数器。
如果将increment()
和getValue()
声明为synchronized
并注释了synchronized
块代码,则输出将显示可见性和竞争问题。
由于increment()
和getValue()
未被声明为synchronized
,如果可以实现同步,则仅使用synchronized
块(通过监视对象),在什么情况下应将它们声明为synchronized
?
答案 0 :(得分:5)
在需要围绕方法主体的同步块的语义时,声明一个同步的方法。
synchronized void increment() {
...
}
与以下内容完全相同:
void increment() {
synchronized (this) { ... }
}
在上面的代码中这样做的事情是,您不再原子地执行increment()
和getValue()
方法:另一个线程可以插入并在线程调用之间执行这些方法:
Thread 1 Thread 2
increment()
increment()
getValue()
getValue()
这是可能的,但是两个线程不能同时执行increment()
(或getValue()
),因为它们是同步的(*)。
另一方面,如问题代码中所示,围绕调用进行同步意味着这两个调用是原子执行的,因此两个线程无法交织:
Thread 1 Thread 2
increment()
getValue()
increment()
getValue()
(*)实际上,他们都可以同时执行该方法。只是除了一个线程之外的所有线程都将在synchronized (this)
中等待。
答案 1 :(得分:2)
在您的代码中,该类本身不是线程安全的,并且必须由客户端(您的run
方法)来确保线程安全。
如果要使类线程安全,从而使客户端不需要执行任何特定操作即可使用它,则它应具有适当的同步机制,并提出可以在组合操作上运行原子操作的“高级”方法如有必要。
例如,考虑AtomicInteger
,它具有incrementAndGet()
方法,该方法将提供更好的方法。您的run
方法将变为:
for( int i = 0; i < 5; i++ ) {
int newValue = counter.incrementAndGet();
System.out.println(Thread.currentThread().getName() + " counter: " + newValue );
}
答案 2 :(得分:1)
您的increment()
方法使用的i++
实际上是由3条指令代替的。如果未将该方法声明为synchronized
,则多个线程可能会同时执行该方法,这将导致争用条件和错误结果。
如果增量()和getValue()被声明为已同步,并且 注释了同步的块代码,输出显示可见性 和种族问题。
在这种情况下,没有比赛条件。您会看到不同的结果,因为现在increment()
和getValue()
并不是原子执行的(原子上我是指两个方法作为一个同步块),而一个线程名为increment()
并即将调用{{ 1}}的另一个线程也称为getValue()
。
因此,如果您打算在increment()
方法的increment()
块之外调用getValue()
和synchronized
(并且因为它们被声明为run
可以拨打电话。