我正在研究如何在Java中同步方法和块,以避免出现竞争状况,并且我尝试以两种方式解决问题。 问题是,如果我尝试使用同步块,则一切正常,但是使用同步方法时,它会卡住。 我以为我可以在没有太大差异的情况下使用两种方式(也许其中一种在某些情况下会降低并行度,但是我不确定)。我想知道代码中有什么问题,我想问一下是否有使用同步块而不是同步方法的情况更可取。
//不工作
import java.util.Random;
class MultiplicationTable extends Thread {
private Cont obj;
private int number;
private Random r;
public MultiplicationTable(Cont o, int num) {
obj = o;
number = num;
r = new Random();
start();
}
public void run() {
for (int j = 0; j < 10; j++) {
for (int i = 0; i < number; i++) {
obj.incr();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + ": " + obj.getVal());
}
try {
Thread.sleep(r.nextInt(2000));
} catch (InterruptedException e) {
}
}
}
class Cont {
private int count = 0;
private boolean available = false;
public synchronized void incr() {
while (available) {
try {
wait();
} catch (InterruptedException e) {
// TODO: handle exception
}
}
available = true;
count++;
notifyAll();
}
public synchronized int getVal() {
while (!available) {
try {
wait();
} catch (Exception e) {
// TODO: handle exception
}
}
available = false;
notifyAll();
return count;
}
}
public class Es3 {
public static void main(String[] args) {
Cont obj = new Cont();
int num = 5;
MultiplicationTable t1 = new MultiplicationTable(obj, num);
MultiplicationTable t2 = new MultiplicationTable(obj, num);
}
}
//正在工作
import java.util.Random;
class MultiplicationTable extends Thread {
private Cont obj;
private int number;
private Random r;
public MultiplicationTable(Cont o, int num) {
obj = o;
number = num;
r = new Random();
start();
}
public void run() {
synchronized (obj) {
for (int j = 0; j < 10; j++) {
for (int i = 0; i < number; i++) {
obj.incr();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
System.out.println(Thread.currentThread().getName() + ": " + obj.getVal());
}
try {
Thread.sleep(r.nextInt(2000));
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
}
class Cont {
private int count = 0;
public void incr() {
count++;
}
public int getVal() {
return count;
}
}
public class Es3 {
public static void main(String[] args) {
Cont obj = new Cont();
int num = 5;
MultiplicationTable t1 = new MultiplicationTable(obj, num);
MultiplicationTable t2 = new MultiplicationTable(obj, num);
}
}
答案 0 :(得分:1)
我不认为这是骗人的,因为尽管有标题,但实际的问题是OP的特定实现。代码中有一个错误,这不是方法与块的问题。
代码中的错误是您尝试实现锁定机制的地方。在incr()
中,您要等到available
设置为false(仅在getVal()
中发生):
public synchronized void incr() {
while (available) { // <-- bug
try {
wait();
由于您的循环仅调用incr()
而不调用getVal()
,因此在第一次调用incr()
之后,两个线程都被卡住。 (您最终会调用getVal()
,但要在内循环完成之后才能调用。两个线程都处于良好状态,并且到那时为止都处于阻塞状态。)
解决方案:AtomicInteger
没有像这样的怪异错误。如果您尝试实现某种生产者/消费者机制,那么并发队列之一(例如ArrayBlockingQueue
)是更好的解决方案。
答案 1 :(得分:0)
同步方法和块之间的一个重要区别是,同步块通常会减小锁定范围。由于锁定范围与性能成反比,因此锁定仅关键部分的代码总是更好。使用同步块的最佳示例之一是在Singleton模式中进行双重检查锁定,在此模式中,我们仅锁定用于创建Singleton实例的代码的关键部分,而不是锁定整个getInstance()
方法。由于仅需要锁定一到两次,因此可以大大提高性能。
同步块提供了对锁的精细控制,因为您可以使用任意任意锁来对关键节代码提供互斥。另一方面,如果同步方法是静态同步方法,则始终锁定此关键字表示的当前对象或类级别锁定。
如果作为参数提供给块的表达式的计算结果为null,则同步块可以抛出java.lang.NullPointerException
,而同步方法则不会。
对于同步方法,锁是在进入方法时由线程获取的,而在退出方法时通常是通过抛出Exception来释放的。另一方面,在同步块的情况下,线程在进入同步块时获得锁定,而在离开同步块时释放。