java多线程

1 线程状态

Java线程的状态在java.lang.Thread类中的State内部枚举类中定义,共有六种状态,分别是NEW(初始化)、 RUNNABLE(可运行)、 BLOCKED(阻塞)、 WAITING(等待)、 TIMED_WAITING(超时等待)、 TERMINATED(终止)。六种状态的转换如下:

1.1 NEW(初始化)

新建一个线程,此时线程尚未启动,也就是没有没有调用Thread.start()方法

1.2 RUNNABLE(可运行)

Java中runnable状态包含ready(就绪)和running(运行中)两种状态,new、block、waiting、time_waiting状态到runnable状态时,线程首先进入可运行线程池中,等待被调度,此时线程处于ready状态。当线程获取到CPU时间片,线程进去running状态,如果时间片用完或调用Thread.yield()方法,线程会让出CPU时间片,此时,线程处于ready状态。如果在running状态,程序执行完成或者发生异常,程序最终会到terminated(终止)状态。

1.3 TERMINATED(终止)

线程已经结束的状态

1.4 BLOCKED(阻塞)

当线程需要一个锁才能执行接下来的操作,而锁又被其他线程占用时,该线程进入阻塞状态。

1.5 WAITING(等待)

当出现以下两种情况

1、线程a执行了Object.wait(),而其他线程并没有Object.notify()或Object.notifyAll()时

2、线程a执行了thread.join(),线程thread没有执行完时

线程a会处于等待状态。需要注意的是此时线程是没有超时时间的。

1.6 TIME_WAITING(超时等待)

与waiting不同的是:time_waiting有超时时间的概念。如果线程等待了一段时间(超时时间)之后会重新获取锁。

2 四种实现方式

2.1 继承Thread类

通过继承Thread类,并重写Thread类的run方法即可创建一个新的线程类

public class ThreadDemo1 extends Thread {
    @Override
    public void run() {
        System.out.println("当前线程是:"+Thread.currentThread().getName());
    }
}

启动线程类是通过调用start()方法进行的

public static void main(String[] args) {
        Thread thread1 = new ThreadDemo1();
        thread1.start();
    }

 

2.2 实现Runnable接口

事实上Thread类也是通过实现Runnable接口实现的。如果一个类已经继承了一个类,那么此时它就无法在继承Thread类了,如果此时我们还想此类是一个线程类,那么最好的方法应该是实现Runnable接口了。代码如下

public class ThreadDemo2 implements Runnable {
    @Override
    public void run() {
        System.out.println("当前线程是:"+Thread.currentThread().getName());
    }
}

读过源码的朋友应该都知道Thread类和Runnble接口以及Runnable接口的实现类是典型的代理模式,Thread类实现了Runnable接口并代理了Runnable接口的其他实现类。所以实现Runnable接口的线程启动方式与2.1稍有不同,需要new一个Thread类在启动,代码如下

    public static void main(String[] args) {
        ThreadDemo2 demo2 = new ThreadDemo2();
        Thread thread2 = new Thread(demo2);
        thread2.start();
    }

 

2.3 实现Callable接口

不管是继承Thread类还是实现Runnable接口,线程都没法返回结果,如果我们想要获得新线程的结果怎么办?通过Callable和FuterTask的组合可以很容易实现这一点。使用方法可以分为以下四步:1)创建Callable实现类,并实现Callable接口;2)new一个Callable的实现,并使用FuterTask包装这个实现;3)将FuterTask的实现作为参数创建Thread,调用Thread的start方法启动线程;4)使用FuterTast的get()方法获取子线程的返回结果。代码如下

//1、创建Callable实现类
public class CallableTask<Object> implements Callable<Object> {
    @Override
    public Object call() {
        System.out.println(Thread.currentThread().getName());
        return (Object) "这是callable返回值";
    }
}
public class ThreadDemo3  {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //2、new一个Callable的实现,并使用FuterTask包装这个实现
        Callable<Object> callable = new CallableTask<>();
        FutureTask<Object> futureTask = new FutureTask<>(callable);
        //3、将FuterTask的实现作为参数创建Thread,调用Thread的start方法启动线程
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println(Thread.currentThread().getName());
        //4、使用FuterTast的get()方法获取子线程的返回结果
        System.out.println(futureTask.get());
    }
}

2.4 使用线程池

由于线程池资源可控、响应快并且便于管理,所以我们在实际工作中多线程用到最多的还是线程池。线程池的使用也非常简单,只需要使用Executors类的相关方法,然后将线程类submit到线程池中,最后使用Future去接收执行的结果就行。实例代码如下

public class ThreadDemo3  {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        Future<String> future = threadPool.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                System.out.println(Thread.currentThread().getName());
                return "我是线程池里返回的字符串";
            }
        });
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName());
        System.out.println(future.get());
    }
}
Executors提供了一些列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService这个接口。最常用的是一下四个方法:
  1. newFixedThreadPool()创建固定数目线程的线程池
  2. newCachedThreadPool()创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
  3. newSingleThreadExecutor()创建一个单线程化Executor
  4. newScheduledThreadPool() 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

3 线程池原理

3.1 线程池参数

2.4提到的四种创建线程池的方法最终都是调用new ThreadPoolExecutor()去创建线程池,ThreadPoolExecutor构造函数共有7个参数,他们分别是:

  1. corePoolSize:核心线程数大小。当提交一个任务到线程池时,线程池会创建一个线程去执行此任务(有空闲线程也创建),当线程池中的线程数目达到corePoolSize时就不再创建线程,而是把此线程放入到缓存队列中。线程池创建时,默认情况下,corePoolSize为0,如果调用了prestartAllCoreThreads()或prestartCoreThread()这两个方法,会预先创建一些线程放入到线程池。
  2. maximumPoolSize:最大线程数。线程池允许创建的最多的线程数量,超过此数量线程池就不能在创建线程,如果此时缓存队列也满了,就会触发拒绝策略。
  3. keepAliveTime:线程没有任务执行时的存活时间。这个参数在线程数大于corePoolSize才有意义。
  4. unit:keepAliveTime的时间单位
  5. workQueue:阻塞队列。用来存储等待执行的任务,常用的有LinkedBlockingQueue、SynchronousQueue,此外还有ArrayBlockingQueue、PriorityBlockingQueue
  6. threadFactory:线程工厂。用来创建线程
  7. handler:拒绝策略。主要有AbortPolicy(直接丢弃并且抛出RejectedExecutionException异常)、DiscardPolicy(丢弃任务但不抛异常)、DiscardOldestPolicy(丢弃最前面的任务,并执行当前任务)、CallerRunsPolicy(调用者所在线程处理任务)

3.2 重要的成员变量

        //低29位标识线程池中线程数量,高3位标识线程池运行状态
        //线程池运行状态分别为:RUNNING(111)、SHUTDOWN(000)、STOP(001)、TIDYING(010)、TERMINATED(011)
        private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

        //线程池最大数量,2^29 - 1
        private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

        //任务缓存队列,用来存放等待执行的任务
        private final BlockingQueue<Runnable> workQueue;

        //可重入锁,线程池状态改变时使用
        private final ReentrantLock mainLock = new ReentrantLock();

        //存放工作线程
        private final HashSet<Worker> workers = new HashSet<Worker>();

 

3.3 execute(Runnable command)源码分析

在看execute()源码之前,我们先看一下submit()的源码,源码比较简单,直接附上

public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

从这段源码可以看出,submit最终也是调execute()方法执行的。

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        //线程池中线程数量小于核心线程数,将任务加入workers集合,如果加入成功直接返回
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        //如果线程池处于RUNNING状态,且任务能加入阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
            //重新获取线程池的状态,如果线程池处于非RUNNING状态,那么将任务从阻塞队列中移除,并拒绝任务
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //如果线程池没有任务,创建新线程去执行任务
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //如果上面都没有成功,尝试开启新线程去执行任务,如果开启失败,拒绝任务
        else if (!addWorker(command, false))
            reject(command);
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值