线程如何共享创建它们的同一实例的字段变量?

时间:2014-04-19 21:38:33

标签: java multithreading runnable

我想测试Runnable接口。 创建实现接口Runnable的类的实例。然后由同一个实例创建三个线程。观察线程如何共享实例的字段变量。 两个问题: 1.为什么这两个结果不像“20,19,18 ...... 1,0”的顺序? 2.为什么这两个结果彼此不同? (我运行代码两次。) 代码如下:

public class ThreadDemo2 {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        TestThread tt = new TestThread();
        Thread t1 = new Thread(tt);
        Thread t2 = new Thread(tt);
        Thread t3 = new Thread(tt);
        t1.start();
        t2.start();
        t3.start();
    }
}
class TestThread implements Runnable {
    public int tickets = 20;
    public void run(){
        while (tickets >= 0){
            System.out.println(Thread.currentThread().getName() + ":the number of tickets is " + tickets--);
        }
    }
}

我运行代码两次。两个结果如下所示。 第一次:

Thread-1:the number of tickets is 20
Thread-2:the number of tickets is 18
Thread-2:the number of tickets is 16
Thread-0:the number of tickets is 19
Thread-2:the number of tickets is 15
Thread-1:the number of tickets is 17
Thread-2:the number of tickets is 13
Thread-0:the number of tickets is 14
Thread-2:the number of tickets is 11
Thread-1:the number of tickets is 12
Thread-2:the number of tickets is 9
Thread-0:the number of tickets is 10
Thread-2:the number of tickets is 7
Thread-1:the number of tickets is 8
Thread-2:the number of tickets is 5
Thread-0:the number of tickets is 6
Thread-2:the number of tickets is 3
Thread-1:the number of tickets is 4
Thread-2:the number of tickets is 1
Thread-0:the number of tickets is 2
Thread-1:the number of tickets is 0

第二次:

Thread-0:the number of tickets is 19
Thread-2:the number of tickets is 18
Thread-2:the number of tickets is 16
Thread-2:the number of tickets is 15
Thread-1:the number of tickets is 20
Thread-2:the number of tickets is 14
Thread-2:the number of tickets is 12
Thread-2:the number of tickets is 11
Thread-0:the number of tickets is 17
Thread-2:the number of tickets is 10
Thread-2:the number of tickets is 8
Thread-1:the number of tickets is 13
Thread-1:the number of tickets is 6
Thread-1:the number of tickets is 5
Thread-2:the number of tickets is 7
Thread-0:the number of tickets is 9
Thread-2:the number of tickets is 3
Thread-1:the number of tickets is 4
Thread-2:the number of tickets is 1
Thread-0:the number of tickets is 2
Thread-1:the number of tickets is 0

4 个答案:

答案 0 :(得分:5)

欢迎来到令人惊叹的并行处理世界。使用线程时,除非你使用锁和障碍等同步机制,否则没有人能保证如何安排进度。

你在这里要做的是打印一个统一的输出流,据说可以显示线程的进展情况。这意味着您正在合并线程中的打印输出,但您无法分辨这些打印是如何交错的。此外,打印不一定按调用函数的顺序完成,有几层缓冲,更糟糕的是 - 对打印代码的实际调用不是通过读取和减量原子完成的。

你可以说变量是重复递减的(虽然它没有使用任何原子/同步机制你甚至不能确定你不会看到重复的结果和减少被覆盖),并且每个线程都不会在打印较低值之后打印更高的值,但在线程之间,消息可能会停止,因此打印失序。

当您在第一个示例中看到时 -

Thread-2:the number of tickets is 16
Thread-0:the number of tickets is 19

线程0实际上首先读取和递减变量,但是打印延迟了(由于上下文切换或其他任何原因)。线程2在其他几个实例已经运行之后运行,但是立即打印它的消息,然后线程0才完成之前的实例。
请注意,您在此处看不到线程0在其间打印任何其他值,其下一次迭代已经读取为14。

修改 为了进一步阐述,这里有一个可能的交错的小例子。

假设每个线程的机器代码是 - (以伪格式组成)

label:
    load [var] -> rax
    dec rax
    store rax -> [var]
    call print function // implicitly uses rax 
    cmp rax, 0
    jg label  /// jump-if-greater

(var是一个内存位置,在堆栈上,例如。)

让我们说你有2个线程正在运行。一种可能的交错可能是 -

thread 0              |   thread 1
------------------------------------
load [var] -> rax     |                          // reads 20
dec rax               |
store rax -> [var]    |
                      |  load [var] -> rax       // reads 19
                      |  dec rax         
                      |  store rax -> [var]
                      |  call print function     // prints 19
                      |  cmp rax, 0           
                      |  jg label 
call print function   |                          //prints 20
cmp rax, 0            |
jg label              |

它有点过于简单化,但它显示了如何获得无序打印的值。任何交错都是可能的,只要在同一个线程内保留订单。

另请注意,您可以使用

thread 0              |   thread 1
------------------------------------
load [var] -> rax     |                          // reads 20
dec rax               |
                      |  load [var] -> rax       // reads 20 again !!!
                      |  dec rax         
                      |  store rax -> [var]
store rax -> [var]    |
...

在这种情况下,您将获得20次打印。

答案 1 :(得分:4)

这是多线程程序的正常行为。只有有限数量的线程可以在一个instatnt处获得CPU,这取决于处理器的容量。在多线程环境中,每个线程获得cpu时间,此顺序可能是顺序也可能不是顺序。

您可以使用'synchronized`语句进行​​顺序处理。尽管该程序用于显示多线程的功能并且使用同步实现了实际目的,但在某些情况下需要进行同步,例如访问共享资源。

以下是herbert schildt完整参考文献中的几行

Java旨在适用于各种环境。一些 这些环境实现多任务处理的根本不同于其他环境。对于 安全性,具有相同优先级的线程应该偶尔产生控制权。这个 确保所有线程都有机会在非抢占式操作系统下运行。 实际上,即使在非抢占式环境中,大多数线程仍然有机会运行, 因为大多数线程不可避免地遇到一些阻塞情况,比如等待 I / O。发生这种情况时,阻塞的线程将被挂起,其他线程可以运行。但, 如果你想要平滑的多线程执行,你最好不要依赖它。也, 某些类型的任务是CPU密集型的。这样的线程主导着CPU。

线程可以有五种状态。 Thread states 当一个线程正在运行时,所有其他线程都在竞争获取CPU。任何线程(根据他们的优先级)都可以获得CPU。所以订单可能不一定是串行的。这就是为什么你每次运行都得到随机输出。

答案 2 :(得分:1)

所以你有三个线程都在处理一个变量。我认为这是有意的。

这就是我们所说的数据竞赛。在每个线程内部有几个操作:读取变量,测试它,读取它,减去一个,写回来,打印它。那些可以在线程之间以任意顺序发生。如果碰巧有两个人在打印它之前减去它,那么你将跳过这个数字。如果其中一个在打印前稍微延迟,那么数字将会出现故障。

如果在读取和写入变量的代码周围添加一个synchronized (this)块,则一次只允许一个线程在tt上运行该代码。确保在一个区块中覆盖循环条件和打印,但不要将整个循环放在块中,否则一个线程将完成所有工作。

while (true) 
    synchronized (this) {
        if (tickets >= 0) {
            System.out.print  (Thread.currentThread().getName());
            System.out.println(":the number of tickets is " + tickets--); 
         } else
            break;
    }

答案 3 :(得分:0)

这对你来说是多线程的。线程被单独安排到处理器上,甚至可以在不同的CPU内核上以真正的并行运行(如果你有一个多核处理器)。 这就是为什么这两个运行会产生不同的结果:线程的调度方式取决于当前环境,它还包括系统中的其他进程。

线程执行可以以各种方式交错,并且来自不同线程的println到达单个输出日志的顺序也可以变化。一个线程可能已经递减了计数器,但在它写入输出日志之前,(a)其他线程可能同时做了其他事情。那个/那些其他线程甚至可能在第一个线程有机会写入旧值之前递减并写入一个更新的值。这些都是可能的有效交错,这就是为什么你可能不会在输出中得到递减的顺序。

哎呀,如果你用更多的线程做更多的测试,你甚至可能会看到从两个不同的线程中弹出两次的相同值!这是因为两个线程可能正在尝试同时读取或写入tickets字段,导致它们都读取相同的值。有各种有趣的并发问题,这就是为什么你需要同步机制,如锁或信号量或原子更新,以获得正确的行为。