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();
     }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值