你应该同步run方法吗?为什么或者为什么不?

问题描述 投票:32回答:7

我一直认为在实现Runnable的java类中同步run方法是多余的。我试图找出人们为什么这样做:

public class ThreadedClass implements Runnable{
    //other stuff
    public synchronized void run(){
        while(true)
             //do some stuff in a thread
        }
    }
}

它似乎是多余的和不必要的,因为它们正在为另一个线程获取对象的锁。或者更确切地说,他们明确表示只有一个线程可以访问run()方法。但是由于它的run方法,它本身不是自己的线程吗?因此,只有它可以访问自己,它不需要一个单独的锁定机制?

我在网上发现了一个建议,通过同步run方法,你可以创建一个事实上的线程队列,例如:

 public void createThreadQueue(){
    ThreadedClass a = new ThreadedClass();
    new Thread(a, "First one").start();
    new Thread(a, "Second one, waiting on the first one").start();
    new Thread(a, "Third one, waiting on the other two...").start();
 }

我个人永远不会这样做,但它提出了为什么有人会同步run方法的问题。任何想法为什么或为什么不应该同步run方法?

java multithreading synchronization synchronized runnable
7个回答
30
投票

同步run(Runnable方法是完全没有意义的,除非你想在多个线程之间共享Runnable并且你想要序列化这些线程的执行。这基本上是矛盾的。

理论上还有一个更复杂的场景,你可能想要同步run()方法,这又涉及在多个线程之间共享Runnable,但也使用wait()notify()。我从未在21年多的Java中遇到过它。


2
投票

使用synchronized void blah()而不是void blah() { synchronized(this) {有一个优点,那就是你的结果字节码将缩短1个字节,因为同步将是方法签名的一部分而不是单独的操作。这可能会影响JIT编译器内联方法的机会。除此之外没有区别。

最好的选择是使用内部private final Object lock = new Object()来防止某人可能锁定您的显示器。它实现了相同的结果,没有邪恶的外部锁定的缺点。你确实有这个额外的字节,但它很少有所作为。

所以我会说不,不要在签名中使用synchronized关键字。相反,使用类似的东西

public class ThreadedClass implements Runnable{
    private final Object lock = new Object();

    public void run(){
        synchronized(lock) {
            while(true)
                 //do some stuff in a thread
            }
        }
    }
}

编辑以回应评论:

考虑同步的作用:它阻止其他线程进入相同的代码块。所以想象你有一个类似下面的类。假设当前大小为10.有人试图执行添加并强制调整后备阵列的大小。当他们正在调整数组的大小时,有人在不同的线程上调用makeExactSize(5)。现在突然间你正试图访问data[6]并且它会炸毁你。同步应该可以防止这种情况发生。在多线程程序中,您只需要NEED同步。

class Stack {
    int[] data = new int[10];
    int pos = 0;

    void add(int inc) {
        if(pos == data.length) {
            int[] tmp = new int[pos*2];
            for(int i = 0; i < pos; i++) tmp[i] = data[i];
            data = tmp;
        }
        data[pos++] = inc;
    }

    int remove() {
        return data[pos--];
    }

    void makeExactSize(int size) {
        int[] tmp = new int[size];
        for(int i = 0; i < size; i++) tmp[i] = data[i];
        data = tmp;
    }
}

2
投票

为什么?最小的额外安全性,我没有看到任何可能的情况会有所作为。

为什么不?这不标准。如果你是作为一个团队的一部分编码,当其他成员看到你的同步run时,他可能会浪费30分钟试图找出你的run或你用来运行Runnable的框架的特别之处。


1
投票

根据我的经验,将“synchronized”关键字添加到run()方法中没有用。如果我们需要同步多个线程,或者我们需要一个线程安全的队列,我们​​可以使用更合适的组件,例如ConcurrentLinkedQueue。


0
投票

那么理论上你可以毫无问题地调用run方法(毕竟它是公共的)。但这并不意味着应该这样做。所以基本上没有理由这样做,除了给调用run()的线程增加可忽略的开销。好吧,除非你多次使用实例调用new Thread - 虽然我是一个)不确定这是否合法使用线程API和b)似乎完全没用。

你的createThreadQueue也行不通。非静态方法上的synchronized在实例对象(即this)上同步,因此所有三个线程将并行运行。


0
投票

浏览代码注释并取消注释并运行不同的块以清楚地看到差异,注意同步只有在使用相同的runnable实例时才有区别,如果每个线程启动得到一个新的runnable它将没有任何区别。

class Kat{

public static void main(String... args){
  Thread t1;
  // MyUsualRunnable is usual stuff, only this will allow concurrency
  MyUsualRunnable m0 = new MyUsualRunnable();
  for(int i = 0; i < 5; i++){
  t1 = new Thread(m0);//*imp*  here all threads created are passed the same runnable instance
  t1.start();
  }

  // run() method is synchronized , concurrency killed
  // uncomment below block and run to see the difference

  MySynchRunnable1 m1 = new MySynchRunnable1();
  for(int i = 0; i < 5; i++){
  t1 = new Thread(m1);//*imp*  here all threads created are passed the same runnable instance, m1
  // if new insances of runnable above were created for each loop then synchronizing will have no effect

  t1.start();
}

  // run() method has synchronized block which lock on runnable instance , concurrency killed
  // uncomment below block and run to see the difference
  /*
  MySynchRunnable2 m2 = new MySynchRunnable2();
  for(int i = 0; i < 5; i++){
  // if new insances of runnable above were created for each loop then synchronizing will have no effect
  t1 = new Thread(m2);//*imp*  here all threads created are passed the same runnable instance, m2
  t1.start();
}*/

}
}

class MyUsualRunnable implements Runnable{
  @Override
  public void  run(){
    try {Thread.sleep(1000);} catch (InterruptedException e) {}
}
}

class MySynchRunnable1 implements Runnable{
  // this is implicit synchronization
  //on the runnable instance as the run()
  // method is synchronized
  @Override
  public synchronized void  run(){
    try {Thread.sleep(1000);} catch (InterruptedException e) {}
}
}

class MySynchRunnable2 implements Runnable{
  // this is explicit synchronization
  //on the runnable instance
  //inside the synchronized block
  // MySynchRunnable2 is totally equivalent to MySynchRunnable1
  // usually we never synchronize on this or synchronize the run() method
  @Override
  public void  run(){
    synchronized(this){
    try {Thread.sleep(1000);} catch (InterruptedException e) {}
  }
}
}

-2
投票

实际上很容易证明“同步或不同步”

如果您的方法调用可以改变对象的内部状态,则“同步”否则不需要

简单的例子

public class Counter {

  private int count = 0; 

  public void incr() {
    count++;
  }

  public int getCount() {
    return count;
  }
}

在上面的示例中,incr()需要同步,因为它将更改count的val,而getCount()同步不是必需的

但是还有另一个极端情况,如果计数是java.lang.Long,Double,Object那么你需要声明为

private volatile long count = 0;

确保ref更新是原子的

基本上这就是你在处理多线程时需要考虑99%的时间

© www.soinside.com 2019 - 2024. All rights reserved.