线程对象
每一个线程都是和类Thread的实例相关联的。在Java中,有两种基本的使用Thread对象的方式,可用来创建并发性程序。
1.在应用程序需要发起异步任务的时候,只要生成一个Thread对象即可(继承Thread类和实现runnable接口),这样可以直接控制线程的创建并对其进行管理。
2.把应用程序的任务交给执行器(executor),这样可以将对线程的管理从程序中抽离出来。
Thread类中的静态方法
Thread类中的静态方法表示操作的线程是"正在执行静态方法所在的代码块的线程"。为什么Thread类中要有静态方法,这样就能对CPU当前正在运行的线程进行操作。下面来看一下Thread类中的静态方法:
1、currentThread()
currentThread()方法返回的是对当前正在执行线程对象的引用。看一个重要的例子,然后得出结论:
public class MyThread04 extends Thread { static { System.out.println("静态块的打印:" + Thread.currentThread().getName()); } public MyThread04() { System.out.println("构造方法的打印:" + Thread.currentThread().getName()); } public void run() { System.out.println("run()方法的打印:" + Thread.currentThread().getName()); } } public static void main(String[] args) { MyThread04 mt = new MyThread04(); mt.start(); }
看一下运行结果:
静态块的打印:main
构造方法的打印:main
run()方法的打印:Thread-0
这个例子说明了,线程类的构造方法、静态块是被main线程调用的,而线程类的run()方法才是应用线程自己调用的。
1、睡眠 (public static native void sleep(long millis) throws InterruptedException;)(是Thread类的静态方法)
Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)静态方法强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。该方法的作用是在指定的毫秒内让当前"正在执行的线程"休眠(暂停执行)。这个"正在执行的线程"是关键,指的是Thread.currentThread()返回的线程。根据JDK API的说法,"该线程不丢失任何监视器的所属权",简单说就是sleep代码上下文如果被加锁了,锁依然在,但是CPU资源会让出给其他线程。
线程睡眠的原因:线程执行太快,或者需要强制进入下一轮,因为Java规范不保证合理的轮换。
睡眠的实现:调用静态方法。
try {
Thread.sleep(123);
} catch (InterruptedException e) {
e.printStackTrace();
}
睡眠的位置:为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程中会睡眠。
注意:
1、线程睡眠是帮助所有线程获得运行机会的最好方法。
2、线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。
3、sleep()是静态方法,只能控制当前正在运行的线程。
2、线程的优先级和线程让步yield() (是Thread类的静态方法)
线程的让步是通过Thread.yield()来实现的。yield()方法的作用是:暂停当前正在执行的线程对象,并执行其他线程。
要理解yield(),必须了解线程的优先级的概念。线程总是存在优先级,优先级范围在1~10之间。JVM线程调度程序是基于优先级的抢先调度机制。在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况。
注意:当设计多线程应用程序的时候,一定不要依赖于线程的优先级。因为线程调度优先级操作是没有保障的,只能把线程优先级作用作为一种提高程序效率的方法,但是要保证程序不依赖这种操作。
当线程池中线程都具有相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操作有两种可能:一是选择一个线程运行,直到它阻塞或者运行完成为止。二是时间分片,为池内的每个线程提供均等的运行机会。
设置线程的优先级:线程默认的优先级是创建它的执行线程的优先级。可以通过setPriority(int newPriority)更改线程的优先级。例如:
Thread t = new MyThread();
t.setPriority(8);
t.start();
线程优先级为1~10之间的正整数,JVM从不会改变一个线程的优先级。然而,1~10之间的值是没有保证的。一些JVM可能不能识别10个不同的值,而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级的线程可能被映射为一个优先级。
线程默认优先级是5,Thread类中有三个常量,定义线程优先级范围:
static int MAX_PRIORITY
线程可以具有的最高优先级。
static int MIN_PRIORITY
线程可以具有的最低优先级。
static int NORM_PRIORITY
分配给线程的默认优先级。
3、Thread.yield()方法
Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
结论:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
看一下例子:
public class MyThread08 extends Thread { public void run() { long beginTime = System.currentTimeMillis(); int count = 0; for (int i = 0; i < 50000000; i++) { Thread.yield(); count = count + i + 1; } long endTime = System.currentTimeMillis(); System.out.println("用时:" + (endTime - beginTime) + "毫秒!"); } } public static void main(String[] args) { MyThread08 mt = new MyThread08(); mt.start(); } —————————————————————————————————————————————————————————————————————————————————————————————————— 结果: 用时:3264毫秒! 用时:3299毫秒! 用时:3232毫秒! 用时:3256毫秒! 用时:3283毫秒! 用时:3504毫秒! 用时:3378毫秒!
看到,每次执行的用时都不一样,证明了yield()方法放弃CPU的时间并不确定。
4. isDaeMon、setDaemon(boolean on) (是Thread的实例方法)
讲解两个方法前,首先要知道理解一个概念。Java中有两种线程,一种是用户线程,一种是守护线程。守护线程是一种特殊的线程,它的作用是为其他线程的运行提供便利的服务,最典型的应用便是GC线程。如果进程中不存在非守护线程了,那么守护线程自动销毁,因为没有存在的必要,为别人服务,结果服务的对象都没了,当然就销毁了。理解了这个概念后,看一下例子:
public class MyThread1 extends Thread { private int i = 0; public void run() { try { while (true) { i++; System.out.println("i = " + i); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { try { MyThread1 mt = new MyThread1(); mt.setDaemon(true); mt.start(); Thread.sleep(5000); System.out.println("我离开thread对象再也不打印了,我停止了!"); } catch (InterruptedException e) { e.printStackTrace(); } } ———————————————————————————————— 结果: i = 1 i = 2 i = 3 i = 4 i = 5 我离开thread对象再也不打印了,我停止了! i = 6
要解释一下。我们将MyThread1线程设置为守护线程,看到第6行的那句话,而i停在6不会再运行了。这说明,main线程运行了5秒多结束,而i每隔1秒累加一次,5秒后main线程执行完结束了,MyThread1作为守护线程,main函数都运行完了,自然也没有存在的必要了,就自动销毁了,因此也就没有再往下打印数字。
关于守护线程,有一个细节注意下,setDaemon(true)必须在线程start()之前