java多线程
java多线程使用
java多线程的实现方式有: 继承Thread类 , 实现Runnable接口,实现Callable接口,其中继承thread类和实现Runnable接口是没有返回值的,而通过实现Callable接口可以又返回值。
1.通过继承Thread类实现多线程
通过继承Thread类实现多线程需要重现run()方法,Thread类实现了Runnable接口,所以通过这种方法,本质上还是实现了Runnable的方式,启动这个多线程需要使用Thread的start()方法。
//可以看到Thread类实现了Runnable方法
class Thread implements Runnable
具体实现也很很简单,如下:
public class Test{
public static void main (String[] args) {
Mythread th1 = new MyThread();
Mythread th2 = new MyThread();
th.start();
}
}
class MyThread extends Thread {
//输出1到10
@Override
public void run() {
for(int i = 0; i < 10; i++){
System.out.println(i);
}
}
}
2.通过实现Runnable接口实现多线程
这种方式和第一种继承Thread类实现方式差不多,一样是重写run()方法,然后通过start()方法来启动,本质都是实现了Runnable接口。但是这种方式不能直接使用start()方法来启动run()方法,需要通过Thread来启动,因为在Runnable接口中只定义了一个run()方法,start()方法是Thread中的。
//Runnable 接口理只有定义了一个run()方法
public interface Runnable {
public abstract void run();
}
具体实现:
public class Test2{
public static void main (String[] args) {
MyThreadRunnable myth = new MyThreadRunnable ();
Thread th = new Thread(myth);
th.start();
}
}
class MyThreadRunnable implements Runnable {
//同样的输出1到10
@Override
public void run() {
for(int i = 0; i < 10; i++){
System.out.println(i);
}
}
}
3.通过实现Callable接口实现多线程
通过Callable接口,配合上javaFutureTask类多线程可以实现具有返回值的任务。因为Callable和Runnable接口相似并没有执行能力,他只有一个方法Call(),所以需要通过FutureTask,将Callable实例放入Thread中来实现。
为什么是FutureTask呢? FutureTask实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable接口
//Callbale
public interface Callable<V> {
V call() throws Exception;
}
//FutureTask
public class FutureTask<V> implements RunnableFuture<V>
//RunnableFuture
public interface RunnableFuture<V> extends Runnable, Future<V>
具体实现过程为:
public class Test3{
public static void main (String[] args) {
MyCallable myCallable = new MyCallable();
//创建FutureTask实例并传入MyCallable实例
FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
Thread th = new Thread(futureTask,"CallableThread");
th.start();
//获取输入返回值
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Integer sum = 0;
for(int i = 0; i < 10; i++){
System.out.println(sum+=i);
}
return sum;
}
}
4.线程池
当频繁的使用线程时候,会反复的进行线程的创建和销毁按工作,而线程的差创建和销毁回耗费资源增加系统的耗时,影响系统的性能,在这种情况下就要使用线程池了。简单的来说,线程池是一个容器,里面存放了一定数量的线程,当收到线程任务时,将一个池中的未被使用的线程分配给当前任务,任务完成后线程并不是销毁而是将其释放从新回到线程池中。
1.线程池的状态
线程池有Running、Shutdown、Stop、Tidying、Terminated五种状态
Running:线程池能够接收任务,并对已经存在的任务进行处理,初始的状态就是Running状态。
Shutdown:此状态下,线程池不在接收新发来的任务,只处理已经处在还未处理完的任务。当调用了线程池的shutdown()方法就会使线程池有Running状态进入Shutdown状态。
Stop:此状态下,线程池不会接受新任务,也不再处理已经存在的任务,而且会中断已经在处理中的任务当调用了线程池的shutdownNow()方法就会使线程池有Running状态进入Shutdown状态。
Tidying:当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为Tidying状态。当线程池变为Tidying状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为Tidying时,进行相应的处理;可以通过重载terminated()函数来实现。当线程池在Shutdown状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 Shutdown-> Tidying。 当线程池在STOP状态下,线程池中执行的任务为空时,就会由Stop-> Tidying。
Terminated:线程池销毁,就进入了 Terminated状态。
线程池定义的状态的:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//将COUNT_BITS 的值设置为29,Integer.SIZE = 32
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 将各个状态的初始值左移29位得到状态的具体数值码。
/**
*以RUNNING的-1为例
*-1的原码:10000000 00000000 00000000 00000001
*-1的反码:11111111 11111111 11111111 11111110
*-1的补码:11111111 11111111 11111111 11111111
*将其左移29位得到的值为11100000 00000000 00000000 00000000
*/
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
//将ctl封装和解析的三个方法
//将ctl的值,取高3位的数值,即取状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//将ctl的值 ,取低29位的值,即线程池容量
private static int workerCountOf(int c) { return c & CAPACITY; }
//传入rs(状态)wc(线程池容量),取得ctl
private static int ctlOf(int rs, int wc) { return rs | wc; }
2.任务的执行过程
在默认的情况下,当线程池创建后,并不是直接将所有的线程都创建好放在容器中,而是在任务到来后根据条件创建的。
1.当任务到提交到线程池,如果线程池中的线程小于核心线程数(corePoolSize),则每次提交过来一个任务就创建一个线程。
2.当线程池中的线程数大于等于核心线程数,则每来一个任务,就会尝试将其添加到任务缓存队列(workQueue)当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;
3.当前线程池中的线程数目达到最大线程数(maximumPoolSize)时,线程池会采取任务拒绝策略进行处理;
4.线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程就会被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止,直至线程池中的线程为0。
3.线程池的缓存队列
前面提到的workQueue就是线程池的缓存队列,存放提交过来的等待执行得队列。
常见的几种队列:
(1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小;
(2)LinkedBlockingQueue:此队列没有固定的大小,若在创建是指定大小就有了大小的限制,如果不指定则默认是Integer.MAX_VALUE
(3)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务
(4)synchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务
三种通用的排队策略:
来自:https://www.oschina.net/question/565065_86540
1.直接提交: 工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。
在此,如果不存在可用于立即运行任务的线程,则把任务加入队列将失败,因此会构造一个新的线程。
此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性;
2.无界队列: 使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
3.有界队列: 当使用有限的 maximumPoolSizes时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
4.任务的拒绝
线程池的任务缓存队列已满,或者线程数达到maxnumPoolSize,就会对新来的任务实施拒绝策略,以下是常见的几种策略
1) AbortPolicy : 默认的拒绝策略,会将任务丢弃,并抛出异常
2)DiscardPolicy: 会默默的将任务丢弃,不回有任何提示
3)DiscardOldestPolicy: 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
4)有界队列: 线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度
5.线程池的实现
public class TestThreadPool {
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5));
for(int i=0;i <10;i++){
MyThread myThread = new MyThread ();
executor.execute(myThread );
System.out.println("线程数:"+executor.getPoolSize());
System.out.println("队列中的任务数量:" + executor.getQueue().size());
}
executor.shutdown();
}
}