本章 是补充章节(学的时候跳过了这一章节),简单的就不写程序了.直接把笔记总结.
本章内容包括
- 线程的创建和运行
- 线程信息的获取和设置
- 线程的中断
- 线程中断的控制
- 线程的休眠和恢复
- 等待线程的终止
- 守护线程的创建和运行
- 线程中不可空异常的处理
- 线程局部变量的使用
- 线程的分组
- 线程组中不可空异常的处理
- 使用工厂类创建线程
1.1.简介
- 并发(Concurrency):
只一系列任务的同时运行,如果电脑有多个处理器或者有一个多核处理器,这个同时性是真正意义的并发,但是一台电脑只有一个单核处理器,这个同时性并不是真正的并发. - 进程级(Process_level)并发:
在windows中,同时可看电视,听歌,这样的是进程级并发 - 线程(Thread)
在一个进程内可以有多个同时进行的任务 - 并行
书上说得有几种,把我搞蒙了,我觉得:并发是同时间执行的线程,并发是几个线程可以一起执行?
1.2.线程的创建和运行
- implements Runnable 接口 或则 extends Thread类,并实现或则重写run方法.
- new Thread(Runnable).start() 可开启线程
1.3.线程信息的获取和设置
Thread类的一些属性:
- ID:线程的唯一标识符
- Name:线程名称
- Priority:线程优先级,从1 到 10,1是最低级,一般不推荐修改线程的优先级
- Status:线程的状态.有: new、runnable、blocked、waiting、time waiting或者terminated
Thread类的属性存储了线程的所有信息,并且线程的id和status不能手动更改。如果你是实现 Runnable接口,那么可以通过 Thread的静态方法currentThread()来获取Thread对象
1.4.线程的中断
一个Java程序,如果不止一个执行线程,当所有的线程都运行结束的时候,这个Java程序才能运行结束: 更准确的来说是 所有的非守护线程运行结束时,或则其中一个线程调用了System.exit()方法时,这个Java程序才运行结束. 如果你想终止一个程序,或则程序的某个用户视图取消线程对象正则运行的任务,就需要结束这个线程.
中断机制: 可以用来结束一个线程. 这种机制要求线程检测它是否被中断了,然后决定是不是相应这个中断请求.线程运行忽略中断请求并且继续执行.
本节将学习如何将一个线程 中断.(对于中断的机制,其实还是有点难的.需要写一下)
* 总结 *
难就难在,中断的代码相关代码,大部分都是原生代码,看到怎么实现的.
- 线程在休眠中,如果被中断 会抛出InterruptedException异常.捕获这个异常,通过跳出循环的方式先 run方法体执行完成.
- 外部设置 中断. 如果线程没有被休眠,可以通过 thread.isInterrupted() 来获取线程的中断状态.
- 还可以使用 Thread的静态方法 interrupted() 来判断是否被中断
- interrupted 和 isInterrupted() :的区别,前者,会清除 中断状态.也就是说,如果外部设置了中断状态,调用interrupted的时候,返回true,同时会把中断状态清除为false
- 在递归循环的复杂调用中.(run方法中调用了多个方法.可以通过 在 多个方法中. 判断是否被中断了.然后手动的 抛出一个中断异常,在run中捕获这个异常,最终让任务结束)
示例
/**
* Created by zhuqiang on 2015/10/12 0012.
*/
public class Client {
public static void main(String[] args){
Thread thread = new Thread(new Task());
thread.start();
//休眠3秒后发起中断请求
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
System.out.println("出错了");
e.printStackTrace();
}
thread.interrupt();
System.out.println("请求中断:");
}
}
class Task implements Runnable{
@Override
public void run() {
boolean flag = true;
Thread thread = Thread.currentThread();
while (flag){
System.out.println(thread.getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("是否被中断1 : " + thread.isInterrupted()); // 这个结果是fals
flag = false; //要注意这里的坑. 在休眠中,如果线程被中断会被抛出
}
if(thread.isInterrupted()){ //是否被中断
System.out.println("是否被中断2 : " + thread.isInterrupted()); // 把上面休眠的代码注释掉,能看到这个结果是 true
flag = false;
}
}
}
}
* 某一次运行结果 *
Thread-0
Thread-0
Thread-0
请求中断:
java.lang.InterruptedException: sleep interrupted
是否被中断1 : false
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at java7Concurrency.sync1_4.Task.run(Client.java:34)
at java.lang.Thread.run(Thread.java:745)
* 结果说明 *
通过中断异常能捕获到 线程被设置为了中断.我猜测可能是. 因为线程在休眠中,然后又被设置了中断请求,然后run中肯定不能被执行到的.所以 休眠方法就抛出了一个中断异常,表示该线程已经被中断了.
1.5.线程中断的控制
线程的中断 中总结了
1.6.线程的休眠和恢复
好吧,在1.4中就已经用到了 休眠,果然是补的章节啊.
- 休眠: 使用TimeUnit.SECONDS.sleep(1)是线程休眠1秒; 能让线程让出cpu执行时钟指令,在这个期间线程是不占用资源的,并且不占用cpu时钟的,所以cpu可以去执行其他的任务.TimeUnit.SECONDS.sleep(1) 也是包装了Thread.sleep(ms, ns)方法.
- 恢复: 等待休眠时间结束后,该线程又拥有了cpu时钟执行权,线程恢复.
- thread的yield()方法,线程让步,让出当前线程的cpu时钟,但是又可能下一次还是让当前线程获得执行权,这个也是 和 休眠的一个区别.
1.7.等待线程的终止
在一些情形下,比如 需要先初始化一些资源,初始化完成之后,再继续执行.这个时候 我们就需要等待线程终止再执行程序的其他任务. 可以使用 thread 的 join()方法. 示例就不写了,在后面的章节中会大量的用到.很简单的调用一个方法而已.
- join() : 等待线程的终止
1.8.守护线程的创建和运行
嗯!这段的描述,让我疑惑了.准备写点demo验证下.
守护线程(daemon) : 这种线程的优先级很低,通常来说一个程序中没有其他线程运行的时候,守护线程才运行.当守护线程是程序中唯一的线程的时候,守护线程结束后,jvm也就结束了.
因为这种特性,守护线程通常用来作为统一程序中普通线程(也称为用户线程)的服务提供者.它通常是无线循环的,以等待服务请求或则执行线程的任务.因此他们不能做重要的工作,因为不知道守护线程什么时候能获取cpu时钟指令,也有可能随时结束.
好把.我的疑问:
优先级低,不知道何时能获取cpu始终指令我能理解,但是说 一个程序中没有其他线程运行的时候,守护线程才运行? 是什么意思
* 总结 *
- 作为守护线程的 线程,要在该线程start前设置.
- 守护线程 会一直在后台运行,如果没有任何用户线程运行的时候,守护线程也会 结束.
- isDaemon() : 获取是否是守护线程
示例
场景描述: 下面的示例 ,就是 用3个线程 来往队列中增加元素. 用一个守护线程来清除元素.
/**
* Created by zhuqiang on 2015/10/12 0012.
*/
public class Client {
public static void main(String[] args) {
LinkedBlockingQueue<Event> clq = new LinkedBlockingQueue<Event>();
// Deque<Event> deque = new ArrayDeque<>();
for (int i = 0; i < 3; i++) {
new AddTask(i + ":name", clq).start();
}
CleanTask cleanTask = new CleanTask(clq);
cleanTask.setDaemon(true); //要在 任务开始前设置守护线程
cleanTask.start();
}
}
class AddTask extends Thread {
private String name; //任务名称
// private Deque<Event> dq; //队列,该线程任务将 每秒往队列中增加一个元素
private LinkedBlockingQueue<Event> clq;
public AddTask(String name, LinkedBlockingQueue<Event> clq) {
this.name = name;
this.clq = clq;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
clq.add(new Event(name + ":" + i, new Date()));
System.out.println(Thread.currentThread().getName() + " : 增加了元素 size:" + clq.size());
try {
TimeUnit.SECONDS.sleep(1);
// TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 清除线程,一直运行
class CleanTask extends Thread {
// private Deque<Event> dq;
private LinkedBlockingQueue<Event> clq;
public CleanTask(LinkedBlockingQueue<Event> clq) {
this.clq = clq;
}
@Override
public void run() {
while (true) {
long time;
Date now = new Date();
Event last = null;
try {
last = clq.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
Date date = last.getDate();
time = now.getTime() - date.getTime();
System.out.println("清除了一个元素:" + JSON.toJSONString(last) + " dq.size=" + clq.size());
// System.out.println("清除线程 运行中 ************" + dq.size());
}
}
}
class Event {
private String name; //事件名称
private Date date; //创建时间
public Event(String name, Date date) {
this.name = name;
this.date = date;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
* 一部分运行结果 *
Thread-0 : 增加了元素 size:1
Thread-1 : 增加了元素 size:2
Thread-2 : 增加了元素 size:3
清除了一个元素:{"date":1444663754124,"name":"0:name:0"} dq.size=2
清除了一个元素:{"date":1444663754125,"name":"1:name:0"} dq.size=1
清除了一个元素:{"date":1444663754125,"name":"2:name:0"} dq.size=0
Thread-0 : 增加了元素 size:2
Thread-1 : 增加了元素 size:3
Thread-2 : 增加了元素 size:2
清除了一个元素:{"date":1444663755126,"name":"2:name:1"} dq.size=2
清除了一个元素:{"date":1444663755126,"name":"0:name:1"} dq.size=1
清除了一个元素:{"date":1444663755126,"name":"1:name:1"} dq.size=0
Thread-1 : 增加了元素 size:2
Thread-2 : 增加了元素 size:3
* 结果说明 *
我最开始按照书上的例子,使用ArrayDeque来作为存储元素的队列,最后发现守护线程获取到的大小一直是0,然后我把守护线程休眠,就能获取到非0的队列. 这个就是一个线程问题了. 所以我觉得书上的列子是错误的结论. 然后使用 线程安全的队列(就是上面的示例),也只证实了.守护线程 会随着没有用户线程的时候跟着消失! 不知道是不是我的机器性能太好的缘故.书上的例子 没有办法重现证书说的是正确的.
1.9.线程中不可空异常的处理
Java中分为两种异常:
- 非运行时异常(Checked Exception):这种异常必须在方法声明throws语句.或则在方法体中捕获
- 运行时异常(Unchecket Exception): 这种感异常 不必抛出,也可以捕获.
在run方法中,不支持抛出异常.所以 如果出现了在运行时异常,你不捕获(一般不仔细看api是不会知道的).默认是在控制台打印. 好在java提供了另一种在线程中捕获运行时异常的一种机制.
- implements Thread.UncaughtExceptionHandler 类
- 在线程start前设置异常处理器 thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler);
- 这种捕获异常的方法.只可能是 运行时异常.原理肯定是 try 了下,然后把线程对象和异常对象回调给处理器的.( 不过短时间没有找到相关的源码)
- 也可以使用静态方法 Thread.setDefaultUncaughtExceptionHandler(new MyExHander()); 设置异常处理器
- jvm调用查找异常处理器的顺序: 线程对象设置的 –> 线程组(ThreadGroup的) –> 最后是上面讲的默认的异常处理器
示例
/**
* Created by zhuqiang on 2015/10/14 0014.
*/
public class Client {
public static void main(String[] args) {
Thread thread = new Thread(new Task());
thread.setUncaughtExceptionHandler(new MyExHander()); //设置自定义的 异常处理器
thread.start();
}
}
//异常处理器
class MyExHander implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t.getName());
System.out.printf("Exception:%s,msg=%s",e.getClass().getName(),e.getMessage());
}
}
class Task implements Runnable{
@Override
public void run() {
int sss = Integer.parseInt("sss"); // 运行时异常
}
}
* 运行结果 *
Thread-0
Exception:java.lang.NumberFormatException,msg=For input string: "sss"
1.10.线程局部变量的使用
在一个task被多个线程运行的时候,那么这个task中的属性就成了共享属性,但是有时候,我们并不想变量被所有线程共享,这个时候 就要用到 线程局部变量(Thread-Local Variable)机制了.
很简单的使用,就不示例了.伪代码,把loc 用ThreadLocal来包装,并初始化. 而不是使用private Date date;
ThreadLocal<Date> loc = new ThreadLocal<Date>(){
@Override
protected Date initialValue() {
return new Date();
}
};
Date date = loc.get(); //获取值
System.out.println("开始时间:" + date);
loc.set(new Date()); //设置值
System.out.println("结束时间:" + loc.get());
loc.remove(); //删除值
InheritableThreadLocal : 也是一种局部变量,不过它是用于给子线程 获取父线程的 局部变量的.假如:A线程创建了B线程.线程B的局部变量和A的局部变量是一样的.也可以覆盖childValue()方法,这个方法用来初始化子线程在线程局部变量中的值.它使用父线程在线程局部变量中的值作为参数传入.
1.11.线程的分组
好吧,这个我觉得是一个很有趣的功能.之前一直都没有学习过.
ThreadGroup(线程组): 表示一组线程,可以包含线程对象,也可以包含线程组对象,它是一个树形结构.
在某些情况下,线程分组比较好控制,比如在多个线程运行中,你要控制他们.只需要一个单一的操作就能达到效果.
好把.看示例和运行结果,原来线程分组就是这样使用的.但是在以下的示例中,其实有弊端的.前面已经讲到过了, 中断:当线程在活动之前或活动期间处于正在等待、休眠或占用状态且该线程被中断时,抛出该异常InterruptedException .这样不在这些状态的时候 就没招了.然而不是还有一种方法么 就是通过检测是否中断
if (Thread.interrupted()) // Clears interrupted status!
throw new InterruptedException();
前面也有讲到,那么在下面的示例中 其实就很难运用到线程分组用组来操作线程了. 其他的我也没有想通.
示例
场景描述: 将创建几个线程,并把他们分到一组.只要有一个线程找到了指定的数字,就让其他几个线程中断.
/**
* Created by zhuqiang on 2015/10/14 0014.
*/
public class Client {
public static void main(String[] args) throws InterruptedException {
ThreadGroup group = new ThreadGroup("xxx");
for (int i = 0; i < 10; i++) {
Thread t = new Thread(group, new Task(2,group));
t.start();
// TimeUnit.MILLISECONDS.sleep(200);
}
int i = group.activeCount();
System.out.println("活动的线程:" + i);
System.out.println("打印线程组信息---- 开始");
group.list();
System.out.println("打印线程组信息---- 结束");
Thread[] list = new Thread[i];
group.enumerate(list); //把活动的线程复制到指定的线程中.
for (Thread t : list) {
System.out.printf("name=%s,state=%s\n", t.getName(),t.getState());;
}
}
}
class Task implements Runnable{
private int num;
private ThreadGroup xx;
public Task(int num, ThreadGroup xx) {
this.num = num;
this.xx = xx;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " : start");
TimeUnit.MILLISECONDS.sleep((long) (Math.random() * 1000));
Random random = new Random();
int i = random.nextInt(3);
if(i== num){
System.out.println(Thread.currentThread().getName() + " : 命中目标");
xx.interrupt(); //找到了就中断所有线程组中的线程
}
System.out.println(Thread.currentThread().getName() + " : end");
}catch (InterruptedException e){
return; //直接返回
}
}
}
* 某一次运行结果 *
活动的线程:10
打印线程组信息---- 开始
java.lang.ThreadGroup[name=xxx,maxpri=10]
Thread[Thread-0,5,xxx]
Thread[Thread-1,5,xxx]
Thread[Thread-2,5,xxx]
Thread[Thread-3,5,xxx]
Thread[Thread-4,5,xxx]
Thread[Thread-5,5,xxx]
Thread[Thread-6,5,xxx]
Thread[Thread-7,5,xxx]
Thread[Thread-8,5,xxx]
Thread[Thread-9,5,xxx]
打印线程组信息---- 结束
name=Thread-0,state=RUNNABLE
Thread-7 : start
Thread-6 : start
Thread-3 : start
Thread-2 : start
Thread-9 : start
Thread-8 : start
Thread-5 : start
Thread-4 : start
Thread-1 : start
Thread-0 : start
name=Thread-1,state=BLOCKED
name=Thread-2,state=RUNNABLE
name=Thread-3,state=RUNNABLE
name=Thread-4,state=RUNNABLE
name=Thread-5,state=RUNNABLE
name=Thread-6,state=RUNNABLE
name=Thread-7,state=RUNNABLE
name=Thread-8,state=RUNNABLE
name=Thread-9,state=TIMED_WAITING
Thread-9 : end
Thread-0 : 命中目标
Thread-0 : end
1.12.线程组中不可空异常的处理
直接上代码,全是干货
- extends ThreadGroup
- 重写 uncaughtException(Thread t, Throwable e) 方法.只要该线程组中有抛出异常的,该方法就会被调用
/**
* Created by zhuqiang on 2015/10/14 0014.
*/
public class Client {
public static void main(String[] args) {
MyGroup my = new MyGroup("my");
for (int i = 0; i < 3; i++) {
new Thread(my,new Task()).start();
}
}
}
//线程组,覆盖uncaughtException方法,捕获 当前组中所有抛出的非捕获异常
class MyGroup extends ThreadGroup{
public MyGroup(String name) {
super(name);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.printf("%s,Exception:%s,msg=%s\n",t.getName(),e.getClass().getName(), e.getMessage());
}
}
class Task implements Runnable{
@Override
public void run() {
Integer.parseInt("ss");
}
}
* 运行结果 *
Thread-1,Exception:java.lang.NumberFormatException,msg=For input string: "ss"
Thread-2,Exception:java.lang.NumberFormatException,msg=For input string: "ss"
Thread-0,Exception:java.lang.NumberFormatException,msg=For input string: "ss"
1.13.使用工厂类创建线程
这个就不讲了.很简单的. 在 7.4.实现ThreadFactory接口生成定制线程 已经使用过了.
/**
* Created by zhuqiang on 2015/10/14 0014.
*/
public class Client {
public static void main(String[] args) {
MyFactrory myFactrory = new MyFactrory();
Thread thread = myFactrory.newThread(new Runnable() { //用我们的工厂来创建 线程
@Override
public void run() {
}
});
}
}
class MyFactrory implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setPriority(2); // 可以设置优先级
thread.setName("xxxxxx" + (long)(Math.random() * 10) ); // 可以设置线程名称等
return thread;
}
}