多线程启动
线程有两种启动方式:实现Runnable接口:继承Thread类并重写run()方法。
1、Thread 与 Runnable
Runnable接口表示线程要执行的任务。当Runnable中的run()方法执行时,表示线程在激活状态,
run()方法一旦执行完毕,即表示任务完成,则表示任务完成,则线程将被停止。
public interface Runnable {
void run();
}
Thread类默认实现了Runnable接口,并且其构造方法的重载形式允许传入Runnable接口对象作为任务。
* Thread类的源代码片段如下:
public class Thread implements Runnable {
private Runnable target;
public void run() {
if (target != null) {
target.run();
}
}
}
通过Thread类的源代码可以发现,线程的两种启动方式,其本质都是实现Thread类中run()方法。而实现Runnable接口,然后传递给Thread类的方式,比Thread子类重写run()方法更加灵活。
2、线程标识
Thread类用于管理线程,如设置线程优先级、设置Daemon属性、读取线程名字和ID、启动线程任务、
暂停线程任务、中断线程等。
为了管理线程、每个线程在启动后都会生成一个唯一的标识符,并且在其生命周期内保持不变。
当线程被终止时,该线程ID可以被重用。而线程的名字更加直观,但是不具有唯一性。
//线程标识测试
class MyRunnable implements Runnable {
@Override
public void run(){
System.out.println("我的任务开始执行。。。");
}
}
public class ThreadTest01 {
public static void main(String[] args) {
for (int i = 0; i < 5 ; i++) {
MyRunnable mr = new MyRunnable();
//启动线程,申请执行任务
Thread th = new Thread(mr);
th.start();
//读取线程名字和ID
System.out.println(th.getId());
System.out.println(th.getName());
}
}
}
运行结果:
3、run()与start()方法
Thread类中的run()方法与start()方法很容易混淆,我们对比分析一下。
public class Thread implements Runnable{
public void run() {
}
public synchronized void start() {
}
}
调动Thread对象的start()方法,使线程对象开始执行任务,这会触发Java虚拟机调用当前线程对象的run()方法。调用后start()方法后,将会导致两个线程并行运行,一个是调用start()方法的当前线程,另一个是执行run()方法的线程。
如果重复调用start()方法,这是一个非法操作,它不会长生更多的线程,反而会导致IllegalThreadStateExcepyion异常。
测试如下:
class MyRunnable1 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + "-w我的任务开始执行。。。");
}
}
public class ThreadTest02 {
public static void main(String[] args) {
//创建任务
MyRunnable1 mr = new MyRunnable1();
//创建线程对象,任务还未执行
Thread th = new Thread(mr);
//启动线程,申请执行任务
th.start();
//再次调用start()会引发异常
th.start();
/**
* 这里我们要特别注意的是,Thread对象调用start()方法之后,触发了JVM底层调用run()方法。
* 如果我们主动调用Thread对象的run()方法,并不能启动一个新线程。
*/
// th.run(); // 测试结果:执行run()方法的线程与主线程ID一样,表明它们是同一个线程。
System.out.println("主线程:" + Thread.currentThread().getId());
}
}
测试1:
//启动线程,申请执行任务
th.start();
测试2:IllegalThreadStateExcepyion异常
//再次调用start()会引发异常
th.start();
th.start();
Thread源码分析
创建Thread类实例,首先会执行registerNatives()方法,它在静态代码块中加载。线程的启动、运行、生命周期管理和调度等都依赖于操作系统,Java本身并不具备与底层操作系统交互的能力。因此线程的底层操作都是使用了native方法,registerNatives()就是用C语言编写的底层线程注册方法。
public class Thread implements Runnable{
private static native void registerNatives();
static {
registerNatives();
}
}
无论通过Thread类的哪种构造方法去创建线程,都需要首先调用init()方法,初始化线程环境,源码如下:
public class Thread{
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
}
在init()方法中,做了如下操作:
1、设置线程名称。
2、将线程的父线程设置为当前线程。
3、获取系统的安全管理SecurityMannager,并获得线程组。SecurityManagezr在Java中被 用来检查应用程序是否能访问一些受权限资源,如文件、套接字(scoket)等。它可以 用在哪些具有高安全的应用程序中。
4、获取线程组的权限检查。
5、在线程组中增加未启动的线程数量
6、设置新线程的属性,包括守护线程属性(默认继承父线程)、优先级(默认继承父线程)、 堆栈大小(如果为0,则默认由JVM分配)、线程组、线程安全控制上下文(一种Java安 全模式,设置访问控制权限)等。
Thread类中start()方法源码:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
它做了如下操作:
1、在线程组中减少未知启动的线程数量。
2、调用底层的native方法start()进行线程启动。
3、最终由底层native方法调用run()执行。
4、如果启动失败,从线程组中移除该线程,并且增加未知启动线程数量。
线程状态
线程对象在不同的运行时期存在着不同的状态,在Thread类中通过一个内部枚举类Start保持状态信息,了就线程状态对于并发编程非常重要。
public class Thread{
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
}
Java中的线程存在6种状态,我们可以通过Thread类中的Thread.getState()方法获取线程在某个时期的线程状态。在给定的时间点,线程只能处于一种状态。
NEW状态
NEW代表着线程新建状态,一个已创建但是未起动(start)的线程处于NEW状态。
public class ThreadTest03 {
public static void main(String[] args) {
//NEW状态
Thread th = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + "-我的任务开始执行。。。");
}
});
System.out.println(th.getName() + ":状态:" + th.getState());
System.out.println("主线程 :" + Thread.currentThread().getId());
}
}
运行:
RUNNABLE状态
RUNNABLE状态表示一个线程正在Java虚拟机中运行,但是这个线程是否获得了出现器分配资源并不确定。调用Thread的start()方法后,线程从NEW状态切换到了RUNNABLE状态。
测试代码:
public class ThreadTest03 {
public static void main(String[] args) {
//测试RUNNABLE状态
Thread th = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ": 状态 :" + Thread.currentThread().getState());
System.out.println(Thread.currentThread().getName() + "-我的任务开始执行。。");
System.out.println(Thread.currentThread().getName() + ": 状态 :" + Thread.currentThread().getState());
}
});
System.out.println(th.getName() + ": 状态 :" + th.getState());
th.start();
System.out.println("主线程:" + Thread.currentThread().getName() + ":状态 :" + Thread.currentThread().getState());
}
运行结果:
BLOCKED状态
BLOCKED为阻塞状态,表示当前线程正在阻塞等待获得监视器锁。当一个线程要访问被其他线程synchronized锁定的资源时,当前线程需要阻塞等待。
代码测试:在主函数中分别启动了两个线程,它们都需要获得Object对象的监听器锁后执行任务。第一个线程启动后,首先获得了Object监视器锁。由于在run()方法中使用了死循环while(true),因此第一个线程获得了Object监视器后不会释放,这导致第二个线程长期处于阻塞等待状态。
public class ThreadTest03 {
public static void main(String[] args) {
Object object = new Object();
Thread th = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
while (true){
}
}
}
});
Thread th2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
while (true) {
}
}
}
});
System.out.println(th.getName() + ": 状态 :" + th.getState());
System.out.println(th2.getName() + ": 状态 :" + th2.getState());
th.start();
System.out.println(th.getName() + ": 状态 :" + th.getState());
for (int i = 0; i < 300000 ; i++) {
//适当延迟后,启动第二个线程
}
th2.start();
System.out.println(th2.getName() + ": 状态 :" + th2.getState());
for (int j = 0; j < 3000000; j++) {
//适当延迟后,重新获取线程状态
}
System.out.println(th.getName() + ": 状态 :" +th.getState());
System.out.println(th2.getName() + ": 状态 :" +th2.getState());
System.out.println("主线程:" + Thread.currentThread().getName() + ": 状态 : " +
Thread.currentThread().getState());
}
}
}
运行结果:
WAITING状态
WAITING表示线程处于等待状态。
在当前线程中调用如下方法之一时,会使当前线程程序进入等待状态:
Object类的wait()方法(没有超时设置);
Thread类的join()方法(没有超时设置);
LockSupport类的park()方法。
处于等待状态的线程,正在等待另外一个线程去完成某个特殊操作。例如,在某个线程中调用了Object 对象的wait()方法,它会进入等待状态,等待Object 对象调用notify()或notiyAll()方法。一个线程对象调用了join()方法,则会等待指定的线程终止任务。
代码测试:注意调用object对象的wait()方法时,必须要先用synchronized锁定object对象。当线程1进入WAITING状态后,后续代码不再执行。直到object对象调用了notify()方法
//测试代码
public class ThreadTest04 {
//测试WAITING状态
public static void main(String[] args) {
Object object = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ", i =" +i);
if (i == 5) {
synchronized (object) {
try {
System.out.println(Thread.currentThread().getName() +
"开始等待。。。");
object.wait();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "running...");
synchronized (object){
try {
System.out.println(Thread.currentThread().getName() +
", 发送notify通知。。。");
object.notify();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
//启动第一个线程
t1.start();
double d = 0;
for (int i = 0; i < 100000; i++) {
//等待第一个线程调用wait()方法
d += (Math.PI + Math.E) / (double)i;
}
//在主线程读取第一个线程的状态
System.out.println(t1.getName() + "状态:" + t1.getState());
//启动第二个线程,发送notify()通知
t2.start();
}
}
运行结果:
注意:调用wait()/notify()方法前需要先锁定object对象,而调用park()和unpark()方法前无须锁定对象。
TIMED_WATING状态
TIMED_WATING 表示线程处于定时等待状态。
在当前线程中调用如下方法之一时,使当前线程进入定时等待状态:
Object 类的 wait()方法(有超时设置);
Thread类的join()方法(有超时设置);
Thread类的sleep()方法(有超时设置);
LockSupport() 类的 parkNanos()方法;
LockSupport()类的 parkUntil()方法。
代码测试:调用object.wait(3000),在指定时间内没有调用object对象notify()或notifyAII(),就会触发超时等待结束,当前线程重新进入RUNNABLE状态。
//测试TIMED_WAITING状态
public static void main(String[] args) {
Object object = new Object();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ",i=" + i);
if (i==5) {
synchronized (object){
try{
System.out.println(Thread.currentThread().getName() +
"开始等待。。。");
object.wait(3000);
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
});
t1.start();
try {
//延迟1秒,等待t1进入等待状态
Thread.sleep(1000);
}catch (Exception e){
}
System.out.println(t1.getName() + "状态:" + t1.getState());
}
运行结果:
调用Thread.sleep(3000),当前线程也会进入定时等待状态。
WAITING 与 BLOCKED 的区别
WAITING、TIMED_WAITING、BLOCKED 这几个线程状态,都会使当前线程处于停顿状态,因此容易混淆。
总结以上这些状态之间的区别:
-
Thread.sleep()不会释放占有的对象锁,因此会持续占用CPU。
-
Object.wait() 会释放占有的对象锁,不会占用CPU。
-
BLOCKED 使当前线程进入阻塞后,为了抢占对象监听器锁,一般操作系统都会给这个线程持续的CPU使用权。
-
LockSupport.park()底层调用 UNSAFE.park()方法实现,它没有使用对象监听器锁,不会占用CPU。
TERMINATED 状态
TERMINATED 表示线程为完成状态。当线程完成其run()方法中的任务,或者因为某些未知的异常而强制中断时,线程状态变为TERMINATED。
测试代码:
//测试TERMINATED状态:
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
double d = 0;
for (int i = 0; i < 1000; i++) {
d += (Math.PI + Math.E)/(double)i;
}
}
});
t1.start();
try {
//等待线程运行结束
Thread.sleep(2000);
} catch (Exception e){
}
System.out.println("t1 线程状态:" + t1.getState());
}
运行结果:
线程状态装换
把线程RUNNABLE状态细分为两种:runnable(准备就绪)和 running(运行中),runnable表明线程刚刚被JVM启动,还没有获得CPU的使用权:running 表示线程获取获得了CPU的使用权,正在运行。
sleep()与yield()方法
线程休眠sleep()
Thread类的sleep()方法,使当前正在执行的线程以指定的毫秒数暂时停止执行,具体停止时间取决于系统定时器和调度程序的精度和准确性。当前线程状态由RUNNABLE切换到TIMED_WAITING。调用sleep()方法不会使线程丢失任何监视器所有权,因此当前线程仍然占用CPU分片。
public class Thread{
public static native void sleep(long millis)
throws InterruptedException;
}
调用sleep()方法可能会抛出InterruptedException异常,它应该在run()方法中被捕获,因为异常无法传递到其他线程,如主线程就无法捕获子线程抛出的异常。
Java SE5 引入了更加显式sleep()版本,作为TimeUit类的一部分。例如,TimeUnit.MILLISECONDS.sleep(1000) 等价余Thread.Sleep(1000),表示休眠1秒,TimeUnit类提供了更好的可读性。
//测试sleep()方法
class Task implements Runnable {
@Override
public void run() {
try {
long beginTime = System.currentTimeMillis();
System.out.println("每隔1秒输出[0,10]区间的整数,开始...");
for (int i = 0; i < 10; i++) {
TimeUnit.SECONDS.sleep(1);
System.out.println("数字:" + i);
}
System.out.println("每隔1秒输出[0,10]区间的整数,结束...");
long endTime = System.currentTimeMillis();
System.out.println("共耗时:" + (endTime - beginTime)/1000 + "秒.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadTest05 {
public static void main(String[] args) {
Task t = new Task();
new Thread(t).start();
}
}
输出结果:
使用独立线程输出【0,10】区间的整数,每输出一个数字,线程通过TimeUnit.SECONDS.sleep(1)语句休眠1秒。
线程让步yield()方法
Thread类都yield()方法对线程调度器发出一个暗示,即当前线程愿意让出正在使用使用的处理器。调度程序可以响应暗示请求,也可以自由地忽略这个提供。(线程调用yield()方法后,可能从running状态转为runnable状态)。
public class Thread{
public static native void yield();
}
需要强调的是:yield()仅仅是一个暗示,没有任务机制保证它一定会被采纳。线程调度器是Java线程机制的底层对象,可以把CPU的使用权从一个线程转移到另一个线程。如果你的计算机是多核处理器,那么分配线程到不同的处理器执行任务要依赖线程调度器。
1、新建任务类ListOff,用于倒计时显示,使用sleep()作为线程延时:
class ListOff implements Runnable {
private int countDown = 5;
@Override
public void run() {
while(countDown-- > 0) {
String info = Thread.currentThread().getId() + "#" + countDown;
System.out.println(info);
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (Exception e) {
}
}
}
}
2、创建一个倒计时器,两个线程同时使用。参见运行2结果可知吗,每次的运行结果都不同,而且都不正确。 其关键原因是countDown唯一,两个线程可能同时访问这块内存,后面我们可以通过加锁的方式解决这个问题:
/**
创建一个倒计时器,两个线程同时使用。参见运行2结果可知吗,每次的运行结果都不同,而且都不正确。
其关键原因是countDown唯一,两个线程可能同时访问这块内存,后面我们可以通过加锁的方式解决这个问题。
*/
public class ThreadTest05 {
public static void main(String[] args) {
ListOff lf = new ListOff(); //创建一个倒计时器
new Thread(lf).start();
new Thread(lf).start();
}
}
第一次运行:
第二次运行:
每次运行都不一样。
3、把sleep()代码修改为yield(),三次测试的结果输出都是正确的。而且yield()方法起到了线程让步的效果(此时没有使用锁,不能保证每次的运行结果都正确):
class ListOff implements Runnable {
private int countDown = 5;
@Override
public void run() {
while(countDown-- > 0) {
String info = Thread.currentThread().getId() + "#" + countDown;
System.out.println(info);
Thread.yield(); //让步暗示
}
}
}
运行结果1:
运行结果2:
线程优先级
每个线程都有优先级,具有较高优先级的线程可能优先获得CPU的使用权。创建一个新的Thread对象时,新线程的优先级默认与创建线程的优先级一致。
JDK中实际上存在着10个优先级,但是这与大多数操作系统不能建立很好的映射关系。比如Windows有7个线程优先级设置,而Sun的Solaris只有两线程优先级,因此在Java中一般使用下面的三种优先级设置。
public class Thread{
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
}
可以通过Thread类中setPriority()方法对线程的优先级进行设置,参考JDK的源码:
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
这里面需要注意的是,如果设置的线程优先级小于 1 (MIN_PRIORITY) 或者大于10 (MAX_PRIOPITY) 都将抛出异常 : IllegalArgunmentException异常。
注意:不能过分依赖于线程优先级的设置,理论上线程优先级高的优先执行,但实际情况可能并不明确,例如,线程调度机制还没有来得及介入时,线程可能就已经执行完了。所以优先级具有一定的“随机性”。
线程优先级与资源竞争
具有较高优先级的线程会优先得到调度系统资源分配。也就是说优先级高的线程可以优先竞选共享资源。但线程的优先级调度和底层操作系统有密切的关系,在各个平台上表现不一并且无法精确控制。因此在要求严格的场合,需要开发者在应用层解决线程调度问题。
当调用Thread.yield()方法时,会给线程调度器一个暗示,即优先级高的其他线程或相同优先级的其他线程,都可以优先获得CPU分片。
案例:大型浮点运算测试
案例场景描述:创建6个线程,每个线程都计算了足够量级的浮点运算(浮点运算比较费时),目的是线程调度机制来得及介入。其中将1个线程的优先级设置为最高(MAX_PRIORITY=10),1个线程的优先级设置为默认(NORM_PRIORITY=5),其余4个线程优先级设置为最低(MIN_PRIORITY=1)。为了保证计算的精度,在代码中使用了BigDecimal对象。
//案例测试
public class ThreadTest06 implements Runnable {
private int priority;
public ThreadTest06(int priority) {
this.priority = priority;
}
@Override
public void run() {
BigDecimal value = new BigDecimal("0");
//按照参数传递值修改当前线程优先级
Thread.currentThread().setPriority(priority);
BigDecimal pi = new BigDecimal(Math.PI);
BigDecimal e = new BigDecimal(Math.E);
//足够耗时的计算,使任务调度器可以反应
for (int i = 0; i < 3000; i++) {
for (int j = 0; j < 3000; j++) {
value = value.add(pi.add(e).divide(pi,4));
}
}
Thread self = Thread.currentThread();
System.out.println("线程编号为" + self.getId() + ", 优先级为"+
self.getPriority() + ", 计算结果为" + value.doubleValue());
}
public static void main(String[] args) {
new Thread(new ThreadTest06(Thread.MIN_PRIORITY)).start();
new Thread(new ThreadTest06(Thread.MIN_PRIORITY)).start();
new Thread(new ThreadTest06(Thread.MIN_PRIORITY)).start();
new Thread(new ThreadTest06(Thread.MIN_PRIORITY)).start();
new Thread(new ThreadTest06(Thread.NORM_PRIORITY)).start();
new Thread(new ThreadTest06(Thread.MAX_PRIORITY)).start();
}
}
运行结果:
守护线程:
守护线程的概念
在Java线程中有两种线程,一种是用户线程,另一种是守护线程(Daemon)。
所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如,垃圾回收线程就是一个很职称的守护线程者(当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用)。
Deamon线程与用户线程在使用时没有任何区别,唯一的不同是:当所有用户线程结束时,程序也会终止,Java虚拟机不管是否存在守护线程,都会退出。
public class ThreadTest07{
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while(true) {
System.out.println(Thread.currentThread().getId() +
",run...");
try {
Thread.sleep(1000);
} catch (Exception e){
}
}
}
});
t1.setDaemon(true); //设置为守护线程 ,会用户线程结束后,此线程也结束!
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getId() + ", i =" + i); //运行5次结束
try {
Thread.sleep(1000);
}catch (Exception e) {
}
}
}
});
t2.start();
System.out.println(Thread.currentThread().getName() + "-主线程结束...");
}
}
运行结果:
结论:
守护线程会随着用户线程的结束然后自动退出。
这里提出一个问题,是不是每使用用户线程会紧跟着一个守护线程?
在使用守护线程时需要注意以下几点:
1、setDeamon()方法必须在start()方法之前设置,否则会抛出一个IllegalThreadStateException异常。不能把正在运行的常规线程设置为守护线程。
2、在守护线程Deamon中产生的新线程也是守护线程,存在着继承性。
3、守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
4、守护线程通常都使用while(true)的死循环来持续执行任务。
案例:清道夫与工作者
案例场景描述:设置守护线程判断每位员工是否可以下班。要求:员工工作时间必须大于或等于8小时才能够下班,否则守护进程不能同意员工下班。
测试:
1、使用随机数模拟工人的上下班时间,工人能否下班由守护线程决定:
class Worker extends Thread {
//上班打卡时间
private Date beginTime;
//下班打卡时间
private Date endTime;
//下班状态
private boolean isStop;
public Worker(Date beginTime) {
this.beginTime = beginTime;
}
public void run() {
Thread self = Thread.currentThread();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
String bStr = sdf.format(beginTime);
System.out.println(self.getId() + "号员工" + bStr + "打卡上班");
Calendar cal = Calendar.getInstance();
cal.setTime(beginTime);
Random random = new Random();
while (true) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
}
if (isStop) {
String eStr = sdf.format(endTime);
System.out.println(self.getId() + "员工号" + eStr + "下班------");
break;
} else {
int hour = random.nextInt(5);
cal.set(Calendar.HOUR_OF_DAY, cal.get(Calendar.HOUR_OF_DAY) + hour);
endTime = cal.getTime();
}
}
}
public void setStop(boolean stop) {
isStop = stop;
}
public boolean getStop(){
return isStop;
}
public Date getEndTime() {
return endTime;
}
public double getWorkerLongTime() {
return (endTime.getTime() - beginTime.getTime())/(1000*60*60);
}
}
2、定义一个守护线程,定时监督工人的下班状态:
class Sweeper extends Thread {
private List<Worker> workers = new ArrayList<Worker>();
public Sweeper() {
this.setDaemon(true); //守护线程
}
public void run(){
while(true) {
for (int i = 0; i < workers.size(); i++) {
Worker worker = workers.get(i);
if (worker.getEndTime() != null && !worker.getStop()) {
double longTime = worker.getWorkerLongTime();
if (longTime >= 8) {
worker.setStop(true);
System.out.println(worker.getId() + "号员工超过8小时,可以下班!");
}else{
System.out.println(worker.getId() + "号员工不足8小时,不能下班...");
}
}
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
}
}
}
}
public void addWorker (Worker worker) {
this.workers.add(worker);
}
}
3、创建三个工人和一个守护线程,模拟下班监督的工作场景:
public class WorkerTest {
public static void main(String[] args) {
Sweeper sweeper = new Sweeper();
sweeper.start();
Worker w1 = new Worker(new Date());
sweeper.addWorker(w1);
Worker w2 = new Worker(new Date());
sweeper.addWorker(w2);
Worker w3 = new Worker(new Date());
sweeper.addWorker(w3);
w1.start();
w2.start();
w3.start();
}
}
运行结果:
补充:
单核CPU与多核CPU的理解:
单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费只有一个工作人员在收费,只有收了费才能通过,那么CPU 就好比收费人员。如果有某个人不想交钱,那么收费人员可以把他 “挂起” (晾着他, 等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不 出来。如果是多线程的话,才能更好的发挥多线程的效率。(现在的服务器都是多
核的)
一个Java应用程序Java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
并行与并发的理解: