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 {
        ...
     }
     ...
}
View Code

注意: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

posted @ 2023-07-22 09:33  _Explosion!  阅读(15)  评论(0编辑  收藏  举报