理解并发从例子开始:作为用电脑的小白使用者,并发就是一边记笔记一边听音乐,通过cpu在各个进程间切换实现并发;作为常规程序员,web服务中每个请求对应一个线程,服务端可同时处理多个请求,这也是并发;作为高效程序员,通过并发的方式调用第三方系统从而提高效率;作为一个用户友好程序员,长时间执行的任务可以用一个线程执行,从而不影响用户交互;再比如网红‘事件驱动开发’,每个事件的处理也是并发的。
Java中通过线程实现并发,并采用抢占式资源调度,因此可能出现类似下面的情形:‘平行时空,多个人在多个平行时空吃一桌饭,彼此看不到彼此的存在,我想吃鸡腿,突然就不见了,原来我被suspend,来自另一个平行时空的我吃掉了鸡腿。’,这就是共享变量引发的线程安全问题。更多关于线程安全的问题详见我的文章:https://www.jianshu.com/p/cd41921e5f8e
并发concurrent vs 并行parallel:并发包括单核CPU下时分切换,和多核CPU下同时执行(并行)。其中单CPU下的假并行不能提高效率,相反上下文切换会降低效率。
ConcurrentHashMap也关联到这里:https://www.jianshu.com/p/516a809ce41a
Java对并发的支持:concurrent包
1. Thread类
1)生命周期相关:Thread.start()用于启动线程;Thread.yield()任务提前完成则通过该方法通知调度器出让执行资源。
2)线程属性包括:名称/group/优先级/类型/状态。名称:例如main线程就是main函数执行线程;group默认为pool-1-thread-1;类型:通过Thread.setDaemon(true)设置线程为守护线程,守护线程后台运行,创建的线程都结束后才会结束,守护线程创建的线程都是守护线程;优先级和状态在上面另外两篇文章涉及。
3)Thread.currentThread():在task中调用来查看执行该task的线程。
2. 异常
线程抛出的异常不会传递回主线程,因此task中必须处理异常。
InterruptedException:比如sleep方法声明了InterruptedException。当处于阻塞状态的线程被interrupt时,或者被interrupt的线程试图进入阻塞状态时会抛出这个异常。
可以通过UncaughtExceptionHandler来处理并发异常
3. 可执行任务Task
1)Callable接口提供call()方法;Future接口提供isDone方法来判断是否执行完成;执行完成后调用Future的get方法获取结果。
2)Runnable接口提供run()方法。
4. 通过Executors+ExecutorService来管理线程
Executors相当于工厂,用于创造ExecutorServcie:ExecutorService service = Executors.newCachedThreadPool();
ExecutorService提供接口执行任务:service.execute(new RunnableTask()); Future<T> result = service.submit(new CallableTask()); service.invokeAll/invokeAny用于批量执行;service.shutdown(); shutdown表示停止接收task。
Executors用来创建线程池,弊端在于允许的最大值为Integer.MAX_INTEGER。CachedThreadPool;创建缓存线程池,调用该线程池的execute方法可重用以前的线程;FixedThreadPool固定大小线程池;SingleThreadExecutor串行执行。更加推荐使用ThreadPoolExecutor的构造函数来创建线程池。
网友评论