java多线程之线程--调度

Thread.sleep(500);会让主线程睡眠500毫秒
                        线程的调度
计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。所谓多线程的并发运行,其实是从宏观上看,各个线程轮流获得CPU的使用权才能执行指令,分别执行各自的任务。在可运行池中,会有多个处于就绪态的线程在等待CPU,Java虚拟机的一项任务就是负责线程的调度。线程的调度是指按照特定的机制为多个线程分配CPU的使用权。有两种调度模型:分时调度模型和抢占式调度模型
分时调度模型是指让所有线程轮流获得CPU的使用权,并且平均分配每个线程占用CPU的时间片。
Java虚拟机采用抢占式调度模型,是指优先让可运行池中处于就绪态的线程中优先级高的占用CPU,如果可运行池中线程的优先级相同,那么就随机选择一个线程,使其占用CPU,处于运行状态的线程会一直执行,直至它不得不放弃CPU,一个线程会因为以下原因放弃CPU:
(1)Java虚拟机让当前线程暂时放弃CPU,转到就绪态,使其他线程获得运行机会
(2)当前线程因为某些原因而处于阻塞状态
(3)线程运行结束
值得注意的是,线程的调度不是跨平台的,它不仅取决于Java虚拟机,还依赖于操作系统。在某些操作系统中,即使运行中的线程没有遇到阻塞,也会在运行一段时间后放弃CPU,给其他线程运行的机会。

java线程的调度不是分时的,同时启动多个线程后,不能保证各个线程轮流获得均等的时间片

package t2;

public class Test implements Runnable {
    privatestatic StringBuffer sb = new StringBuffer();
    privatestatic int count;
    
    publicstatic void main(String[] args) throws Exception{
      // TODO Auto-generated method stub
      Thread t1= new Thread(new Test());
      Thread t2 = new Thread(new Test());
      t1.setName("线程1:");
      t2.setName("线程2:");
      t1.start();
      t2.start();
      while(t1.isAlive() || t2.isAlive() ||t3.isAlive())
      {
         Thread.sleep(500);  //主线程进入睡眠
      }
      System.out.println(sb);
    }
    public voidrun()
    {
      for(int i=0; i<20; i++)
      {
         sb.append(Thread.currentThread().getName() + i +"  ");
         if((++count) % 10 == 0)
         {
            sb.append("\n");
         }
      }
    }

}
线程1:0  线程1:1 线程1:2  线程1:3 线程1:4  线程1:5 线程1:6  线程1:7 线程1:8  线程1:9  
线程1:10  线程1:11 线程1:12  线程1:13 线程1:14  线程1:15 线程1:16  线程1:17 线程1:18  线程1:19  
线程2:0  线程2:1 线程2:2  线程2:3 线程2:4  线程2:5 线程2:6  线程2:7 线程2:8  线程2:9  
线程2:10  线程2:11 线程2:12  线程2:13 线程2:14  线程2:15 线程2:16  线程2:17 线程2:18  线程2:19  

从上面的打印结果可以看出,线程1和线程2启动后,线程1先获得CPU的使用权,进入运行状态,直至线程1结束生命周期,线程2就从就绪状态进入运行状态
如果希望明确让一个线程给另外一个线程运行的机会,可以
(1)调整各个线程的优先级
(2)让处于运行状态的线程调用Thread.sleep()方法
(3)让处于运行状态的线程调用Thread.yield()方法
(4)让处于运行状态的线程调用另一个线程的join()方法



1.调整各个线程的优先级
所有处于就绪状态的线程根据优先级存放在可运行池中,优先级低的线程获得较少的运行机会,优先级高的获得较多的运行机会。Thread类的setPriority(int)和getPriority()方法用于设置和读取优先级。优先级用整数表示,取值范围时1-10,Thread类有以下三个静态常量。
MAX_PRIORITY:取值是10,表示最高优先级
MIN_PRIORITY:取值是1,表示最低优先级
DEFAULT_PRIORITY:取值是5,表示默认优先级
每个线程都有其优先级。主线程的默认优先级是Thread.DEFAULT_PRIORITY。如果线程A创建了线程B,那么线程B就和线程A具有同样的优先级
***值得注意的是,尽管Java提供了10个优先级,但它与多数操作系统都不能很好的映射。比如windows2000有7个优先级,并且不是固定的,而sun公司的Solaris操作系统有2^31个优先级。如果希望程序能移植到各个操作系统中,应该确保在设置线程的优先级时,只使用MIN_PRIORITY,DEAULT_PRIORITY,MAX_PRIORITY这三个优先级。这样才能保证在不同的操作系统中,对同样优先级的线程采用同样的调度方式
2.线程睡眠:Thread.sleep()方法
当一个线程在运行中sleep()方法,它就会放弃cpu,转到阻塞状态。Thread类的sleep(longmillis)方法是静态的,millis参数设置睡眠的时间,单位是毫秒。
**假如线程1线程进入睡眠,线程2获得cpu,当线程1结束睡眠后,后先进入就绪状态,假如线程2正在运行,线程1并不一定会立即执行,而是在可运行池中等待获取CPU
3.线程让步:Thread.yield()方法
当线程在运行中执行了Thread类的yield()静态方法时,如果此时具有相同优先级的其他线程处于就绪状态,那么yield()方法将把当前运行的线程放到可运行池中并使另一个线程运行。如果没有相同优先级的可运行进程,则yield()方法什么也不做
sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程。两者的区别在于:
(1)sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低优先级线程一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会
(2)当线程执行sleep(longmillis)方法后,将转到阻塞状态,参数millis制定睡眠时间;当线程执行yield()方法后,将转到就绪状态
(3)sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常
(4)sleep()方法比yield()方法具有更好的可移植性。不能依靠yield()方法来提高程序的并发性能。对于大多数程序员来说,yield()方法的唯一用途是在测试期间人为地提高程序的并发性能,以帮助发现一些隐藏的错误。
4.等待其他线程结束:join()
当前运行的线程可以调用另一个线程的join()方法,当前运行的线程将转到阻塞状态,直至另一个线程运行结束,它才会恢复运行(阻塞恢复到就绪)
public class TestJoinImpl implements Runnable {

   @Override
    public voidrun() {
      for(int i=0; i<20; i++) {
         System.out.println(Thread.currentThread().getName()+ i);
      }
    }
    publicstatic void main(String[] args)  {
      Thread t = new Thread(new TestJoinImpl());
      t.setName("线程1:");
      t.start();
      System.out.println("main:join machine");
      
      try {
         t.join();
      } catch (InterruptedException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }
      
      System.out.println("main:end");
    }
}
main:join machine
main:end
线程1:0
线程1:1
线程1:2
线程1:3
线程1:4
线程1:5
线程1:6
线程1:7
线程1:8
线程1:9
线程1:10
线程1:11
线程1:12
线程1:13
线程1:14
线程1:15
线程1:16
线程1:17
线程1:18
线程1:19
join()方法有两种重载方式:
public void join()
public void join(long timeout)
以上timeout参数设定当前线程被阻塞的时间,以毫秒为单位,如果超过阻塞时间,那么线程就会恢复运行
5.返回当前线程对象的引用:Thread类的currentThread()静态方法返回当前线程对象的引用
6.后台线程
后台线程是指为其他线提供服务的线程,也称为守护线程。Java虚拟机的垃圾回收线程时典型的后台线程,它负责回收其他线程不再使用的内存
后台线程的特点是:后台线程与前台线程相伴相随,只有所有的前台线程都结束生命周期。调用Thread类的setDaemaon(true)方法,就能把一个线程设置为一个后台线程。Thread类的isDaemon()方法用来判断一个线程是不是后台线程
在使用后台线程时,有以下注意点:
(1)Java虚拟机所能保证的是,当所有前台进程结束时,假如后台进程还在运行,Java虚拟机就会终止后台线程。此外,后台线程是否一定在前台线程的后面结束生命周期,还取决于程序的实现。
(2)只有在线程启动前(即调用start()方法以前),才能把它设置为后台线程。如果线程启动后,再调用这个线程的setDaemon()方法,就会导致IllegalThreadStateException异常
(3)由前台线程创建的线程在默认情况下仍然是前台线程,由后台线程创建的线程默认情况下也是后台线程
7.定时器:Timer
在start()方法中定义
public void start() {
    Timer timer= new Timer(true);  //将与Timer有关的线程设为后台线程
    TimerTasktask = new TimerTask(){
       public void run() {
         while(true) {
            任务    
         }
      }
    };
   timer.schedule(task,10,500);  //设置定时任务
}
Timer类本身不是线程类,但是在它的实现中,利用线程来执行定时任务,timer.schedule(task,100,500)表示100毫秒后执行task任务,以后每隔500毫秒执行一次

猜你喜欢

转载自blog.csdn.net/weixin_40751299/article/details/82186055