多线程运行机制 概要

     1.多线程运行特点
    同一段Java程序,在不同虚拟机(JVM)的实现可能会有不同,尤其Java线程。学习线程时,最令我印象深刻的就是那种不确定性、没有保障性,各个线程的运行完全是以不可预料的方式和速度推进,有的一个程序运行了N次,其结果差异性很大,这全要归咎于JVM。
    所谓的多任务是通过周期性地将CPU时间片切换到不同的子任务,虽然从微观上看来,单核的CPU上同时只运行一个子任务,但是从宏观来看,每个子任务似乎是同时连续运行的(但是JAVA的线程不是按时间片分配的。
   
     2.解释:引用一段网友翻译的国外某Java原著中对线程的理解。
    Thread Scheduling
    In Java technology,threads are usually preemptive,but not necessarily Time-sliced(the process of giving each thread an equal amount of CPU time).It is common mistake to believe that "preemptive" is a fancy word for "does time-slicing".
    For the runtime on a Solaris Operating Environment platform,Java technology does not preempt threads of the same priority.However,the runtime on Microsoft Windows platforms uses time-slicing,so it preempts threads of the same priority and even threads of higher priority.Preemption is not guaranteed;however,most JVM implementations result in behavior that appears to be strictly preemptive.Across JVM implementations,there is no absolute guarantee of preemption or time-slicing.The only guarantees lie in the coder’s use of wait and sleep.
    The model of a preemptive scheduler is that many threads might be runnable,but only one thread is actually running.This thread continues to run until it ceases to be runnable or another thread of higher priority becomes runnable.In the latter case,the lower priority thread is preempted by the thread of higher priority,which gets a chance to run instead.
    A thread might cease to runnable (that is,because blocked) for a variety of reasons.The thread’s code can execute a Thread.sleep() call,deliberately asking the thread to pause for a fixed period of time.The thread might have to wait to access a resource and cannot continue until that resource become available.
    All thread that are runnable are kept in pools according to priority.When a blocked thread becomes runnable,it is placed back into the appropriate runnable pool.Threads from the highest priority nonempty pool are given CPU time.
    The last sentence is worded loosed because:
    (1) In most JVM implementations,priorities seem to work in a preemptive manner,although there is no guarantee that priorities have any meaning at all;
    (2) Microsoft Window’s values affect thread behavior so that it is possible that a Java Priority 4 thread might be running,in spite of the fact that a runnable Java Priority 5 thread is waiting for the CPU.
In reality,many JVMs implement pool as queues,but this is not guaranteed hehavior.

     热心网友翻译的版本:
    在java技术中,线程通常是抢占式的而不需要时间片分配进程(分配给每个线程相等的cpu时间的进程)。一个经常犯的错误是认为“抢占”就是“分配时间片”。
    在Solaris平台上的运行环境中,相同优先级的线程不能相互抢占对方的cpu时间。但是,在使用时间片的windows平台运行环境中,可以抢占相同甚至更高优先级的线程的cpu时间。抢占并不是绝对的,可是大多数的JVM的实现结果在行为上表现出了严格的抢占。纵观JVM的实现,并没有绝对的抢占或是时间片,而是依赖于编码者对wait和sleep这两个方法的使用。
    抢占式调度模型就是许多线程属于可以运行状态(等待状态),但实际上只有一个线程在运行。该线程一直运行到它终止进入可运行状态(等待状态)或是另一个具有更高优先级的线程变成可运行状态。在后一种情况下,低优先级的线程被高优先级的线程抢占,高优先级的线程获得运行的机会。
    线程可以因为各种各样的原因终止并进入可运行状态(因为堵塞)。例如,线程的代码可以在适当时候执行Thread.sleep()方法,故意让线程中止;线程可能为了访问资源而不得不等待直到该资源可用为止。所有可运行的线程根据优先级保持在不同的池中。一旦被堵塞的线程进入可运行状态,它将会被放回适当的可运行池中。非空最高优先级的池中的线程将获得cpu时间。
    上一段最后一个句子是不精确的,因为:
    (1)在大多数的JVM实现中,虽然不能保证说优先级有任何意义,但优先级看起来象是用抢占方式工作。
    (2)微软windows的评价影响线程的行为,以至尽管一个处于可运行状态的优先级为5的java线程正在等待cpu时间,但是一个优先级为4的java线程却可能正在运行。
    实际上,许多JVM用队列来实现池,但没有保证行为。

    3.另一种解释
    原稿:jvm是在底层用os实现thread的,所以线程问题在java中反而容易呈现因平台而异的现象,java能不能充分利用双核的硬件资源,是看对应的操作系统的实现的。
    抢占和时间片,可以同时作为调度策略,所谓抢占,也不是优先执行,根据实现不同,可以是时间片加长,或者轮替几率加大。原来是依赖于具体的平台。这是java的平台无关性的一个例外吧,还有一处就是浮点数据类型的精度依赖于具体的芯片。

     回复:java线程实现是基于平台特点的没错,但同样可以保证跨平台的线程设计准确可靠地运行(前提是你设计得对),毕竟多核模拟出的是和单核相似的不确定性的线程执行情况,而确定性的编程设计才是基于这个之上的。关于浮点数和芯片是否有关,你可以看看core java2里面的论述,他用intel的芯片表达浮点数的与众不同说明了浮点数的精度问题。
    线程部分,上面已经有了“我们必须了解到什么是有保障的操作,什么是无保障的操作,以便设计的程序在各种jvm上都能很好地工作。”和你说的跨平台是一个意思,不同平台对应不同的jvm,同样的平台也有不同的jvm。
    我们运行java程序时有一个入口函数main()函数,它对应的线程被称为主线程。一个新线程一旦被创建,就产生一个新调用栈,从原主线程中脱离,也就是与主线程并发执行。
    当提到线程时,很少是有保障的。我们必须了解到什么是有保障的操作,什么是无保障的操作,以便设计的程序在各种jvm上都能很好地工作。比如,在某些jvm实现中,把java线程映射为本地操作系统的线程。这是java核心的一部分。
  1. package edu.hust.test;
  2. public class ThreadPriority implements Runnable {
  3.     
  4.     /*
  5.      * 大多数JVM都有基于Thread Priority的抢先调度机制,但是不是当前运行Thread的Priority一定比其他Thread的Priority高呢?不是的。
  6.      * 当所有Thread的Priority都相同时,JVM的调度实现会选择它喜欢的线程。也许是选择一个去运行,直至其完成;或者用分配时间片的方式,为每个线程提供均等的机会。
  7.      * 所以不能依赖Thread Priority来保证程序执行的顺序,而只能把其作为一种提高程序效率的手段。
  8.      * Thread Priority默认为5:Thread类具有三个静态常量:Thread.MIN_PRIORITY(1) Thread.NORM_PRIORITY(5) Thread.MAX_PRIORITY(10)
  9.      * */
  10.     
  11.     public void run() {
  12.         System.out.println("My name is " + Thread.currentThread().getName() + " and My priority is " + Thread.currentThread().getPriority());
  13.     }
  14.     
  15.     public static void main(String[] args) {
  16.         ThreadPriority threadPriority = new ThreadPriority();
  17.         Thread[] threads = new Thread[10];
  18.         int i = 0;
  19.         for (Thread thread : threads) {
  20.             thread = new Thread(threadPriority);
  21.             thread.setPriority(++i);
  22.             thread.start();
  23.         }
  24.     }
  25.     /*
  26.      * 运行结果:
  27.      * My name is Thread-7 and My priority is 8
  28.      * My name is Thread-8 and My priority is 9
  29.      * My name is Thread-6 and My priority is 7
  30.      * My name is Thread-0 and My priority is 1
  31.      * My name is Thread-4 and My priority is 5
  32.      * My name is Thread-9 and My priority is 10
  33.      * My name is Thread-5 and My priority is 6
  34.      * My name is Thread-3 and My priority is 4
  35.      * My name is Thread-2 and My priority is 3
  36.      * My name is Thread-1 and My priority is 2
  37.      * 
  38.      * 线程是从Thread-0 --> Thread-9依次启动的, 而优先启动Thread的优先级是最低的. 这样二者结合, 打印出以上不确定结果.
  39.      * 如果优先启动的Thread又可以获得最高的优先级, 则打印结果是基本有序的.
  40.      * 
  41.      * 但这种输出结果是没有保障的,Java线程的执行和JVM有关,而JVM又是依赖于OS的。Windows线程是依靠抢占的方式获得cpu时间片的,所以有时候会出现低级别后运行的Thread抢占到cpu时间片的现象。
  42.      * 
  43.      * */
  44. }
  1. package edu.hust.test;
  2. public class ThreadSeq {
  3.     
  4.     public void run() {
  5.         System.out.println("普通打印语句1");
  6.         System.out.println("普通打印语句2");
  7.     }
  8.     
  9.     public static void main(String[] args) {
  10.         Thread t = new Thread() {
  11.             public void run() {
  12.                 System.out.println("线程:" + Thread.currentThread().getName() + " 启动了");
  13.             }
  14.         };
  15.         //这两个运行结果不同
  16.         t.start();
  17.         new ThreadSeq().run();
  18.     }
  19.     
  20.     /*
  21.      * 运行结果比较有趣:
  22.      * 普通打印语句1
  23.      * 线程:Thread-0 启动了
  24.      * 普通打印语句2
  25.      * 
  26.      * 注意输出的顺序,好像跟我们想象的不太一致,为什么呢?一旦调用start()方法,必须给JVM点时间,让它配置进程。而在它配置完成之前,main线程调用了ThreadSeq中的run()方法(注意:只是普通调用,而不是启动),
  27.      * 所以print"普通打印语句1",此时进程配置完毕,t进程开始执行,所以print"线程:Thread-0 启动了",然后main线程再次得到时间片,print"普通打印语句2"。
  28.      * 
  29.      * 然而这个例子的输出顺序是没有保障的。原因见ThreadPriority类。
  30.      * 
  31.      * 补充:线程的启动要调用start()方法,只有这样才能创建新的调用栈。而直接调用run()方法的话,就不会创建新的调用栈,也就不会创建新的线程,run()方法就与普通的方法没什么两样了!
  32.      * 
  33.      * */
  34. }
  1. package edu.hust.test;
  2. public class ThreadOrder implements Runnable {
  3.     public void execute() {
  4.         synchronized(this) {
  5.             System.out.println(Thread.currentThread().getName() + ", 第一步");
  6.             System.out.println(Thread.currentThread().getName() + ", 第二步");
  7.             System.err.println(Thread.currentThread().getName() + ", 第三步");
  8.             System.out.println(Thread.currentThread().getName() + ", 第四步");
  9.             System.out.println("========================================================");
  10.         }
  11.     }
  12.     
  13.     public void run() {
  14.         execute();
  15.     }
  16.     
  17.     public static void main(String[] args) {
  18.         ThreadOrder threadOrder = new ThreadOrder();
  19.         Thread[] threads = new Thread[5];
  20.         
  21.         System.out.println(Thread.currentThread().getName() + "线程的状态为:" + Thread.currentThread().getState());
  22.         
  23.         for (Thread thread : threads) {
  24.             thread = new Thread(threadOrder);
  25.             thread.start();
  26.         }
  27.     }
  28.     
  29. }
  1. package edu.hust.test;
  2. public class SynchronizedTest1 implements Runnable {
  3.     
  4.     //当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
  5.     public void execute() {
  6.         synchronized (this) {
  7.             for (int i = 0; i < 5; i++) 
  8.                 System.out.println(Thread.currentThread().getName() + " is running, the loop num is " + i);
  9.         }
  10.     }
  11.     
  12.     public void run() {
  13.         execute();
  14.     }
  15.     public static void main(String[] args) {
  16.         SynchronizedTest1 synchronizedTest1 = new SynchronizedTest1();
  17.         Thread threadA = new Thread(synchronizedTest1, "红薯Thread");
  18.         Thread threadB = new Thread(synchronizedTest1, "土豆Thread");
  19.         threadA.start();
  20.         threadB.start();
  21.     }
  22.     
  23.     /*
  24.      * 运行结果:
  25.      * 红薯Thread is running, the loop num is 0
  26.      * 红薯Thread is running, the loop num is 1
  27.      * 红薯Thread is running, the loop num is 2
  28.      * 红薯Thread is running, the loop num is 3
  29.      * 红薯Thread is running, the loop num is 4
  30.      * 土豆Thread is running, the loop num is 0
  31.      * 土豆Thread is running, the loop num is 1
  32.      * 土豆Thread is running, the loop num is 2
  33.      * 土豆Thread is running, the loop num is 3
  34.      * 土豆Thread is running, the loop num is 4
  35.      * 
  36.      * 结果分析:只有"红薯Thread"全部执行完毕, 释放了同步锁后, "土豆Thread"才可以执行.
  37.      * 
  38.      * */
  39.     
  40. }
  1. package edu.hust.test;
  2. public class SynchronizedTest2 {
  3.     //当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
  4.     //当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对该object中所有其它synchronized(this)同步代码块的访问将被阻塞。
  5.     public void execute_synchronized1() {
  6.         synchronized (this) {
  7.             for (int i = 0; i < 5; i++)
  8.                 System.out.println(Thread.currentThread().getName() + " 位于/"同步块/"中 " + i);
  9.         }
  10.     }
  11.     
  12.     public void execute_synchronized2() {
  13.         synchronized (this) {
  14.             for (int i = 0; i < 5; i++)
  15.                 System.out.println(Thread.currentThread().getName() + " 位于/"同步块/"中 " + i);
  16.         }
  17.     }
  18.     
  19.     public void execute_none_synchronized() {
  20.         for (int i = 0; i < 5; i++)
  21.             System.out.println(Thread.currentThread().getName() + " 位于/"非同步块/"中 " + i);
  22.     }
  23.     
  24.     public static void main(String[] args) {
  25.         final SynchronizedTest2 synchronizedTest2 = new SynchronizedTest2();
  26.         
  27.         //利用匿名内部类创建Thread
  28.         Thread thread1 = new Thread(new Runnable() {
  29.             public void run() {
  30.                 synchronizedTest2.execute_synchronized1();
  31.             }
  32.         }, "红薯Thread");
  33.         
  34.         Thread thread2 = new Thread(new Runnable() {
  35.             public void run() {
  36.                 synchronizedTest2.execute_none_synchronized();
  37.             }
  38.         }, "土豆Thread");
  39.         
  40.         Thread thread3 = new Thread(new Runnable() {
  41.             public void run() {
  42.                 synchronizedTest2.execute_synchronized2();
  43.             }
  44.         }, "红薯土豆Thread");
  45.         
  46.         thread1.start();
  47.         thread2.start();
  48.         thread3.start();
  49.     }
  50.     
  51.     /*
  52.      * 运行结果:
  53.      * 土豆Thread 位于"非同步块"中 0
  54.      * 红薯Thread 位于"同步块"中 0
  55.      * 土豆Thread 位于"非同步块"中 1
  56.      * 红薯Thread 位于"同步块"中 1
  57.      * 红薯Thread 位于"同步块"中 2
  58.      * 红薯Thread 位于"同步块"中 3
  59.      * 红薯Thread 位于"同步块"中 4
  60.      * 土豆Thread 位于"非同步块"中 2
  61.      * 
  62.      * 红薯土豆Thread 位于"同步块"中 0
  63.      * 红薯土豆Thread 位于"同步块"中 1
  64.      * 红薯土豆Thread 位于"同步块"中 2
  65.      * 红薯土豆Thread 位于"同步块"中 3
  66.      * 红薯土豆Thread 位于"同步块"中 4
  67.      * 
  68.      * 土豆Thread 位于"非同步块"中 3
  69.      * 土豆Thread 位于"非同步块"中 4
  70.      * 
  71.      * 结果分析:
  72.      * 土豆Thread和红薯Thread可以交替打印 说明 --> 当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块
  73.      * 红薯土豆Thread和红薯Thread不可能出现交替打印 说明 --> 当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对该object中所有其它synchronized(this)同步代码块的访问将被阻塞
  74.      * */
  75.     
  76. }
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值