Java:线程顺序执行

3

以下代码发现输出结果不是从小到大的顺序,如何保证它按照从小到大的顺序?

Java代码

public class TestSync {  

    /** 
     * @param args 
     */  
    public static void main(String[] args) {  
        for (int i = 0; i < 10; i++) {  
            new Thread(new Thread1()).start();  
        }  

    }  

    public int getNum(int i) {  
        synchronized (this) {  
            i++;  
        }  
        return i;  
    }  

    static class Thread1 implements Runnable {  
        static Integer value = 0;  
        @Override  
        public void run() {  
            TestSync ts = new TestSync();  
            value = ts.getNum(value);  
            System.out.println("thread1:" + value);  
        }  
    }  

}  

如果您想要顺序执行,为什么要使用线程呢? 线程是异步的。 您可以将值存储在向量中,它是线程安全的,在所有操作完成后进行排序,然后打印出来。 - Jianhong
你可以使用Java线程池或ExecutorService。 - Mohammod Hossain
@Nick getNum 对于每个线程都只返回1...你从未实际修改过 value。此外,你真正想要的是带有 incrementAndGet 方法的 AtomicInteger - obataku
6个回答

2
虽然人们会想知道为什么需要这样做,但以下是一种实现方式。虽然不够优雅,但对原始程序的更改最少:
import java.util.concurrent.*;

public class TestSync {

    public static void main(String[] args) {

    ExecutorService service = Executors.newSingleThreadExecutor();
    for (int i = 0; i < 10; i++) {
        service.submit(new Thread1());
    }

}

public int getNum(int i) {
    synchronized (this) {
        i++;
    }
    return i;
}

static class Thread1 implements Runnable {
    static Integer value = 0;
    @Override
    public void run() {
        TestSync ts = new TestSync();
        value = ts.getNum(value);
        System.out.println("thread1:" + value);
    }
}

这里有一个更好的版本。它使用AtomicInteger作为计数器(在这种情况下可能过度)来消除不愉快的getNum()方法:

import java.util.concurrent.*;
import java.util.concurrent.atomic.*;

public class TestSync {  
    static private AtomicInteger i = new AtomicInteger(0);

    public static void main(String[] args) {  
        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {  
            service.submit(new MyThread(i));  
        }  
        try { Thread.sleep(2*1000); } catch(Exception ex) {}
        service.shutdown();
    }  

    static class MyThread implements Runnable {  
        private int num = 0;
        public MyThread(int num) {
            this.num = num;
        }
        @Override  
        public void run() {  
            int value = i.incrementAndGet();
            System.out.println("thread # " + num + " value = " + value);  
        }  
    }  
}  

2
你想要实现什么?你的代码只同步对特定TestSync实例的调用。由于每个线程都创建自己的实例,就好像你根本没有同步任何东西。你的代码没有做任何事情来同步或协调不同线程之间的访问。
我建议以下代码可能更符合你想要实现的目标:
public static void main (String[] args) throws java.lang.Exception {
        for (int i = 0; i < 10; i++) {  
            new Thread1().start();  
        }  
}

//no need for this to be an instance method, or internally synchronized
public static int getNum(int i) {  
       return i + 1;  
}

static class Thread1 extends Thread {  
    static Integer value = 0;  

    @Override  
    public void run() {  
        while (value < 100) {
            synchronized(Thread1.class) {  //this ensures that all the threads use the same lock
                value = getNum(value);  
                System.out.println("Thread-" + this.getId() + ":  " + value);  
            }

            //for the sake of illustration, we sleep to ensure some other thread goes next
            try {Thread.sleep(100);} catch (Exception ignored) {} 
        }
    }  
}

示例网址: http://ideone.com/BGUYY

请注意,getNum() 实际上是多余的。如果您用简单的 value++; 替换 value = getNum(value);,上面的示例将起同样的作用。


我的架构师给了我一个顺序ID生成器的例子,它也使用了同步代码块。我用AtomicLong(在你的情况下是AtomicInteger)重新编写了它,我们获得了40%的性能提升。嗯...哦。糟糕,这是线程顺序的吗?那可能会增加一些复杂性。 - user447607
太棒了!非常感谢! - spiralmoon

1
更不用说,在线程运行时,代码执行的顺序没有任何保证。这是由所谓的时间片轮转引起的。CPU将为特定线程分配“时间片”。当时间片结束时,它会暂停该线程,并允许其他线程获取时间片。最终,它会回到已暂停的线程并给它们额外的时间片,但这实际上取决于CPU。
拥有多个核心和/或超线程允许CPU同时给更多线程分配时间片。
然而,正如我所说,无法保证线程的时间片顺序以及每个单独线程何时暂停和恢复。
这意味着,“i ++”操作(以及相关的打印)完成的顺序不一定是启动线程的顺序。此外,任何在线程之间共享的变量都应该使用“volatile”修饰符声明,以防止值在线程级别缓存。
如果您想强制顺序排序,那么您应该质疑为什么首先要使用线程而不是顺序循环。

0

重写你的 main() 方法,如下:

public static void main(String[] args)
{
    for (int i = 0; i < 10; i++)
    {
        Thread th = new Thread(new Thread1());
        th.start();
        try
        {
            th.join();
        }
        catch (InterruptedException e)
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

0
我不是Java程序员,但同步锁并不能防止getNum()被按顺序调用。而且因为Thread1在独立的线程中运行,调度器可以自由地以任何顺序运行这10个线程。
同步锁保证的是在同一时间只有一个线程可以执行同步块内的代码。
如果你想要顺序执行,那就在一个线程中调用所有的getNums()。或者使用ExecutorService这样的线程队列。

0

实际上有两个问题。

1). 同步块是特定于Thread1实例的。

你应该使用以下任一方法:

 synchronized(TestSync.class) {  
                //  
                //
            }

或者

synchronized(Thread1.class) {  
                //  
                //
            }

2). 核心问题在于,需要同步设置静态变量和您的 System.out.println(),以便generationprinting值的顺序是一致的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接