java并发基础(一)
本文主要讲述线程的创建及相关知识,参考《thinking in java》一书第21章,记录本人在学习过程中的笔记
定义任务
-
线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供,并且实现run方法,例如
public class RunnableItem implements Runnable{ protected int countDown = 10; private static int taskCount = 0; private int taskId = taskCount++; public RunnableItem() { } public RunnableItem(int countDown) { this.countDown = countDown; } @Override public String toString() { return "#" + taskId + "(" + countDown + ")"; } @Override public void run() { while (countDown-- > 0){ System.out.println(this); Thread.yield(); } } }
-
Thread.yield():表明将CPU时间让给该进程的其它线程来执行,该选择完全是自愿的,但这只是一个暗示,并没有机制保证它会被采纳。
执行任务
-
传统方式,将一个任务提交给一个Thread构造器
public static void main(String[] args) { for (int i = 0; i < 5; i++) new Thread(new RunnableItem()).start(); System.out.println("the end?"); }
-
分析结果:具体的运行结果不再说明,需要注意的是在程序一开始就打印了“the end?”,这是因为RunnableItem.run是由不同的线程执行的,此时你仍可以执行main()线程中的其它操作
-
使用Executor(java.util.concurrent)管理Thread对象:
public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++) service.execute(new RunnableItem()); System.out.println("the end?"); service.shutdown(); }
-
shutdown()方法:防止新任务提交给这个Executor,当前线程将继续运行调用shutdown()之前提交的所有任务,该程序将在所有任务完成之后尽快退出
-
各种ExecutorService
名称 描述 newCachedThreadPool 在程序执行过程中会创建与所需线程数量相同的线程,然后在它回收旧线程时停止创建新线程 newFixThreadPool 一次性预先执行代价高昂的线程分配,也可以限制线程的数量 SingleThreadExecutor 相当于线程数量为1的newFixThreadPool -
还可以直接继承Thread,调用start()方法即可运行任务
public class ThreadTest extends Thread{ private static int count = 10; private final int id = count--; @Override public void run() { System.out.println(this); } @Override public String toString() { return Thread.currentThread().getName(); } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 10; i++) new ThreadTest().start(); } }
从任务中产生返回值
-
Runnable是执行工作的独立任务,但是不返回任何值,需要返回值的话可以实现Callable接口
public class CallableTask implements Callable<String> { private int taskId; public CallableTask(int taskId) { this.taskId = taskId; } @Override public String call() throws Exception { return Integer.toString(taskId); } }
-
测试Callable任务:
public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); ArrayList<Future<String>> results = new ArrayList<>(); for (int i = 0; i < 10; i++) results.add(service.submit(new CallableTask(i))); for (Future<String> fs : results) { try { System.out.println(fs.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } service.shutdown(); }
-
submit()方法会产生Future对象,它用Callable返回结果的特定类型进行了参数化,可以用isDone()方法来查询Futrue是否完成。当任务完成时,可以用get()方法获取结果。如果不采用isDone()方法直接使用get(),那么在任务没有结束前,一直会阻塞着。
线程休眠
- Thread.sleep()和TimeUnit的sleep都可以进行线程的睡眠,其中TimeUnit是一个枚举量,可以选择不同的单位,例如
TimeUnit.SECONDS.sleep(1)
,表示睡眠一秒。 - 对sleep调用可以抛出InterruptedException异常,在run()方法中捕获,因为异常不可以跨线程传播到其它线程(例如main()),所以你必须本地处理任务内部产生的异常。
线程的优先级
- 调度器倾向于让优先级高的线程先执行,并不是说优先级低的线程得不到执行,只是执行的频率比较低。
- 调用Thread.currentThread的getPriority()和setPriority()可以修改当前线程的优先级。
后台线程
-
后台线程(daemon)指的是程序运行的时候后台提供一种通用服务的线程,但种线程并不属于程序中不可或缺的一部分。所以所有的非后台线程结束的时候,程序也会结束,同时会杀死所有的后台线程。反过来说,只要有任何的非后台线程还在运行,那么程序就不会终止
-
线程在启动之前调用setDaemon()方法即可设置为后台线程
-
通过编写定制ThreadFactory可以定制由Executor创建的线程的属性(后台、优先级、名称)
public class DaemonThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setDaemon(true); // 设置为后台线程 return thread; } }
-
测试后台线程
public class DaemonFromFactory implements Runnable { @Override public void run() { while (true){ try { TimeUnit.MILLISECONDS.sleep(100); System.out.println(Thread.currentThread() + " " + this); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) throws InterruptedException { ExecutorService service = Executors.newCachedThreadPool(new DaemonThreadFactory()); for (int i = 0; i < 10; i++) service.execute(new DaemonFromFactory()); System.out.println("all daemons started"); TimeUnit.MILLISECONDS.sleep(500); } }
-
一个由后台线程创建的所有线程都将会自动设置为后台线程。
-
后台线程不会运行finally语句,例如一个将会被设置为后台运行的任务的run()方法如下,实践证明不会执行
@Override public void run() { System.out.println("starting ADaemon"); try { TimeUnit.MILLISECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println("run run ??"); } }
-
当最后一个非后台线程终止时,后台线程会突然终止。一旦main()退出,JVM就会关闭所有的后台线程
加入一个线程
-
先看下面代码
class Sleeper extends Thread{ private int duration; public Sleeper(String name, int duration){ super(name); this.duration = duration; start(); } @Override public void run() { try { sleep(duration); } catch (InterruptedException e) { System.out.println(getName() + " is interrupted ? " + isInterrupted()); } System.out.println(getName() + " has awakened"); } } class Joiner extends Thread { private Sleeper sleeper; public Joiner(String name, Sleeper sleeper){ super(name); this.sleeper = sleeper; start(); } @Override public void run() { try { sleeper.join(); // 需要等待sleeper执行完才可以继续执行 } catch (InterruptedException e) { System.out.println("interrupted"); } System.out.println(getName() + " join completed"); } } public class Joining{ public static void main(String[] args) { Sleeper sleepy = new Sleeper("sleepy", 1500); Sleeper grumpy = new Sleeper("grumpy", 1500); Joiner dopey = new Joiner("dopey", sleepy); Joiner doc = new Joiner("doc", grumpy); grumpy.interrupt(); } }
-
一个线程可以在其它线程上调用join()方法,作用是这个线程会被挂起,直到指定的其它线程执行完毕才会继续执行。如果在调用join()时加上一个超时参数,即使目标线程在这段时间到期时还没有结束,join()方法总可以返回
-
一个线程在该线程上调用interrupt()时,将给该线程一个标志,表明该线程已经被中断,但是异常被捕获时将会清理这个标志(运行上面的代码即可观察到)。
捕捉异常
-
运行如下代码,你会发现main中不可以捕获子线程逃逸的异常
public class ExceptionThread implements Runnable { @Override public void run() { throw new RuntimeException("test"); } public static void main(String[] args) { try { ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(new ExceptionThread()); } catch (RuntimeException e) { System.out.println(e.getMessage()); System.out.println("exception has been handled"); } } }
-
要解决上述问题,需要在创建线程池时,传递一个生成线程的工厂,该工厂实现ThreadFactory类,重写newThread方法,此方法内还需要为新生成的线程附着一个自定义的异常处理器,该处理器需要实现Thread.UncaughtExceptionHandler类
class ExceptionThread2 implements Runnable { @Override public void run() { Thread t = Thread.currentThread(); throw new RuntimeException("test"); // 抛出运行时异常,查看是否能够捕获 } } class MyUncaughtExceptionFactory implements Thread.UncaughtExceptionHandler { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("caught " + e); } } class HandlerThreadFactory implements ThreadFactory{ @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setUncaughtExceptionHandler(new MyUncaughtExceptionFactory()); return thread; } } public class CaptureUncaughtException { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(new HandlerThreadFactory()); service.execute(new ExceptionThread2()); service.shutdown(); } }