线程管理
Posted 专注,坚持
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程管理相关的知识,希望对你有一定的参考价值。
因为书中涵盖的知识点比较全,所以就以书中的目录来学习和记录。当然,学习书中知识的时候自己的思考和实践是最重要的。说到线程,脑子里大概知道是个什么东西,但很多东西都还是懵懵懂懂,这是最可怕的。所以想着细致的来学习一下,就从这本实战开始学习。
疑问点:什么时候会用到多线程?什么情况下使用多线程来解决问题比较合适?
线程的创建和运行
就像学习任何知识一样,要学线程,先得学一下线程是怎么声明(创建)和运行起来的。
一般来说,java创建线程有两种常用的方式(线程池后面再谈):
1、继承Thread类,并且覆盖run()方法。
2、实现Runnable接口类,使用带参数的Thread构造器来创建Thread对象,参数就是Runnable接口的一个对象。
那么创建完了怎么运行呢?调用run()方法?调用run()方法只是简单的 类对象调用自己的成员方法,那么怎么会开启线程呢?而且每一个线程还有自己的信息(线程名字,线程ID,优先级等)。那么应该是怎么运行呢?答案是调用start()方法。来个实例更加直观:
- 继承Thread类
1 //创建 2 public class Thread2 extends Thread{ 3 @Override 4 public void run() { 5 System.out.println("继承Thread类创建线程"); 6 } 7 } 8 //运行 9 private static void createThread2() { 10 Thread2 t2 = new Thread2(); 11 Thread thread = new Thread(t2); 12 thread.start(); 13 }
- 实现Runnable接口
1 //创建 2 public class Thread1 implements Runnable { 3 @Override 4 public void run() { 5 System.out.println("实现Runnable接口创建线程"); 6 } 7 } 8 //运行 9 private static void createThread1() { 10 Runnable t1 = new Thread1(); 11 Thread thread = new Thread(t1); 12 thread.start(); 13 }
线程信息的获取和设置
Thread类有一些保存信息的属性,这些属性可以用来标识线程,显示线程状态或者控制线程的优先级。其实这些信息在Thread类中都可以找到,也就是Thread类的一些成员变量。
- ID:保存线程的唯一标识符
- Name:线程名称
- Priority:线程对象的优先级,线程优先级从1-10,1是最低优先级,10是最高优先级。
- Status:线程状态。在Java中,线程的状态有6种:new(创建)、runnbale(运行)、blocked(阻塞)、waiting(等待)、time waiting和terminated(终止)。状态转换图:
这个图和上面的六种状态有点差异,六种状态是在Thread类中的枚举类State中的六种枚举。
1 public enum State { 2 NEW, 3 RUNNABLE, 4 BLOCKED, 5 WAITING, 6 TIMED_WAITING, 7 TERMINATED; 8 }
在记忆的时候可以像上图中所示的那样,线程的状态转换图总是会忘掉,可以类比来记忆。线程的物种状态 创建、就绪、运行、阻塞和终止状态 类比开车的过程。
创建--->把车从车库拿出来
就绪--->坐在驾驶位置上,发动,拉手刹
运行--->车启动,开始走
阻塞--->路口红灯需要停车等待
就绪--->红灯停止,拉手刹开车
终止--->到达目的地,停车
例子举的不是很恰当,其实状态转换主要还是就绪、运行和阻塞三个状态的切换。运行状态运行一段时间后因为某些原因转阻塞状态,阻塞状态解除后 不会立马到运行状态而是到就绪状态,只有就绪状态才有可能获取CPU调度然后重新 变为运行状态。
线程的中断
就是结束正在运行的线程。中断方式有以下几种:
- 调用interrupt()方法,当前执行的线程就会被中断。
1 task.interrupt();
- interrupted(),检查当前执行的线程是否被中断
- isInterrupted():当interrupt()方法被调用时,Thread类中表名线程是否被中断的属性会被设置为true。isInterrupted()方法返回这个属性的值。
1 public static boolean interrupted() { 2 return currentThread().isInterrupted(true); 3 }
线程中断的控制
如果线程实现了复杂的算法那并且分布在几个方法中,或者线程中有递归调用的方法,如何去中断线程?因为直接 调用interrupt()方法不能立竿见影。java还提供了InterruptException异常。当检查到线程中断的时候,就抛出异常,然后在run()方法中捕获并处理这个异常。
run()方法中捕获异常,打印线程信息
1 @Override 2 public void run() { 3 File file = new File(initPath); 4 if (file.isDirectory()) { 5 try { 6 dirctoryProcess(file); 7 } catch (InterruptedException e) { 8 System.out.printf("%s:the search has been interrupted", Thread.currentThread().getName()); 9 } 10 } 11 }
在dirctoryProcess()方法中抛出异常。
1 private void dirctoryProcess(File file) throws InterruptedException { 2 File[] list = file.listFiles(); 3 if (list != null) { 4 for (int i = 0; i < list.length; i++) { 5 if (list[i].isDirectory()) { 6 dirctoryProcess(list[i]); 7 } else { 8 fileProcess(list[i]); 9 } 10 } 11 } 12 if (Thread.interrupted()) { 13 throw new InterruptedException(); 14 } 15 }
线程的休眠和恢复
java里面提供了sleep()方法来休眠线程。需要掌握的知识点:
- 休眠指定时间后线程自动恢复
- 线程休眠的方式有两种:
- Thread.sleep(1000);//休眠一秒,其中这里的单位为ms
- TimeUnit.SECONDS.sleep(1);//休眠一秒,这里的单位为s
等待线程的终止(join())
当一个线程对象的join()方法被调用时,调用它的线程将被挂起,直到这个线程对象完成它的任务。意思就是:如果在线程B中线程A调用了join()方法,那么只有A线程执行完毕后,才会接着执行线程B下面的代码。
DataSourceLoader睡眠3秒
1 public class DataSourceLoader implements Runnable { 2 @Override 3 public void run() { 4 System.out.printf("Begining data sources loading: %s\\n",new Date()); 5 try { 6 TimeUnit.SECONDS.sleep(3); 7 } catch (InterruptedException e) { 8 e.printStackTrace(); 9 } 10 System.out.printf("Data Sources loading has finised:%s\\n",new Date()); 11 } 12 }
NetWorkConnectionLoader睡眠6秒
1 public class NetWorkConnectionLoader implements Runnable { 2 @Override 3 public void run() { 4 System.out.printf("Begining data sources loading: %s\\n",new Date()); 5 try { 6 TimeUnit.SECONDS.sleep(6); 7 } catch (InterruptedException e) { 8 e.printStackTrace(); 9 } 10 System.out.printf("Data Sources loading has finised:%s\\n",new Date()); 11 } 12 }
执行Main
1 public class Main { 2 public static void main(String[] args) { 3 DataSourceLoader dsLoader = new DataSourceLoader(); 4 Thread t1 = new Thread(dsLoader,"DataSourceThread"); 5 NetWorkConnectionLoader ncLoader = new NetWorkConnectionLoader(); 6 Thread t2 = new Thread(ncLoader,"NetWorkConnectionThread"); 7 t1.start(); 8 t2.start(); 9 try { 10 t1.join(); 11 t2.join(); 12 } catch (InterruptedException e) { 13 e.printStackTrace(); 14 } 15 System.out.printf("Main: Configuration has been loaded:%s\\n",new Date()); 16 } 17 }
输出结果如下:
1 Begining data sources loading: Tue Apr 25 14:46:39 CST 2017 2 Begining data sources loading: Tue Apr 25 14:46:39 CST 2017 3 Data Sources loading has finised:Tue Apr 25 14:46:42 CST 2017 4 NetWorkConnectionLoader loading has finised:Tue Apr 25 14:46:45 CST 2017 5 Main: Configuration has been loaded:Tue Apr 25 14:46:45 CST 2017
从结果可以看出:对于main函数线程,如果t1,t2没有调用join函数(),则“
Main: Configuration has been loaded:Tue Apr 25 14:46:45 CST 2017
”这句话应该是先打印的,由于t1,t2调用join函数,结果是main要等待t1,t2执行完成后再执行main线程,即使t1,t2睡眠了一段时间。
java还提供了另外两种形式的join()方法:
- join(long milliseconds)
- join(long milliseconds,long nanos)
- thread2运行已经完成
- 时钟已经过去了1000毫秒
第二种join()方法跟第一种相似,只是需要接受毫秒和纳秒两个参数。
其实join()方法在底层调用的就是join(long milliseconds)方法,只不过传递的值是0,即:
1 public final void join() throws InterruptedException { 2 join(0); 3 }
join()底层实现有一个wait()方法,这样看就比较好记忆了
1 public final synchronized void join(long millis) 2 throws InterruptedException { 3 long base = System.currentTimeMillis(); 4 long now = 0; 5 if (millis < 0) { 6 throw new IllegalArgumentException("timeout value is negative"); 7 } 8 if (millis == 0) { 9 while (isAlive()) { 10 wait(0); 11 } 12 } else { 13 while (isAlive()) { 14 long delay = millis - now; 15 if (delay <= 0) { 16 break; 17 } 18 wait(delay); 19 now = System.currentTimeMillis() - base; 20 } 21 } 22 }
守护线程的创建和运行
守护线程(Daemon):这种线程的优先级很低,通常来说,当一个应用程序中没有其他线程运行的时候,守护线程才运行(这个是守护线程的特性)。当守护线程是程序中唯一运行的线程时,守护线程执行结束后,JVM也就结束了。一个典型的守护线程是Java的垃圾回收器。
举的例子是一个队列中的数据插入和取出:
其中WriteTask负责向队列中插入数据,循环一百次,插入的内容为 时间和 字符串类型的 事件
1 public class WriterTask implements Runnable { 2 Deque<Event> deque; 3 4 public WriterTask (Deque<Event> deque){ 5 this.deque=deque; 6 } 7 8 @Override 9 public void run() { 10 11 // Writes 100 events 12 for (int i=1; i<100; i++) { 13 // Creates and initializes the Event objects 14 Event event=new Event(); 15 event.setDate(new Date()); 16 event.setEvent(String.format("The thread %s has generated an event",Thread.currentThread().getId())); 17 18 // Add to the data structure 19 deque.addFirst(event); 20 try { 21 // Sleeps during one second 22 TimeUnit.SECONDS.sleep(1); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 } 27 } 28 }
记录队列中信息的类 Event
1 public class Event { 2 3 private Date date; 4 private String event; 5 6 public Date getDate() { 7 return date; 8 } 9 10 11 public void setDate(Date date) { 12 this.date = date; 13 } 14 15 16 public String getEvent() { 17 return event; 18 } 19 20 public void setEvent(String event) { 21 this.event = event; 22 } 23 }
取出数据的线程是一个守护线程 ClearTask
1 public class CleanerTask extends Thread { 2 private Deque<Event> deque; 3 4 public CleanerTask(Deque<Event> deque) { 5 this.deque = deque; 6 // Establish that this is a Daemon Thread 7 setDaemon(true); 8 } 9 10 @Override 11 public void run() { 12 while (true) { 13 Date date = new Date(); 14 clean(date); 15 } 16 } 17 18 private void clean(Date date) { 19 long difference; 20 boolean delete; 21 22 if (deque.size()==0) { 23 return; 24 } 25 26 delete=false; 27 do { 28 Event e = deque.getLast(); 29 difference = date.getTime() - e.getDate().getTime(); 30 if (difference > 10000) { 31 System.out.printf("Cleaner: %s\\n",e.getEvent()); 32 deque.removeLast(); 33 delete=true; 34 } 35 } while (difference > 10000); 36 if (delete){ 37 System.out.printf("Cleaner: Size of the queue: %d\\n",deque.size()); 38 } 39 } 40 }
最后是执行的Main:
1 public static void main(String[] args) { 2 3 Deque<Event> deque=new ArrayDeque<Event>(); 4 5 WriterTask writer=new WriterTask(deque); 6 for (int i=0; i<3; i++){ 7 Thread thread=new Thread(writer); 8 thread.start(); 9 } 10 11 CleanerTask cleaner=new CleanerTask(deque); 12 cleaner.start(); 13 }
三个写线程,一个取线程,值得注意的是,只有当三个写线程都休眠的时候,取线程才开始工作。
线程中不可控异常的处理
java中异常Exception下面分两类,
- 受检异常(非运行时异常)(Checked Exception)
- 非受检异常(运行时异常)(UnChecked Exception)
实现用来处理运行时异常的类,这个类实现UnCaughtExceptionHandler接口并且实现这个接口的uncaughtException()方法
1 public class ExceptionHandler implements UncaughtExceptionHandler { 2 @Override 3 public void uncaughtException(Thread t, Throwable e) { 4 System.out.printf("An exception has been captured\\n"); 5 System.out.printf("Thread: %s\\n",t.getId()); 6 System.out.printf("Exception: %s: %s\\n",e.getClass().getName(),e.getMessage()); 7 System.out.printf("Stack Trace: \\n"); 8 e.printStackTrace(System.out); 9 System.out.printf("Thread status: %s\\n",t.getState()); 10 } 11 }
在run()方法里面制造运行时异常:
1 @Override 2 public void run() { 3 // The next instruction always throws and exception 4 int numero=Integer.parseInt("TTT"); 5 }
main函数如下:
1 public static void main(String[] args) { 2 Task task=new Task(); 3 Thread thread=new Thread(task); 4 thread.setUncaughtExceptionHandler(new ExceptionHandler()); 5 thread.start(); 6 try { 7 thread.join(); 8 } catch (InterruptedException e) { 9 e.printStackTrace(); 10 } 11 12 System.out.printf("Thread has finished\\n"); 13 }
这样就捕获了运行时异常,运行结果如下:
1 An exception has been captured 2 Thread: 10 3 Exception: java.lang.NumberFormatException: For input string: "TTT" 4 Stack Trace: 5 java.lang.NumberFormatException: For input string: "TTT" 6 at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) 7 at java.lang.Integer.parseInt(Integer.java:580) 8 at java.lang.Integer.parseInt(Integer.java:615) 9 at com.packtpub.java7.concurrency.chapter1.recipe8.task.Task.run(Task.java:16) 10 at java.lang.Thread.run(Thread.java:745) 11 Thread status: RUNNABLE 12 Thread has finished
如果不捕获异常,那么输出结果如下:
1 Exception in thread "Thread-0" java.lang.NumberFormatException: For input string: "TTT" 2 at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) 3 at java.lang.Integer.parseInt(Integer.java:580) 4 at java.lang.Integer.parseInt(Integer.java:615) 5 at com.packtpub.java7.concurrency.chapter1.recipe8.task.Task.run(Task.java:16) 6 at java.lang.Thread.run(Thread.java:745) 7 Thread has finished
线程局部变量的使用(ThreadLocal)
多线程中,共享数据是不安全的。为了保证共享数据的安全性有两种思路:
- 设置临界区,保证临界区里的数据一次只能有一个线程访问
- 为每个线程维护一个该共享数据的局部变量,这样,每个线程各自使用自己的局部变量。ThreadLocal就是这种思路的实现。
使用ThreadLocal的大致思路是:把共享数据包装在ThreadLocal<T>中。包装完成后ThreadLocal提供了取值和设值的方法。提供的方法有:
- get():返回此线程局部变量的当前线程副本中的值。
- set():将次线程局部变量的当前线程副本中的值设置为指定值。
- remove():移除此线程局部变量当前线程的值。
- initialValue():返回次线程局部变量的当前线程的“初始值”。线程第一次使用get()方法访问变量时将调用此方法,但如果线程之前调用了set(T)方法,则不会对该线程再调用initialValue()方法。通常,此方法对每个线程最多调用一次,但如果在调用get()后又调用了remove(),则可能再次调用此方法。
1 public class SafeTask implements Runnable { 2 3 private static ThreadLocal<Date> startDate= new ThreadLocal<Date>() { 4 protected Date initialValue(){ 5 return new Date(); 6 } 7 }; 8 9 @Override 10 public void run() { 11 // Writes the start date 12 System.out.printf("Starting Thread: %s : %s\\n",Thread.currentThread().getId(),startDate.get()); 13 try { 14 TimeUnit.SECONDS.sleep((int)Math.rint(Math.random()*10)); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 // Writes the start date 19 System.out.printf("Thread Finished: %s : %s\\n",Thread.currentThread().getId(),startDate.get()); 20 } 21 }
线程的分组
Java提供了ThreadGroup类表示一组线程。线程组可以包含线程对象,也可以包含其他的线程组对象,它是一个树形结构。
线程组允许把一个组的线程当做一个单元,对组内线程对象进行访问并操作他们。对一些执行相同任务的线程,只要一个单一的调用,所有这些线程的运行都会被中断。
例子是这样的:创建十个线程并让他们休眠一个随机时间(这段时间比如执行了一个查询),当其中一个线程查找成功的时候,我们将中断其他的9个线程。
疑问点:线程组和线程之间是如何绑定的?
1 ThreadGroup threadGroup = new ThreadGroup("Searcher"); 2 SearchTask searchTask=new SearchTask(result); 3 for (int i=0; i<5; i++) { 4 Thread thread=new Thread(threadGroup, searchTask); 5 thread.start(); 6 try { 7 TimeUnit.SECONDS.sleep(1); 8 } catch (InterruptedException e) { 9 e.printStackTrace(); 10 } 11 }
如上所示,这样就绑定了线程searchTask和线程组threadGroup。线程组提供了一些方法,
activeCount():线程组中活动线程的估计数
interrupt():中断此线程中的所有线程
...其他方法以后用到的时候查看API
等待线程组中有线程满足结束条件
1 private static void waitFinish(ThreadGroup threadGroup) { 2 while (threadGroup.activeCount()>9) { 3 try { 4 TimeUnit.SECONDS.sleep(1); 5 } catch (InterruptedException e) { 6 e.printStackTrace(); 7 } 8 } 9 }
中断线程组中的所有线程
1 threadGroup.interrupt();
线程组中不可控异常的处理
在线程中处理不可控异常,上面的做法是编写一个类继承UncaughtExceptionHandler接口,然后使用thread.setUncaughtExceptionHandler(UncaughtExceptionHandler eh)来实现。因为Thread没有继承UncaughtExceptionHandler接口,而是提供了一个方法:
1 public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) { 2 checkAccess(); 3 uncaughtExceptionHandler = eh; 4 }
对于线程组来说,线程组直接实现了UncaughtExceptionHandler接口,
1 class ThreadGroup implements Thread.UncaughtExceptionHandler {
这样的话,我们就可以声明一个类MyThreadGroup来继承ThreadGroup类,并实现UncaughtExceptionHandler接口唯一的方法:uncaughtException()方法
1 public class MyThreadGroup extends ThreadGroup { 2 public MyThreadGroup(String name) { 3 super(name); 4 } 5 @Override 6 public void以上是关于线程管理的主要内容,如果未能解决你的问题,请参考以下文章
newCacheThreadPool()newFixedThreadPool()newScheduledThreadPool()newSingleThreadExecutor()自定义线程池(代码片段