Java并发编程①

118 阅读6分钟

本节介绍线程创建方式、线程池及其拒绝策略、常见线程池,线程的基本方法和终止线程的方式。

四种常见的线程创建方式

继承Thread

Thread类实现了Runnable接口并定义了操作线程的一些方法,具体实现如下:

public class NewThread extends Thread {
    public void run() {
        System.out.println("create a thread by extends Thread");
    }
}
//实例化
NewThread newThread = new NewThread();
//调用start()方法
newThread.start();

实现Runnable接口

如已继承了一个类则只能通过实现Runnable接口的方式创建线程,具体实现如下:

public class ChildrenClassThread extends SuperClass implements Runnable {
    public void run() {
        System.out.println("create a thread by implements Runnable");
    }
}
//实例化
ChildrenClassThread childrenThread = new ChildrenClassThread();
//创建Thread对象并传入ChildrenClassThread实例
Thread thread = new Thread(childrenThread);
//调用start()方法
thread.start();
/*传入一个实现Runnable的线程实例target给Thread后,Thread的run方法在执行时就
  会调用target.run方法并执行该线程具体实现逻辑,JDK源码中run方法的实现代码如下:
*/
@Override
public void run() {
    if(target != null) {
        target.run();
    }
}

通过ExecutorServiceCallable<Class>实现有返回值的线程

在主线程中开启多个子线程并发执行一个任务,然后收集汇总各子线程返回结果的场景下,需要用到Callable接口和call方法。
具体需要一个线程池、一个用于接收返回结果的Future ListCallable线程实例,使用线程池提交任务并将线程执行之后的结果保存在Future中。

//1.通过Callabe接口创建MyCallable线程
public class MyCallable implements Callable<String> {
    private String name;
    public MyCallable(String name) {
        this.name = name;
    }
    @Override
    public String call() throws Exception {
        return name;
    }
}
//2.创建固定大小为5的线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
//3.创建多个有返回值的任务列表list
List<Future> list = new ArrayList<Future>();
for(int i = 0; i < 5; i++){
    //4.创建一个有返回值的线程实例
    Callable c = new MyCallable(i + " ");
    //5.提交线程,获取Future对象并保存在Future List中
    Future future = pool.submit(c);
    System.out.println("submit a callable thread:" + i);
    list.add(future);
}
//6.关闭线程池
pool.shutdown();
//7.遍历所有线程结果
for (Future future : list) {
    System.out.println("get the result from callable thread:" + f.get().toString());
}

基于线程池

使用缓存策略并使用线程池

ExecutorService threadPool = Executors.newFixedThreadPool(10);
for(int i = 0; i < 10; i++) {
    threadPool.execute(new Runnable() {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "is running");
        }
    });
}

线程池

Java线程池的四个核心组件:线程池管理器、工作线程(具体任务)、任务接口(调用和执行)和任务队列。ThreadPoolExecutor是构建线程的核心方法,该方法的定义如下:

public ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long 
    keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, 
    Executor.defaultThreadFactory(), defaultHandler);

具体的构造函数参数有:线程池核心线程数量、最大线程数量、空闲线程存活时间、keepAliveTime的单位时间、任务队列(被提交但未执行的任务存放的地方)、线程工厂(创建线程)和任务拒绝策略。

拒绝策略

线程池中核心线程数被用完且阻塞队列已满则需要执行拒绝策略处理新添加的线程任务。 JDK内置四种拒绝策略:AbortPolicyCallerRunsPolicyDiscardOldestPolicyDiscardPolicy,也可使用自定义拒绝策略。

AbortPolicy

直接抛出异常,阻止线程正常运行,JDK源码如下:

public static class AbortPolicy implements RejectedExecutionHandler {
    public AbortPolicy() { }
    public void rejectedExecution (Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutioneException("Task " + r.toString() + 
                                                "rejected from " + e.toString());
    }
}

CallerRunsPolicy

不真正丢弃任务

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    public CallerRunsPolicy() { }
    public void rejectedExecution (Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();//执行被丢弃任务r
        }
    }
}

DiscardOldestPolicy

丢弃最早创建的线程

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    public DiscardOldestPolicy() { }
    public void rejectedExecution (Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();//丢弃最早创建的线程
            e.execute(r);//尝试提交当前任务
        }
    }
}

DiscardPolicy

丢弃当前线程不做任何处理

public static class DiscardPolicy implements RejectedExecutionHandler {
    public DiscardPolicy() { }
    public void rejectedExecution (Runnable r, ThreadPoolExecutor e) {
        }
    }
}

自定义拒绝策略

通过实现RejectedExecutionHandler接口,自定义一个DiscardOldestNPolicy策略,具体实现代码如下:

public class DiscardOldestNPolicy implements RejectedExecutionHandler {
    private int discardNumber = 5;
    private List<Runnable> discardList = new ArrayList<Runnable>();
    public DiscardOldestNPolicy (int discardNumber) {
        this.discardNumber = discardNumber;
    }
    public void rejectedExecution (Runnable r, ThreadPoolExecutor e) {
        if(e.getQueue().size() > discardNumber){
            //除移n个线程任务
            e.getQueue().drainTo(discardList,discardNumber);
            //清空discard列表
            discardList.clear();
            //尝试提交当前任务
            if(!e.isShutdown()) {
                e.execute(r);
            }
        }
    }
}

五种常见线程池

newCachedThreadPool

可缓存线程池,重用线程提高系统性能,keepAliveTime时间默认60s,用于执行时间短的大量任务,创建线程时需要申请CPU和内存、记录线程状态、控制阻塞等。

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

newFixedThreadPool

线程资源在队列中循环使用

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);

newScheduledThreadPool

可定时调度的线程池,用于设置延迟时间后执行或定期执行某个线程任务。

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
//延迟三秒执行线程
scheduledThreadPool.schedule(newRunnable() {
    @Override
    public void run() {
        System.out.println("delay 3 seconds execu.");
    }
 }, 3, TimeUnit.SECONDS);
 //延迟一秒执行,每3秒执行一次
 scheduledThreadPool.scheduleAtFixedRate(newRunnable(){
     @Override
     public void run() {
         System.out.println("delay 1 seconds,repeat execute every 3 seconds.");
    }
}, 1, 3, TimeUnti.SECONDS);

newSingleThreadExecutor

永远保证有且只有一个可用的线程。

ExecutorService singleThread = Executors.newSingleThreadExecutor();

newWorkStealingPool

创建有足够线程的线程池达到快速运算阶段,通过多个队列减少线程调度的竞争。足够线程指的是JKD根据线程运行需求向操作系统申请线程

线程

状态:①新建(new)、②就绪(start/调用yield方法、失去CPU资源)
③运行(获得CPU资源/sleep时间到;IO方法返回;获得同步锁;收到通知;线程resume)
④阻塞(sleep方法;IO阻塞;等待同步锁;等待通知;线程suspend)
⑤死亡(stop、Error、Exception、run/call方法执行完毕)。

基本方法

waitnotifynotifyAllsleepjoinyieldinterruptsetDaemon

interrupt方法

仅改变了内部维护的中断标识位。

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
    return isInterrupted(false);
}

中断状态是线程固有的一个标识位,可以通过此标识位安全终止线程。先调用interrupt方法,在run方法中根据线程isInterrupted方法返回状态值安全终止线程:

public class SafeInterruptThread extends Thread {
    @Override
    public void run() {
        if(!Thread.currentThread().isInterrupted()) {
            try{
                //1.这里处理正常线程业务逻辑
                sleep(10);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        if (Thread.currentThread().isInterrupted()) {
            //2.进行处理线程结束前释放资源和清理的工作,如释放锁、数据持久化、异常通知等
            sleep(10);
        }
    }
}
SafeInterruptThread thread = new SafeInterruptThread();
thread.interrupt();

join方法

用于(主线程)等待其他线程(子线程)终止:

System.out.println("子线程开始");
ChildThread childThread = new ChildThread();
childThread.join();
System.out.println("子线程join()结束,开始运行主线程");

setDaemon方法后台守护线程

如GC,不依赖终端,依赖于JVM。

sleepwait方法区别

sleep方法属于Thread类,wait方法属于Object类。
sleep方法指定暂停时间,不释放对象锁;wait方法释放对象锁等待notify方法进入运行状态。

4种线程终止方式

正常运行结束、使用退出标志退出线程、使用Interrupt方法终止线程、使用stop方法终止线程(线程不安全)

使用退出标志退出线程

比如设置boolean类型标志,通过true或者false来控制while循环是否退出:

public class ThreadSafe extends Thread {
    public volatile boolean exit = false;
    public void run() {
        while(!exit)}
            //执行业务逻辑代码
        }
    }
}

volatile关键字保证exit线程同步安全,同一时刻只能有一个线程改变exit值。

使用Interrupt方法终止线程

1.处于阻塞状态,如当调用sleepwaitsocketreceiveraccept方法等线程会处于阻塞状态。要先捕获InterruptedException异常再通过break跳出循环,再正常结束run方法:

public class ThreadSafe extends Thread {
    public void run() {
        while(!isInterrrupted()) {//在非阻塞过程中通过判断中断标志来退出
            try{
                Thread.sleep(5*1000);//在阻塞过程中捕获中断异常来退出
            } catch (InterrruptedException e) {
                e.printStackTrace();
                break;
            }
        }
    }
}

2.未处于阻塞,需要执行线程终止前的资源释放操作,等待资源释放完毕后安全退出该线程。