多线程是Java重要的一块基础知识,各种面试中也是考察的重点,需要非常牢固的掌握。多线程的目的是更好的利用CPU资源
线程状态回顾
首先回顾一下线程的5种状态:
NEW 线程初始化,还没调用
run()
之前的状态RUNABLE 可运行态,其中包括RUNNING和READY
WAIT 等待状态,一般都有线程Thread类的静态方法
sleep()
或join()
TIMING WAIT 可中断等待
BLOKCED 阻塞状态,线程尝试获取对象锁未成功,则进入阻塞,或者已经持有锁的情况下,调用了
Object
的wait()
DEAD 线程运行结束
线程实现的方法
Thread类
这个是最基本的实现多线程的方式,继承Thead类,并且重写run()
方法,如下:
注意,Thread.sleep()
要使用try catch
处理异常。
ThreadGroup线程组
ThreadGroup是为了方便对线程进行管理,例如设定线程的属性,setDaemon,异常处理方法,统一安全策略等,具体的使用如下:
|
|
线程一旦归入某个组,就无法更换组
补充,线程在逻辑上是一颗树,root是system线程组,线程组和包含另一个线程组。最简单的在main()函数生成一个线程,那么这个线程属于main线程组,查看一个线程所属的线程组,可使用如下方法:
此外,线程组还有一些实用的功能, activeCount()
获取活动线程的数量和enumerate()
传入线程数组,传出所有活动线程引用
Runable接口
继承一个Thread类来实现定义线程比较直观,但是如果某个线程有自己的父类,那么由于单一继承的原则,不能在继承Thread,这是,可以考虑使用Runable实现。实现Runable接口仅仅只是定义任务的过程,它本身不具有线程功能,run()
也是不同方法,必须把任务附着在Thread上。
|
|
Runnable对比Thread的优势
适合共享变量,由于Runnable只是一个task,可以将一个task传入不同的Thread;不过我认Thread也不是不能共享变量。
突破单继承的限制
访问当前线程方法,使用Thread.currentThread()得到线程对象;而Thread直接使用this即可
Callable/Future/FutureTask
Callable
Java.util.concurrent下的。 Runable线程的执行是没有返回值的,而Callable允许有返回值。首先来看一下Callable的定义:
实现Callable接口的类是有返回值的任务,和之前一样,它不具有多线程的功能。要让其实现多线程,只能使用线程池。其中,ExcutorService接口是线程池的接口,它提供了submit
方法
Future
Futuresubmit
提交任务后, 会返回一个Future<V>
。它主要提供了一下三个功能:
能够中断执行中的任务,
cancel(true)
判断任务是否执行完成,
isDone()
获取任务执行完成后额结果,
V get()
如果任务没没有完成,阻塞到完成;V get(Long timeout , TimeUnit unit)
阻塞指定时间
代码示例:
FutureTask
上面实现多线程的方式虽然功能挺全的,但是比较繁琐,分成了Callable和Future两部分,FutureTask除了实现了Future接口外还实现了Runnable接口,因此FutureTask也可以直接提交给Executor执行。下面来总结一下FutureTask的特性:
传入Runnable或者Callable给FutureTask
相比于Funture+Callable+ThreadPool的组合,FutureTask具有线程功能,可直接调用
run()
方法,并且多次run方法,它都只会执行一次Runnable或者Callable任务也可提交线程池执行
可通过
cancel()
取消执行
适用场景:提交异步任务,主线程执行其他任务,当需要执行结果时,异步获取子线程的执行结果
应用:FuntureTask的一个重要应用就是保证线程只会被执行一次,在高并发的情况下,能够有效的保证线程安全的同时,也能很好的保证效率。一个案例就是基于ConcurrentHashMap
和FutureTask
保证在高并发的情况下对象被创建一次。一个带key的连接池,当key存在时,即直接返回key对应的对象;当key不存在时,则创建连接。
|
|