到目前为止,你学到的都是顺序编程,顺序编程的概念就是某一时刻只有一个任务在执行,顺序编程当然可以解决不少问题,可是对于某种任务,若是可以并发的执行程序中重要的部分就显得尤其重要,同时也能够极大提升程序运行效率,享受并发为你带来的便利。可是,熟练掌握并发编程理论和技术,对于只会CRUD的你来讲是一种和你刚学面向对象同样的一种飞跃。html
正如你所看到的,当并行的任务彼此干涉时,实际的并发问题就会接踵而至。并且并发问题不是很难复现,在你实际的测试过程当中每每会忽略它们,由于故障是偶尔发生的,这也是咱们研究它们的必要条件:若是你对并发问题置之不理,那么你最终会承受它给你带来的损害。java
速度问题听起来很简单,若是你想让一个程序运行的更快一些,那么能够将其切成多个分片,在单独的处理器上运行各自的分片:前提是这些任务彼此之间没有联系。编程
注意:速度的提升是以多核处理器而不是芯片的形式出现的。多线程
若是你有一台多处理器的机器,那么你就能够在这些处理器之间分布多个任务,从而极大的提升吞吐量。**可是,并发一般是提升在单处理器上的程序的性能。**在单处理上的性能开销要比多处理器上的性能开销大不少,由于这其中增长了线程切换
(从一个线程切换到另一个线程)的重要依据。表面上看,将程序的全部部分看成单个的任务运行好像是开销更小一点,节省了线程切换的时间。并发
在单CPU机器上使用多任务的程序在任意时刻仍旧只在执行一项工做,你肉眼观察到控制台的输出好像是这些线程在同时工做,这不过是CPU的障眼法罢了,CPU为每一个任务都提供了不固定的时间切片。Java 的线程机制是抢占式的,也就是说,你必须编写某种让步语句才会让线程进行切换,切换给其余线程。异步
并发编程使咱们能够将程序划分红多个分离的,独立运行的任务。经过使用多线程机制,这些独立任务中的每一项任务都由执行线程
来驱动。一个线程就是进程中的一个单一的顺序控制流。所以,单个进程能够拥有多个并发执行的任务,可是你的程序看起来每一个任务都有本身的CPU同样。其底层是切分CPU时间,一般你不须要考虑它。ide
线程能够驱动任务,所以你须要一种描述任务的方式,这能够由 Runnable
接口来提供,要想定义任务,只须要实现 Runnable 接口,并在run
方法中实现你的逻辑便可。性能
public class TestThread implements Runnable{ public static int i = 0; @Override public void run() { System.out.println("start thread..." + i); i++; System.out.println("end thread ..." + i); } public static void main(String[] args) { for(int i = 0;i < 5;i++){ TestThread testThread = new TestThread(); testThread.run(); } } }
任务 run 方法会有某种形式的循环,使得任务一直运行下去直到再也不须要,因此要设定 run 方法的跳出条件(有一种选择是从 run 中直接返回,下面会说到。)测试
在 run 中使用静态方法 Thread.yield()
可使用线程调度,它的意思是建议线程机制进行切换:你已经执行完重要的部分了,剩下的交给其余线程跑一跑吧。注意是建议执行,而不是强制执行。在下面添加 Thread.yield() 你会看到有意思的输出this
public void run() { System.out.println("start thread..." + i); i++; Thread.yield(); System.out.println("end thread ..." + i); }
将 Runnable 转变工做方式的传统方式是使用 Thread 类托管他,下面展现了使用 Thread 类来实现一个线程。
public static void main(String[] args) { for(int i = 0;i < 5;i++){ Thread t = new Thread(new TestThread()); t.start(); } System.out.println("Waiting thread ..."); }
Thread 构造器只须要一个 Runnable 对象,调用 Thread 对象的 start() 方法为该线程执行必须的初始化操做,而后调用 Runnable 的 run 方法,以便在这个线程中启动任务。能够看到,在 run 方法尚未结束前,run 就被返回了。也就是说,程序不会等到 run 方法执行完毕就会执行下面的指令。
在 run 方法中打印出每一个线程的名字,就更能看到不一样的线程的切换和调度
@Override public void run() { System.out.println(Thread.currentThread() + "start thread..." + i); i++; System.out.println(Thread.currentThread() + "end thread ..." + i); }
这种线程切换和调度是交由 线程调度器
来自动控制的,若是你的机器上有多个处理器,线程调度器会在这些处理器之间默默的分发线程。每一次的运行结果都不尽相同,由于线程调度机制是未肯定的。
JDK1.5 的java.util.concurrent 包中的执行器 Executor 将为你管理 Thread 对象,从而简化了并发编程。Executor 在客户端和任务之间提供了一个间接层;与客户端直接执行任务不一样,这个中介对象将执行任务。Executor 容许你管理异步任务的执行,而无须显示地管理线程的生命周期。
public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); for(int i = 0;i < 5;i++){ service.execute(new TestThread()); } service.shutdown(); }
咱们使用 Executor 来替代上述显示建立 Thread 对象。CachedThreadPool
为每一个任务都建立一个线程。注意:ExecutorService 对象是使用静态的 Executors
建立的,这个方法能够肯定 Executor 类型。对 shutDown
的调用能够防止新任务提交给 ExecutorService ,这个线程在 Executor 中全部任务完成后退出。
FixedThreadPool 使你可使用有限的线程集来启动多线程
public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(5); for(int i = 0;i < 5;i++){ service.execute(new TestThread()); } service.shutdown(); }
有了 FixedThreadPool 使你能够一次性的预先执行高昂的线程分配,所以也就能够限制线程的数量。这能够节省时间,由于你没必要为每一个任务都固定的付出建立线程的开销。
SingleThreadExecutor 就是线程数量为 1 的 FixedThreadPool,若是向 SingleThreadPool 一次性提交了多个任务,那么这些任务将会排队,每一个任务都会在下一个任务开始前结束,全部的任务都将使用相同的线程。SingleThreadPool 会序列化全部提交给他的任务,并会维护它本身(隐藏)的悬挂队列。
public static void main(String[] args) { ExecutorService service = Executors.newSingleThreadExecutor(); for(int i = 0;i < 5;i++){ service.execute(new TestThread()); } service.shutdown(); }
从输出的结果就能够看到,任务都是挨着执行的。我为任务分配了五个线程,可是这五个线程不像是咱们以前看到的有换进换出的效果,它每次都会先执行完本身的那个线程,而后余下的线程继续“走完”这条线程的执行路径。你能够用 SingleThreadExecutor 来确保任意时刻都只有惟一一个任务在运行。
Runnable 是执行工做的独立任务,但它不返回任何值。若是你但愿任务在完成时可以返回一个值 ,这个时候你就须要考虑使用 Callable
接口,它是 JDK1.5 以后引入的,经过调用它的 submit
方法,能够把它的返回值放在一个 Future 对象中,而后根据相应的 get() 方法取得提交以后的返回值。
public class TaskWithResult implements Callable<String> { private int id; public TaskWithResult(int id){ this.id = id; } @Override public String call() throws Exception { return "result of TaskWithResult " + id; } } public class CallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executors = Executors.newCachedThreadPool(); ArrayList<Future<String>> future = new ArrayList<>(); for(int i = 0;i < 10;i++){ // 返回的是调用 call 方法的结果 future.add(executors.submit(new TaskWithResult(i))); } for(Future<String> fs : future){ System.out.println(fs.get()); } } }
submit() 方法会返回 Future 对象,Future 对象存储的也就是你返回的结果。你也可使用 isDone
来查询 Future 是否已经完成。
影响任务行为的一种简单方式就是使线程 休眠,选定给定的休眠时间,调用它的 sleep()
方法, 通常使用的TimeUnit
这个时间类替换 Thread.sleep()
方法,示例以下:
public class SuperclassThread extends TestThread{ @Override public void run() { System.out.println(Thread.currentThread() + "starting ..." ); try { for(int i = 0;i < 5;i++){ if(i == 3){ System.out.println(Thread.currentThread() + "sleeping ..."); TimeUnit.MILLISECONDS.sleep(1000); } } } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread() + "wakeup and end ..."); } public static void main(String[] args) { ExecutorService executors = Executors.newCachedThreadPool(); for(int i = 0;i < 5;i++){ executors.execute(new SuperclassThread()); } executors.shutdown(); } }
关于 TimeUnit 中的 sleep() 方法和 Thread.sleep() 方法的比较,请参考下面这篇博客
上面提到线程调度器对每一个线程的执行都是不可预知的,随机执行的,那么有没有办法告诉线程调度器哪一个任务想要优先被执行呢?你能够经过设置线程的优先级状态,告诉线程调度器哪一个线程的执行优先级比较高,"请给这个骑手立刻派单",线程调度器倾向于让优先级较高的线程优先执行,然而,这并不意味着优先级低的线程得不到执行,也就是说,优先级不会致使死锁的问题。优先级较低的线程只是执行频率较低。
public class SimplePriorities implements Runnable{ private int priority; public SimplePriorities(int priority) { this.priority = priority; } @Override public void run() { Thread.currentThread().setPriority(priority); for(int i = 0;i < 100;i++){ System.out.println(this); if(i % 10 == 0){ Thread.yield(); } } } @Override public String toString() { return Thread.currentThread() + " " + priority; } public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); for(int i = 0;i < 5;i++){ service.execute(new SimplePriorities(Thread.MAX_PRIORITY)); } service.execute(new SimplePriorities(Thread.MIN_PRIORITY)); } }
toString() 方法被覆盖,以便经过使用 Thread.toString()
方法来打印线程的名称。你能够改写线程的默认输出,这里采用了 Thread[pool-1-thread-1,10,main] 这种形式的输出。
经过输出,你能够看到,最后一个线程的优先级最低,其他的线程优先级最高。注意,优先级是在 run 开头设置的,在构造器中设置它们不会有任何好处,由于这个时候线程尚未执行任务。
尽管JDK有10个优先级,可是通常只有MAX_PRIORITY,NORM_PRIORITY,MIN_PRIORITY 三种级别。
咱们上面提过,若是知道一个线程已经在 run() 方法中运行的差很少了,那么它就能够给线程调度器一个提示:我已经完成了任务中最重要的部分,可让给别的线程使用CPU了。这个暗示将经过 yield() 方法做出。
有一个很重要的点就是,Thread.yield() 是建议执行切换CPU,而不是强制执行CPU切换。
对于任何重要的控制或者在调用应用时,都不能依赖于 yield()
方法,实际上, yield() 方法常常被滥用。
后台(daemon) 线程,是指运行时在后台提供的一种服务线程,这种线程不是属于必须的。当全部非后台线程结束时,程序也就中止了,**同时会终止全部的后台线程。**反过来讲,只要有任何非后台线程还在运行,程序就不会终止。
public class SimpleDaemons implements Runnable{ @Override public void run() { while (true){ try { TimeUnit.MILLISECONDS.sleep(100); System.out.println(Thread.currentThread() + " " + this); } catch (InterruptedException e) { System.out.println("sleep() interrupted"); } } } public static void main(String[] args) throws InterruptedException { for(int i = 0;i < 10;i++){ Thread daemon = new Thread(new SimpleDaemons()); daemon.setDaemon(true); daemon.start(); } System.out.println("All Daemons started"); TimeUnit.MILLISECONDS.sleep(175); } }
在每次的循环中会建立10个线程,并把每一个线程设置为后台线程,而后开始运行,for循环会进行十次,而后输出信息,随后主线程睡眠一段时间后中止运行。在每次run 循环中,都会打印当前线程的信息,主线程运行完毕,程序就执行完毕了。由于 daemon
是后台线程,没法影响主线程的执行。
可是当你把 daemon.setDaemon(true)
去掉时,while(true) 会进行无限循环,那么主线程一直在执行最重要的任务,因此会一直循环下去没法中止。
按须要建立线程的对象。使用线程工厂替换了 Thread 或者 Runnable 接口的硬链接,使程序可以使用特殊的线程子类,优先级等。通常的建立方式为
class SimpleThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { return new Thread(r); } }
Executors.defaultThreadFactory 方法提供了一个更有用的简单实现,它在返回以前将建立的线程上下文设置为已知值
ThreadFactory 是一个接口,它只有一个方法就是建立线程的方法
public interface ThreadFactory { // 构建一个新的线程。实现类可能初始化优先级,名称,后台线程状态和 线程组等 Thread newThread(Runnable r); }
下面来看一个 ThreadFactory 的例子
public class DaemonThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { Thread t = new Thread(r); t.setDaemon(true); return t; } } 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) { System.out.println("Interrupted"); } } } 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); } }
Executors.newCachedThreadPool
能够接受一个线程池对象,建立一个根据须要建立新线程的线程池,但会在它们可用时重用先前构造的线程,并在须要时使用提供的ThreadFactory建立新线程。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory); }
一个线程能够在其余线程上调用 join()
方法,其效果是等待一段时间直到第二个线程结束才正常执行。若是某个线程在另外一个线程 t 上调用 t.join() 方法,此线程将被挂起,直到目标线程 t 结束才回复(能够用 t.isAlive() 返回为真假判断)。
也能够在调用 join 时带上一个超时参数,来设置到期时间,时间到期,join方法自动返回。
对 join 的调用也能够被中断,作法是在线程上调用 interrupted
方法,这时须要用到 try...catch 子句
public class TestJoinMethod extends Thread{ @Override public void run() { for(int i = 0;i < 5;i++){ try { TimeUnit.MILLISECONDS.sleep(1000); } catch (InterruptedException e) { System.out.println("Interrupted sleep"); } System.out.println(Thread.currentThread() + " " + i); } } public static void main(String[] args) throws InterruptedException { TestJoinMethod join1 = new TestJoinMethod(); TestJoinMethod join2 = new TestJoinMethod(); TestJoinMethod join3 = new TestJoinMethod(); join1.start(); // join1.join(); join2.start(); join3.start(); } }
join() 方法等待线程死亡。 换句话说,它会致使当前运行的线程中止执行,直到它加入的线程完成其任务。
因为线程的本质,使你不能捕获从线程中逃逸的异常,一旦异常逃出任务的run 方法,它就会向外传播到控制台,除非你采起特殊的步骤捕获这种错误的异常,在 Java5 以前,你能够经过线程组来捕获,可是在 Java5 以后,就须要用 Executor 来解决问题,由于线程组不是一次好的尝试。
下面的任务会在 run 方法的执行期间抛出一个异常,而且这个异常会抛到 run 方法的外面,并且 main 方法没法对它进行捕获
public class ExceptionThread implements Runnable{ @Override public void run() { throw new RuntimeException(); } public static void main(String[] args) { try { ExecutorService service = Executors.newCachedThreadPool(); service.execute(new ExceptionThread()); }catch (Exception e){ System.out.println("eeeee"); } } }
为了解决这个问题,咱们须要修改 Executor 产生线程的方式,Java5 提供了一个新的接口 Thread.UncaughtExceptionHandler
,它容许你在每一个 Thread 上都附着一个异常处理器。Thread.UncaughtExceptionHandler.uncaughtException()
会在线程因未捕获临近死亡时被调用。
public class ExceptionThread2 implements Runnable{ @Override public void run() { Thread t = Thread.currentThread(); System.out.println("run() by " + t); System.out.println("eh = " + t.getUncaughtExceptionHandler()); // 手动抛出异常 throw new RuntimeException(); } } // 实现Thread.UncaughtExceptionHandler 接口,建立异常处理器 public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{ @Override public void uncaughtException(Thread t, Throwable e) { System.out.println("caught " + e); } } public class HandlerThreadFactory implements ThreadFactory { @Override public Thread newThread(Runnable r) { System.out.println(this + " creating new Thread"); Thread t = new Thread(r); System.out.println("created " + t); t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); System.out.println("ex = " + t.getUncaughtExceptionHandler()); return t; } } public class CaptureUncaughtException { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(new HandlerThreadFactory()); service.execute(new ExceptionThread2()); } }
在程序中添加了额外的追踪机制,用来验证工厂建立的线程会传递给UncaughtExceptionHandler
,你能够看到,未捕获的异常是经过 uncaughtException
来捕获的。
文章来源:
《Java编程思想》
https://www.javatpoint.com/join()-method
下面为本身作个宣传,欢迎关注公众号 Java建设者
,号主是Java技术栈,热爱技术,喜欢阅读,热衷于分享和总结,但愿能把每一篇好文章分享给成长道路上的你。 关注公众号回复 002
领取为你特地准备的大礼包,你必定会喜欢并收藏的。
本文由博客一文多发平台 OpenWrite 发布!