Java并发之Thread

2017/02/18 Java Multithreading

java中的多线程是同时执行多个线程的过程。

Thread

线程基本上是一个轻量级的子进程,是最小的处理单元。

如何新建一个Thread

Thread类实现了Runnable接口,其构造方法有以下几种:

  • Thread()
  • Thread(String name)
  • Thread(Runnable r)
  • Thread(Runnable r,String name)

Thread内部有一个名为target的Runnable属性,由private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc)方法初始化。init方法在Thread类的构造方法里被调用。

线程的生命周期(状态)有哪些?

java 8中Thread.State内部枚举类有6种状态。分别如下图:

一个线程在给定时间点只可以处于一个状态。 这些状态是虚拟机中的状态,并不反映操作系统中的线程状态。

New

线程实例化后还没执行start方法时的状态。

Runnable

线程执行start方法,但线程调度器还没选择它作为running thread。

BLOCKED

线程在等待monitor 锁而被阻塞的状态。

WAITING

线程在等待另一个线程执行特定动作时的状态。

TIMED_WAITING

线程在等待另一个线程执行动作达到指定等待时间时的状态。

TERMINATED

已退出的线程处于此状态。

如何创建线程?

创建线程有两种方式:

  1. 继承Thread类,覆盖run方法。
  • 这一方法创建出来的线程之间内部属性不共享。
  1. 实现Runnable接口,重写其run方法,并将创建的Runnable对象传给Thread对象。
  • 使用这一方法将同一Runnable对象传给多个Thread对象时,这些线程内部属性共享Runnable对象中的属性,应注意线程安全问题。
  • 传入的Runnable对象将赋给target,start时调用其run方法。

如何执行线程?

  • 创建一个Thread类的对象不会创建新的执行线程。同样,调用实现Runnable接口的 run()方法也不会创建一个新的执行线程。只有调用Thread对象的start()方法才能创建一个新的执行线程。
  • start是启动线程的方法,通知Thread Scheduler此线程已经就绪,线程启动后会进行回调(callback)调用run方法。
    • Thread类中,默认调用关系为:start->start0->run->target.run
  • 每个线程的start方法只能被调用一次,重复调用会抛出IllegalThreadStateException异常。
  • 直接调用Thread对象的run方法而非start方法,只会在当前线程中执行run方法,不会开始新的线程。

线程调度

JVM中的Thread Scheduler负责决定执行哪个线程。

抢占式调度与时间分片之间有什么区别?

  • 在抢占式调度中,执行最高优先级任务,直到它进入等待或死亡状态或更高优先级的任务出现。
  • 在时间切片下,任务执行预定义的时间片段,然后重新进入就绪任务池。
  • 调度器根据优先级和其他因素确定下一个执行哪个任务。

线程的优先级

Java中的线程优先级为0-10的整型数,该值只有相对意义,没有绝对意义。

  • 线程的优先级具有继承性. 如,线程A启动线程B,则B和A优先级一样
  • 线程的优先级具有规则性. CPU尽量倾向于把资源优先级高的线程
  • 线程的优先级具有随机性. 优先级不等同于执行顺序,二者关系不确定

守护线程

Thread分为用户线程和守护(Daemon)线程。

  • 守护线程的优先级非常低,通常在程序里没有其他线程运行时才会执行它。
  • 当守护线程是程序里唯一在运行的线程时,JVM会结束守护线程并终止程序。
  • 只能在start() 方法之前可以调用 setDaemon() 方法。一旦线程运行了,就不能修改守护状态。setDaemon(true); 让此线程成为守护线程。
  • 可以使用 isDaemon() 方法来检查线程是否是守护线程(方法返回 true) 或者是使用者线程 (方法返回 false)。
  • 最典型的就是垃圾回收器。

如何停止线程?

  1. 使用退出标识,使线程正常退出,即run方法中判断完成后线程终止。
  2. 使用interrupt方法中断线程。
  3. 使用stop方法,不安全,已过期。

常用方法

sleep

  • 静态方法,使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁 。

    • 如果有synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕捉InterruptedException

      异常。

  • sleep()可以使低优先级的线程得到执行的机会,也可以让同优先级、高优先级的线程有执行的机会。

  • 休眠时间结束后会自动回到Runnable状态。

  • 当线程sleep时被interrupt,会立刻抛出InterruptedException异常并不会一直等到睡眠时间过去。

wait

  • 使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,当前线程被放入对象等待池(wait pool)中。
    • 因为会对对象的“锁标志”进行操作,所以必须在synchronized语句块内使用,否则运行时会发生IllegalMonitorStateException的异常。
    • 只有调用对象的notify或者notifyAlll方法才能唤醒等待池中的线程进入等锁池(lock pool),此时为BLOCKED状态,直到线程重新获得对象的锁就可以进入Runnable状态。

sleep()和wait()有什么区别

  • sleep()是Thread类的静态方法;wait()方法是Object类里的方法
  • sleep()睡眠时,保持对象锁,仍然占有该锁;wait()睡眠时,释放对象锁
  • 在sleep()休眠时间期满后,直接进入就绪状态;wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程,仍需等待对象的锁才能进入就绪状态。
  • wait()必须放在synchronized block中,否则会在runtime时扔出java.lang.IllegalMonitorStateException异常。
方法 是否释放锁 备注
wait wait和notify/notifyAll是成对出现的, 必须在synchronize块中被调用
sleep 可使低优先级的线程获得执行机会
yield yield方法使当前线程让出CPU占有权, 但让出的时间是不可设定的

yield

  • yield()没有参数,放弃当前的CPU资源,不会释放锁标志。
  • 先检测当前是否有相同优先级的线程处于可运行状态,如有,则把CPU的占有权交给该线程,否则继续运行原来的线程,所以yield()方法称为“退让”,它只把运行机会让给了同等级的其他线程。

sleep()和yeild()有什么区别

  • sleep()方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
  • 线程执行sleep()方法后转入阻塞(blocked)状态,而执行yield()方法后转入就绪(ready)状态;
  • sleep()方法声明抛出InterruptedException,而yield()方法没有声明任何异常;
  • sleep()方法可设置睡眠时间,yield()方法没有参数。

join

  • t.join()方法阻塞调用此方法的线程(calling thread),直到线程t(called thread)完成,此线程再继续;通常用于在main()主线程(此时为calling thread)内,等待其它线程完成再结束main()主线程。注意该方法要捕捉InterruptedException

    异常。

  • Join方法实现是通过wait方法。 当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用t.isAlive判断t是否存活,main线程通过调用t.wait交出t的锁,直到该对象唤醒main线程 ,即线程t结束后其start方法会调用this.notifyAll使得main方法被唤醒。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁,否则无法正常进入阻塞。

    while (isAlive()) {
    	wait(0);
    }
    

interrupt

  • Java提供中断机制来通知线程表明我们想要结束它。中断机制的特性是线程需要检查是否被中断,而且还可以决定是否响应结束的请求。所以,线程可以忽略中断请求并且继续运行。
  • interrupted()和isInterrupted()
    • interrupted()是类的静态方法,测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能。
    • isInterrupted()是类的实例方法,测试Thread对象是否已经是中断状态,但不清除状态标志。

currentThread()

currentThread()是Tread中的静态方法,当前线程则是指正在运行的那个线程。例如,直接在main方法里调用某线程的run方法,和调用该线程的start方法,里面打印出的当前线程结果是不同的。

currentThread()和this有什么区别?

Thread t=new Thread(r)会将Runnable对象r(此时r可以为一个Thread对象,因为Thread实现了Runnable)绑定到t1的一个pravite变量target上,

  • 在t被执行的时候即t.run()被调用的时候,它会调用target.run()方法,也就是说它是直接调用r的run方法,

  • 再确切的说,在run方法被执行的时候,this.getName()实际上返回的是target.getName(),即this指向的还是

    r,而Thread.currentThread().getName()实际上是t.getName()。

其它方法

  • isAlive()测试线程是否处于活动状态,即已经启动且未终止。
  • getId()取得线程唯一标识

  • 弃用的API:stop(),suspend(),resume()等,已经弃用了,因为可能产生数据不同步等问题。
Show Disqus Comments

Search

    Post Directory