java多线程
java线程默认等待方式为detach,可手动join同步,但是不需要和c++一样必须去声明等待方法。
多线程
java中多线程的实现其实和c++类似,介绍几种常用方法
1.继承Thread类,重写其run方法
class MyThread extends Thread{ public MyThread() { } public MyThread(String name) { super(name); } @Override public void run(){ System.out.println(getName()); } } public class threadTest { public static void main(String[] Args){ Thread test1 = new MyThread("test1"); Thread test2 = new MyThread("test2"); Thread test3 = new MyThread("test3"); test1.start(); test2.start(); test3.start(); } }
2.实现Runnable接口,实现其run方法
class MyRunnable implements Runnable{ @Override public void run(){ System.out.println("running"); } } public class threadTest { public static void main(String[] Args){ Thread test1 = new Thread(new MyRunnable()); Thread test2 = new Thread(new MyRunnable()); Thread test3 = new Thread(new MyRunnable()); test1.start(); test2.start(); test3.start(); } }
3.实现Callable接口(类似于c++中的async任务实现,可获得返回值),重写call方法
FutureTask继承了Future接口并且支持Callable接口注入,从而实现任务式异步。
public class threadTest { public static void main(String[] Args){ //此处用匿名内部类实现Callable接口 FutureTask<Object> task = new FutureTask(new Callable<String>() { @Override public String call() throws Exception{ return new String("mycall"); } }); Thread th = new Thread(task); th.start(); try{ //调用get使主线程阻塞至任务结束,获得异步结果 Object res = task.get(); System.out.println(res); }catch(Exception e){ e.printStackTrace(); } } }
4.java8异步解决方案CompletableFuture
相比于前者Future,Future只能通过.get阻塞获取异步结果或通过.isDone轮询是否完成;CompletableFuture 实现了 Future
接口和 CompletionStage,比Future更灵活强大。
首先介绍CompletableFuture的几种常用方法
构建异步任务:
- runAsync(Runnable runnable, Executor executor) 参数二可不填,默认线程池ForkJoinPool.commonPool()
- 进行数据处理,接收前一步骤传递的数据,无返回值。
- supplyAsync(Supplier<U> supplier, Executor executor) 参数二可不填,默认线程池ForkJoinPool.commonPool()
- 进行数据处理,接收前一步骤传递的数据,处理加工后返回。返回数据类型可以和前一步骤返回的数据类型不同。
public class CompletableFutureTest { public static void main(String[] Args) throws IOException, ExecutionException, InterruptedException { //自定义线程池 ExecutorService pool = Executors.newCachedThreadPool(); CompletableFuture cf1 = CompletableFuture.supplyAsync(()->{ System.out.println(Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } return new String("cf1666"); },pool); CompletableFuture cf2 = CompletableFuture.runAsync(()->{ System.out.println(Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } }); //阻塞等待结果 System.out.println("main:"+cf1.get()); System.in.read(); } }
回调处理方法:
- thenApply(Function<? super T,? extends U> fn)
- 某个任务执行完成后执行的动作,将上一阶段完成的结果作为当前阶段的入参,支持链式调用
- thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) 参数二可不填,默认线程池ForkJoinPool.commonPool()
- 某个任务执行完成后执行的动作,将上一阶段完成的结果作为当前阶段的入参;与前者的区别是,前者使用的是上一阶段的线程,而thenApplyAsync会另启线程
- thenAccept(Supplier<U> supplier)
- 某个任务执行完成后执行的动作,将上一阶段完成的结果作为当前阶段的入参,无返回值
- thenAcceptAsync(Supplier<U> supplier, Executor executor) 参数二可不填,默认线程池ForkJoinPool.commonPool()
- 某个任务执行完成后执行的动作,将上一阶段完成的结果作为当前阶段的入参,无返回值,会另启线程
- thenRun(Runnable runnable)
- 某个任务执行完成后执行的动作,无入参,无返回值
- thenRunAsync(Runnable runnable Executor executor)
- 某个任务执行完成后执行的动作,无入参,无返回值,会另启线程
- whenComplete和whenCompleteAsync
- 参数:CompletableFuture的result和抛出的异常exception,若正常执行则异常为null
- 无返回值,线程的使用与前面几者一致
- handle和handleAsync
- 和前者一致,不过有返回值
多任务组合:
- anyOf allOf
- 参数:一个或多个CompletableFuture
此处简单介绍一下interrupt方法
当线程调用sleep,wait,await,join方法时会陷入阻塞状态,在该状态下,线程若被父线程调用该线程的interrupt方法,则会被打断并抛出InterruptedException异常。
若线程正在执行,而其父线程调用了该线程的interrupt方法,其打断标记(可用Thread.currentThread().isInterrupted()获得打断标记)会被置为true,而不会被强行终止,可以自己决定何时终止。
thread有两种启动方法,一种是.run(),一种是.start(),run方法是同步启动,即run的对象在当前线程(主线程)执行,相当于直接执行线程的任务;start()方法是异步启动,是新开线程执行。
线程池
Java中提供了ExecutorService线程池,创建其方法为ThreadPoolExecutor,具体结构如下:
public ThreadPoolExecutor(int corePoolSize, //核心线程数,创建后不会释放 int maximumPoolSize, //最大线程数(该值需大于等于核心线程数) long keepAliveTime, //空闲线程最大存活时间 TimeUnit unit, //时间单位TimeUnit BlockingQueue<Runnable> workQueue, //任务队列 ThreadFactory threadFactory, //线程工厂,用于创建线程 RejectedExecutionHandler handler) //处理阻塞
ExecutorService有如下几个常用方法:
- execute(Runnable)
- submit(Runnable)
- submit(Callable)
- invokeAny(...)
- invokeAll(...)
execute接收一个Runnable对象并将其放进任务队列异步执行
submit接收一个Runnable或Callable对象并返回一个Future期约,从而可以检测任务是否完成并获得任务返回值。由上可知,此处也可将FutrueTask对象给予submit
invokeAny和invokeAll接收一个Callable集合,但前者返回集合中随机一个任务的Future,后者返回Future对列,按顺序对应任务集合
ExecutorService有两种关闭方式
第一种是shutdown()方法,调用后ExecutorService不再接收任务并等待当前线程池中所有任务结束后关闭。
第二种是shutdownNow()方法,调用后ExecutorService立即关闭。
Executors中提供了几种常用线程池:
1.可缓存线程池
创建的线程均为非核心线程,空闲线程存活时间为一分钟。适合生命周期短的任务
//Executors.newCachedThreadPool(); public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
2.单线程池
只有一个核心线程,使得任务队列满足FIFO
//Executors.newSingleThreadExecutor(); public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
3.固定线程数线程池
固定数量核心线程
//Executors.newFixedThreadPool(n); public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
4.固定线程数,定时周期线程池
可用于替代handler.postDelay和Timer定时器等延时和周期性任务。
//Executors.newScheduledThreadPool(n); public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS, new DelayedWorkQueue()); }
同样也可手动创建线程池
private ExecutorService pool = new ThreadPoolExecutor(3, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(512), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
ThreadLocal
threadLocal即线程局部变量。作用是实现线程本地存储功能,通过线程本地资源隔离,解决多线程并发场景下线程安全问题。即每个线程的threadLocal变量互不关联。
ThreadLocalMap:
ThreadLocalMap是ThreadLocal的一个静态内部类。每一个Thread对象实例中都维护了ThreadLocalMap对象,对象本质存储了一组以ThreadLocal为key(this对象实际使用的是唯一threadLocalHashCode值),以本地线程包含变量为value的K-V键值对。
作者:苏三说技术 链接:https://www.zhihu.com/question/21709953/answer/2488516865 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 public class ThreadLocal<T> { ... public T get() { //获取当前线程 Thread t = Thread.currentThread(); //获取当前线程的成员变量ThreadLocalMap对象 ThreadLocalMap map = getMap(t); if (map != null) { //根据threadLocal对象从map中获取Entry对象 ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") //获取保存的数据 T result = (T)e.value; return result; } } //初始化数据 return setInitialValue(); } private T setInitialValue() { //获取要初始化的数据 T value = initialValue(); //获取当前线程 Thread t = Thread.currentThread(); //获取当前线程的成员变量ThreadLocalMap对象 ThreadLocalMap map = getMap(t); //如果map不为空 if (map != null) //将初始值设置到map中,key是this,即threadLocal对象,value是初始值 map.set(this, value); else //如果map为空,则需要创建新的map对象 createMap(t, value); return value; } public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //获取当前线程的成员变量ThreadLocalMap对象 ThreadLocalMap map = getMap(t); //如果map不为空 if (map != null) //将值设置到map中,key是this,即threadLocal对象,value是传入的value值 map.set(this, value); else //如果map为空,则需要创建新的map对象 createMap(t, value); } static class ThreadLocalMap { ... } ... }
注意:ThreadLocalMap是Thread类的成员变量。
在ThreadLocalMap内部还维护了一个Entry静态内部类,该类继承了WeakReference,并指定其所引用的泛型类为ThreadLocal类型。Entry是一个键值对结构,使用ThreadLocal类型对象作为引用的key。
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
ThreadLocalMap作为ThreadLocal的静态内部类,需要维护多个ThreadLocal对象所存储的value值。
ThreadLocal调用其get、set或remove,三个方法中的任何一个方法,都会自动触发清理机制,将线程的ThreadLocalMap的Entry的key为null的value值清空。如果key和value都是null,那么Entry对象会被GC回收。如果所有的Entry对象都被回收了,ThreadLocalMap也会被回收了。
Entry之所以被定义为弱引用,是为了防止内存泄漏。比如当外部的ThreadLocal生命周期结束或被设置为null,那么在下一次gc时Entry对应的key就会被回收变为null;如果Entry被设置为强引用,那么就会导致该ThreadLocal对象的生命周期和Thread生命周期一致,若线程存在于线程池中,那么该块内存将会长期存在,导致内存溢出。
一句话理解ThreadLocal,threadlocl是作为当前线程中属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadlocl,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量地址是一样的。
参考文章:https://blog.csdn.net/fwt336/article/details/81530581
https://www.zhihu.com/question/21709953/answer/2488516865