为什么不同时启动所有线程?

时间:2015-07-19 15:42:22

标签: java multithreading synchronization

public class B {
    public static String lock = "a";
    public static void main(String[] args) {
        MyThread t1 = new MyThread("Thread 1");
        t1.start();

        lock = "b";
        MyThread t2 = new MyThread("Thread 2");
        t2.start();

        lock = "c";
        MyThread t3 = new MyThread("Thread 3");
        t3.start();

        lock = "d";
        MyThread t4 = new MyThread("Thread 4");
        t4.start();
    }

}

class MyThread extends Thread{

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        synchronized (B.lock){
            System.out.println(Thread.currentThread().getName() +" is going to sleep for 5 seconds");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " done sleeping ");
        }
    }
}

输出:

Thread 1 is going to sleep for 5 seconds
Thread 2 is going to sleep for 5 seconds
Thread 2 done sleeping 
Thread 1 done sleeping 
Thread 4 is going to sleep for 5 seconds
Thread 4 done sleeping 
Thread 3 is going to sleep for 5 seconds
Thread 3 done sleeping

对于不明确的问题,我们深表歉意。但我在这里的查询是,因为我每次在线程启动后更改锁定对象,为什么不是所有线程同时启动并锁定不同的字符串对象?我猜测可能是操作系统线程调度的原因。但是每次执行导致仅同时启动2个线程(1和2)并且剩余2个线程(3& 4)等待获取锁定。但为什么呢?

3 个答案:

答案 0 :(得分:2)

如果要同时启动所有线程,则需要提供一些机制,通过该机制,线程可以在开始工作之前知道其他线程已准备好执行。

java.util.concurrent.CountDownLatch这样的东西会对此有所帮助。基本的想法是,你在线程中做的第一件事是等待CountDownLatch;只有在创建和启动所有线程时,才会将锁存器计数为零。

例如,在您的main方法中:

CountDownLatch latch = new CountDownLatch(4);

MyThread t1 = new MyThread("Thread 1", latch);
t1.start();
//...
MyThread t4 = new MyThread("Thread 4", latch);
t4.start();    

MyThread

class MyThread extends Thread{
    private final CountDownLatch latch;
    public MyThread(String name, CountDownLatch latch) {
        super(name);
        this.latch = latch;
    }

    @Override
    public void run() {
        latch.countDown();
        latch.await();
        synchronized (B.lock){
          //...
        }
    }
}

线程现在都会同时尝试进入synchronized块。显然,其中只有一个会在任何时候执行该块。

答案 1 :(得分:2)

这个答案的灵感来自Andy Turner's comment

关于为什么程序的行为:B.lock不是您认为的值。我稍微修改了你的源代码。

public class B {
    public static String lock = "a";

    public static void main(String... args) {
        lock = "a";

        MyThread t1 = new MyThread("Thread 1");
        t1.start();
        lock = "b";

        MyThread t2 = new MyThread("Thread 2");
        t2.start();
        lock = "c";

        MyThread t3 = new MyThread("Thread 3");
        t3.start();
        lock = "d";

        MyThread t4 = new MyThread("Thread 4");
        t4.start();
        // lock = "e";
    }
}

class MyThread extends Thread {

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        String s = B.lock;
        synchronized (s) {
            System.out.println(  Thread.currentThread()
                                       .getName()
                               + " is going to sleep for 5 seconds");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(  Thread.currentThread().getName()
                               + " done sleeping ");
        }
        System.out.println(Thread.currentThread().getName() + ": " + s);
    }
}

请理解,从现在开始,我只能解释我的机器的行为,因为行为高度依赖于您运行它的主机系统。但是,我提供的解决方案应独立于主机系统。

如果我执行此代码,我得到一些这样的输出:

  

线程2将睡眠5秒

     

线程3将睡眠5秒

     

线程1将睡眠5秒

     

线程1完成睡眠

     

主题1:b

     

线程3完成了睡眠

     

线程2完成了睡眠

     

主题2:c

     

主题3:d

     

线程4将睡眠5秒

     

线程4完成了睡眠

     

主题4:d

正如您所看到的,线程的开始被轻微延迟。到目前为止,main方法已经更改了lock字段。因此最后一个线程开始得很晚。

如果我取消评论最后一行,该程序按预期运行(但这绝不是保证)。

要解决此问题,我向Object lock添加了MyThread并通过构造函数进行设置。

public class B {
    public static String lock = "a";

    public static void main(String... args) {
        lock = "a";

        MyThread t1 = new MyThread("Thread 1", lock);
        t1.start();
        lock = "b";

        MyThread t2 = new MyThread("Thread 2", lock);
        t2.start();
        lock = "c";

        MyThread t3 = new MyThread("Thread 3", lock);
        t3.start();
        lock = "d";

        MyThread t4 = new MyThread("Thread 4", lock);
        t4.start();
    }
}

class MyThread extends Thread {
    Object lock;

    public MyThread(String name, Object lock) {
        super(name);
        this.lock = lock;
    }

    @Override
    public void run() {
        synchronized (lock) {
            System.out.println(  Thread.currentThread()
                                       .getName()
                               + " is going to sleep for 5 seconds");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(  Thread.currentThread().getName()
                               + " done sleeping ");
        }
        System.out.println(Thread.currentThread().getName() + ": " + lock);
    }
}

通过这种方式,您可以将lock的获取与其实际设置分离,并且您可以完全控制lock中使用的Thread对象。

答案 2 :(得分:1)

一个教育提示是尝试在您的帖子中打印实际的B.lock以及您的消息:

@Override
public void run() {
    String currLock = B.lock;
    synchronized (currLock){
        System.out.println(Thread.currentThread().getName() +" is going to sleep for 5 seconds locking " + currLock);
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " done sleeping ");
    }
}

系统之间可能有所不同,但我得到了这个输出:

Thread 1 is going to sleep for 5 seconds locking c
Thread 3 is going to sleep for 5 seconds locking d
Thread 1 done sleeping 
Thread 3 done sleeping 
Thread 2 is going to sleep for 5 seconds locking c
Thread 4 is going to sleep for 5 seconds locking d
Thread 4 done sleeping 
Thread 2 done sleeping 

(好吧,有时我会得到一些不同的输出,但让我们来看看我们在这里得到的东西)。

当线程1锁定锁定时,它已经更改了两次值。所以它锁定"c"。看来,线程2也很不幸得到"c",因为它已被锁定,它正在等待而不是打印。

当线程3运行时,该值已经更改为"d",这就是它锁定的内容。线程4也得到了锁定,所以它也被延迟了。

因此,一些延迟可能是由于操作系统无法真正一起运行线程。但其中一些原因是由于start()无法保证run方法在main的下一步开始之前启动run()方法。到控件转移到lock时,Thread.start()可能会更改两次值。操作本可以重新排序。

run实际上没有说线程会立即启动 - 它需要被调度,并且需要为"a"方法准备一个框架,到那时,可能正在发生其他线程 - 比如分配字符串,创建其他线程等等。

你几乎可以保证线程1不会锁定在CREATE TEMPORARY TABLE TEMP (half TIME); DROP PROCEDURE IF EXISTS insertTEMP; DELIMITER // CREATE PROCEDURE insertTEMP (first TIME, last TIME) begin WHILE first <= last DO INSERT INTO TEMP Values (first); SET first = ADDTIME(first, "00:30"); END WHILE; END // DELIMITER ; CALL insertTEMP("04:00", "12:00"); SELECT TEMP.half `Time`, COUNT( actions.name ) `count`, GROUP_CONCAT(actions.name) actions FROM (TEMP, actions) WHERE TEMP.half between actions.rowStarTime AND actions.rowEndTime GROUP BY half ,因此会有一些争夺锁定。