什么时候应该使用同步方法,何时使用Java中的同步块?

时间:2019-01-23 16:31:51

标签: java multithreading

我正在研究如何在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);
    }
}

2 个答案:

答案 0 :(得分:1)

我不认为这是骗人的,因为尽管有标题,但实际的问题是OP的特定实现。代码中有一个错误,这不是方法与块的问题。

代码中的错误是您尝试实现锁定机制的地方。在incr()中,您要等到available设置为false(仅在getVal()中发生):

public synchronized void incr() {
    while (available) { // <-- bug
        try {
            wait();

由于您的循环仅调用incr()而不调用getVal(),因此在第一次调用incr()之后,两个线程都被卡住。 (您最终会调用getVal(),但要在内循环完成之后才能调用。两个线程都处于良好状态,并且到那时为止都处于阻塞状态。)

解决方案:AtomicInteger没有像这样的怪异错误。如果您尝试实现某种生产者/消费者机制,那么并发队列之一(例如ArrayBlockingQueue)是更好的解决方案。

答案 1 :(得分:0)

  1. 同步方法之间的一个重要区别是,同步块通常会减小锁定范围。由于锁定范围与性能成反比,因此锁定仅关键部分的代码总是更好。使用同步块的最佳示例之一是在Singleton模式中进行双重检查锁定,在此模式中,我们仅锁定用于创建Singleton实例的代码的关键部分,而不是锁定整个getInstance()方法。由于仅需要锁定一到两次,因此可以大大提高性能。

  2. 同步块提供了对锁的精细控制,因为您可以使用任意任意锁来对关键节代码提供互斥。另一方面,如果同步方法是静态同步方法,则始终锁定此关键字表示的当前对象或类级别锁定。

  3. 如果作为参数提供给块的表达式的计算结果为null,则同步块可以抛出java.lang.NullPointerException,而同步方法则不会。

  4. 对于同步方法,锁是在进入方法时由线程获取的,而在退出方法时通常是通过抛出Exception来释放的。另一方面,在同步块的情况下,线程在进入同步块时获得锁定,而在离开同步块时释放。