1.线程的四种创建方式

1.继承Thread类

  1. public class MyThread extends Thread{//继承Thread类
  2.   public void run(){
  3.   //重写run方法
  4.   }
  5. }
  6. public class Main {
  7.   public static void main(String[] args){
  8.     new MyThread().start();//创建并启动线程
  9.   }
  10. }

2.实现Runnable接口

  1. public class MyThread2 implements Runnable {//实现Runnable接口
  2.   public void run(){
  3.   //重写run方法
  4.   }
  5. }
  6. public class Main {
  7.   public static void main(String[] args){
  8.     //创建并启动线程
  9.     MyThread2 myThread=new MyThread2();
  10.     Thread thread=new Thread(myThread);
  11.     thread().start();
  12.     //或者 new Thread(new MyThread2()).start();
  13.   }
  14. }

3.使用Callable和Future

和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大。

》call()方法可以有返回值

》call()方法可以声明抛出异常

Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务

V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值

V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException

boolean isDone():若Callable任务完成,返回True

boolean isCancelled():如果在Callable任务正常完成前被取消,返回True

介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:

1】创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。

2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值

3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)

4】调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

  1. public static class MyThread3 implements Callable{
  2. @Override
  3. public Object call() throws Exception {
  4. return 5;
  5. }
  6. }
  7. public class Main {
  8.   public static void main(String[] args){
  9.    MyThread3 th=new MyThread3();
  10.    //也可以直接使用Lambda表达式创建Callable对象
  11.    //使用FutureTask类来包装Callable对象
  12.    FutureTask<Integer> future=new FutureTask<Integer>(
  13.     (Callable<Integer>)()->{
  14.       return 5;
  15.     }
  16.    );
  17.    new Thread(future,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程
  18.    try{
  19.     System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回
  20.    }catch(Exception e){
  21.     ex.printStackTrace();
  22.    }
  23.   }
  24. }

4.使用线程池例如Executor框架

1.5后引入的Executor框架的最大优点是把任务的提交和执行解耦。要执行任务的人只需把Task描述清楚,然后提交即可。这个Task是怎么被执行的,被谁执行的,什么时候执行的,提交的人就不用关心了。具体点讲,提交一个Callable对象给ExecutorService(如最常用的线程池ThreadPoolExecutor),将得到一个Future对象,调用Future对象的get方法等待执行结果就好了。Executor框架的内部使用了线程池机制,它在java.util.cocurrent 包下,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,除了更易管理,效率更好(用线程池实现,节约开销)外,还有关键的一点:有助于避免this逃逸问题——如果我们在构造器中启动一个线程,因为另一个任务可能会在构造器结束之前开始执行,此时可能会访问到初始化了一半的对象。

  1. Executor框架包括:线程池,ExecutorExecutorsExecutorServiceCompletionServiceFutureCallable等。
  2. Executor接口中之定义了一个方法executeRunnable command),该方法接收一个Runable实例,它用来执行一个任务,任务即一个实现了Runnable接口的类。ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以调用ExecutorServiceshutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程。
  3. ExecutorService的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了shutdown()方法时,便进入关闭状态,此时意味着ExecutorService不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。如果不调用shutdown()方法,ExecutorService会一直处在运行状态,不断接收新的任务,执行新的任务,服务器端一般不需要关闭它,保持一直运行即可。
  4. Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
  5. public static ExecutorService newFixedThreadPool(int nThreads)
  6. 创建固定数目线程的线程池。
  7. public static ExecutorService newCachedThreadPool()
  8. 创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线 程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
  9. public static ExecutorService newSingleThreadExecutor()
  10. 创建一个单线程化的Executor
  11. public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
  12. 创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

四种方式比较

image.png

Executor执行Runnable任务

通过Executors的以上四个静态工厂方法获得 ExecutorService实例,而后调用该实例的execute(Runnable command)方法即可。一旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上

  1. [java] view pl
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. public class TestCachedThreadPool{
  5. public static void main(String[] args){
  6. ExecutorService executorService = Executors.newCachedThreadPool();
  7. // ExecutorService executorService = Executors.newFixedThreadPool(5);
  8. // ExecutorService executorService = Executors.newSingleThreadExecutor();
  9. for (int i = 0; i < 5; i++){
  10. executorService.execute(new TestRunnable());
  11. System.out.println("************* a" + i + " *************");
  12. }
  13. executorService.shutdown();
  14. }
  15. }
  16. class TestRunnable implements Runnable{
  17. public void run(){
  18. System.out.println(Thread.currentThread().getName() + "线程被调用了。");
  19. }
  20. }

2.线程的生命周期

线程是一个动态执行的过程,它也有一个从产生到死亡的过程,下图显示了一个线程完整的生命周期。
1322453-20181129150053804-206188597.png

3.wait和sleep的区别

1.wait方法

wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去机锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;
wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
wiat()必须放在synchronized block中,否则会在program runtime时扔出“java.lang.IllegalMonitorStateException”异常。

2.sleep方法

sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。

3.共同点

  1. 他们都是在多线程的环境下,都可以在程序的调用处阻塞指定的毫秒数,并返回。
    2. wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。
    如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。
    需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。

    4.不同点

  2. Thread类的方法:sleep(),yield()等
    Object的方法:wait()和notify()等
    2. 每个对象都有一个锁来控制同步访问。Synchronized关键字可以和对象的锁交互,来实现线程的同步。
    sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。
    3. wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
    4. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常
    所以sleep()和wait()方法的最大区别是:
    sleep()睡眠时,保持对象锁,仍然占有该锁;
    而wait()睡眠时,释放对象锁。
    但是wait()和sleep()都可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException(但不建议使用该方法)。

    4.线程的七大参数

    1.corePoolsize(线程池核心线程大小)即线程的最小数量

    线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即是corePoolSize。

    2.maximumPoolSize:

    线程池最大线程数,线程池所能容纳的线程。

    3.keepAliveTime:

    多余的线程没有任务执行之后,线程在线程池中最多待多久的时间才销毁,直到只剩下corePoolSize个线程为止。

    4.TimeUnit:参数keepAliveTime的时间单位,一共7种取值

    1. TimeUnit.DAYS; //天
    2. TimeUnit.HOURS; //小时
    3. TimeUnit.MINUTES; //分钟
    4. TimeUnit.SECONDS; //秒
    5. TimeUnit.MILLISECONDS; //毫秒
    6. TimeUnit.MICROSECONDS; //微妙
    7. TimeUnit.NANOSECONDS; //纳秒

    5.workQueue:

    一种阻塞队列,用来存储等待线程执行的任务。

    6.threadFactory:

    用来创建线程的工厂

    7.RejectedExecutionHandler:

    表示当线程池满以后,拒绝任务时采取的策略,一共四种策略,如下
    1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
    2. ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
    3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务