1、实现下面的一个需求,控制一个执行函数只能被五个线程访问

package www.weiyuan.test;

public class Test {

    public static void main(String[] args) {
for(int i = 0 ;i < 100 ;i++){
new Thread(new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub
method();
}
}).start();
}
} public static void method(){
System.out.println(""+Thread.currentThread().getName()+"进来了");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(""+Thread.currentThread().getName()+"出去了");
}
}

上面我们创建了100个线程,随机的执行method方法

现在我们要控制每次只要5个线程可以method方法,如何实现了,可以采用信号量的方法

操作系统的信号量是个很重要的概念,在进程控制方面都有应用。Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,acquire()获取一个许可,如果没有就等待,而release()释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。

Semaphore维护了当前访问的个数,提供同步机制,控制同时访问的个数。在数据结构中链表可以保存“无限”的节点,用Semaphore可以实现有限大小的链表。另外重入锁ReentrantLock也可以实现该功能,但实现上要负责些,代码也要复杂些。

下面的Demo中申明了一个只有5个许可的Semaphore,而有100个线程要访问这个资源,通过acquire()和release()获取和释放访问许可。

第一种方式使用信号量的方式:

package www.weiyuan.test;

import java.util.concurrent.Semaphore;

public class Test {
private static Semaphore semaphore = new Semaphore(5);
public static void main(String[] args) {
for(int i = 0 ;i < 100 ;i++){
new Thread(new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub
method();
}
}).start();
}
} public static void method(){ try {
semaphore.acquire();
System.out.println(""+Thread.currentThread().getName()+"进来了");
Thread.sleep(1000);
System.out.println(""+Thread.currentThread().getName()+"出去了");
semaphore.release();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }
}

第二种方式使用线程池的方式:

package www.weiyuan.test;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore; public class Test {
private static Executor executor = Executors.newFixedThreadPool(5);
public static void main(String[] args) {
for(int i = 0 ; i< 100 ;i++){
executor.execute(new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub
method();
}
});
} } public static void method(){ try {
System.out.println(""+Thread.currentThread().getName()+"进来了");
Thread.sleep(1000);
System.out.println(""+Thread.currentThread().getName()+"出去了");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }
}

接下来我们自己编写代码自己封装一个线程池:

深入分析java线程池的实现原理

ThreadPoolExecutor

Executors是java线程池的工厂类,通过它可以快速初始化一个符合业务需求的线程池,如Executors.newFixedThreadPool方法可以生成一个拥有固定线程数的线程池。

我们来分析下上面的几个参数的意思

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory)

我们来分析上面参数的意思:

线程池存在下面的两个数据结构

1、第一个数据结构是任务队列,用来存储任务的

2、第二个数据队列来存储线程的集合

线程池会创建线程执行任务,因为执行的任何很多,创建的线程如果达到了corePoolSize的数目,列如现在的corePoolSize的数目是5,但是需要执行的任务数量是100,

当创建了5个线程执行前五个任务之后,如果来了第6个任务,这个时候不会在创建新的线程了,会把多余的任务添加到任务队列中,任务队列也是有上限值的

上面的分析可以用下图来表示

如果工作队列已经达到了最大值,此时如果继续向任务队列中添加任务,即下来会做啥操作了

这个时候线程池会创建新的线程,用新的线程来执行新添加的任务,直到线程数目maximumPoolSize,如果线程的数目已经是maximumPoolSize,如果此时在添加任务,这个时候线程池就会抛出异常了

线程池中的线程数目不是越大越好,推荐的数目是电脑cpu的值加上1

如果线程池的线程数量少于corePoolSize的时候,线程池会使用threadFactory这个线程工厂创建新的线程执行Runnable任务。
如果线程池的线程数量大于corePoolSize的时候,线程池会把Runnable任务存放在队列workQueue中。
线程池的线程数量大于corePoolSize,队列workQueue已满,而且小于maximumPoolSize的时候,线程池会创建新的线程执行Runnable任务。否则,任务被拒。

keepAliveTime

线程空闲时的存活时间,即当线程没有任务执行时,继续存活的时间;默认情况下,该参数只在线程数大于corePoolSize时才有用;

unit

keepAliveTime的单位;

workQueue

用来保存等待被执行的任务的阻塞队列,且任务必须实现Runable接口,在JDK中提供了如下阻塞队列:
1、ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务;
2、LinkedBlockingQuene:基于链表结构的阻塞队列,按FIFO排序任务,吞吐量通常要高于ArrayBlockingQuene;
3、SynchronousQuene:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene;
4、priorityBlockingQuene:具有优先级的无界阻塞队列;

threadFactory

创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。

我们来看一下我们自定定义的一个一个线程池对象实现上面的代码:

package www.weiyuan.test;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; public class Test { public static void main(String[] args) {
LinkedBlockingDeque<Runnable> blockingDeque = new LinkedBlockingDeque<Runnable>(100);//100是该任务队列的最大数目,达到100之后不会自动扩容了
ThreadFactory threadFactory = new ThreadFactory() {
AtomicInteger atomicInteger = new AtomicInteger(); //线程安全的变量自增
@Override
public Thread newThread(Runnable task) {
//收到创建线程,将需要执行的任务交给线程去处理,这个地方一定注意线程安全 thread.setName("MyTHread"+i++);i++是线程不安全的
Thread thread = new Thread(task); thread.setName("MyTHread"+atomicInteger.getAndIncrement());
return thread;
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, blockingDeque, threadFactory ); for(int i = 0 ;i < 100;i++){
threadPoolExecutor.execute(new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub
method();
}
});
}
} public static void method(){ try {
System.out.println(""+Thread.currentThread().getName()+"进来了");
Thread.sleep(1000);
System.out.println(""+Thread.currentThread().getName()+"出去了");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }
}

1.Android中的资源池其实来源于Java。线程池的实现是ThreadPoolExecutor。Java中有一个Executors工厂,这个工厂类提供了许多静态方法用于创建不同类型的线程池。创建自定义的线程池一般需要指定以下的参数。
a.corePoolSize:代表的是线程池的核心线程数量,一般来说,核心线程会在线程池中一直存活,即便处于空闲状态(没有执行任务的状态)。但是也可以通过将ThreadPoolExecutor的allowCoreThreadTimeOut()设置为true,那么核心线程在空闲的时候会有超时的策略。
b.maximumPoolSize:代表线程池能够容纳的最多的线程数量。当线程数量达到这个数值后,后续的任务将会被阻塞。
c.keepAliveTime:设置非核心线程空闲时的超时时长,一旦达到这个限制,线程就会被回收。当设置ThreadPoolExecutor的allowCoreThreadTimeOut()为true时,这个超时限制也可以作用于核心线程。
d.unit:指定keepAliveTime参数的时间单位。
e.workQueue:代表线程池中的任务队列,通过execute()提交的任务都会加到这个队列中。
f.threadFactory:线程工厂,为线程池提供创建线程的功能。

上面代码中,我们创建的线程池中的 maximumPoolSize的值是10,任务队列的最大值是100

 LinkedBlockingDeque<Runnable>(100)

所以该线程池能够最大存储的任务数目是110,最大10个线程执行10个任务,其余100个线程存储在任务队列中

如果你一开始执行的任务大于110,线程池就会报异常
package www.weiyuan.test;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; public class Test { public static void main(String[] args) {
LinkedBlockingDeque<Runnable> blockingDeque = new LinkedBlockingDeque<Runnable>(100);//100是该任务队列的最大数目,达到100之后不会自动扩容了
ThreadFactory threadFactory = new ThreadFactory() {
AtomicInteger atomicInteger = new AtomicInteger(); //线程安全的变量自增
@Override
public Thread newThread(Runnable task) {
//收到创建线程,将需要执行的任务交给线程去处理,这个地方一定注意线程安全 thread.setName("MyTHread"+i++);i++是线程不安全的
Thread thread = new Thread(task); thread.setName("MyTHread"+atomicInteger.getAndIncrement());
return thread;
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS, blockingDeque, threadFactory ); for(int i = 0 ;i < 115;i++){
threadPoolExecutor.execute(new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub
method();
}
});
}
} public static void method(){ try {
System.out.println(""+Thread.currentThread().getName()+"进来了");
Thread.sleep(1000);
System.out.println(""+Thread.currentThread().getName()+"出去了");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} }
}

我们执行115个任务

就会包下面的异常:

查看这篇博客相当的经典:http://www.jianshu.com/p/87bff5cc8d8c

java 中的线程池的更多相关文章

  1. 【万字图文-原创】 | 学会Java中的线程池,这一篇也许就够了!

    碎碎念 关于JDK源码相关的文章这已经是第四篇了,原创不易,粉丝从几十人到昨天的666人,真的很感谢之前帮我转发文章的一些朋友们. 从16年开始写技术文章,到现在博客园已经发表了222篇文章,大多数都 ...

  2. 《Java并发编程的艺术》 第9章 Java中的线程池

    第9章 Java中的线程池 在开发过程中,合理地使用线程池能带来3个好处: 降低资源消耗.通过重复利用已创建的线程 降低线程创建和销毁造成的消耗. 提高响应速度.当任务到达时,任务可以不需要等到线程创 ...

  3. JAVA中创建线程池的五种方法及比较

    之前写过JAVA中创建线程的三种方法及比较.这次来说说线程池. JAVA中创建线程池主要有两类方法,一类是通过Executors工厂类提供的方法,该类提供了4种不同的线程池可供使用.另一类是通过Thr ...

  4. Java中的线程池用过吧?来说说你是怎么理解线程池吧?

    前言 Java中的线程池用过吧?来说说你是怎么使用线程池的?这句话在面试过程中遇到过好几次了.我甚至这次标题都想写成[Java八股文之线程池],但是有点太俗套了.虽然,线程池是一个已经被说烂的知识点了 ...

  5. 浅析Java中的线程池

    Java中的线程池 几乎所有需要异步或并发执行任务的程序都可以使用线程池,开发过程中合理使用线程池能够带来以下三个好处: 降低资源消耗 提高响应速度 提高线程的可管理性 1. 线程池的实现原理 当我们 ...

  6. 第9章 Java中的线程池 第10章 Exector框架

    与新建线程池相比线程池的优点 线程池的分类 ThreadPoolExector参数.执行过程.存储方式 阻塞队列 拒绝策略 10.1 Exector框架简介 10.1.1 Executor框架的两级调 ...

  7. Java中的线程池

    package com.cn.gbx; import java.util.Date; import java.util.Random; import java.util.Timer; import j ...

  8. java 中的线程池和线程 调用小demo

    public class Main { public static void main(String[] args) { try { /// ThreadPoolExecutor executor = ...

  9. Java中的线程池ExecutorService

    示例 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.u ...

随机推荐

  1. Linux显示行号设置

    linux显示行号设置 第一步,打开vim vi ~/.vimrc 第二步,在该文件中加入一行,命令如下: set nu # 显示行号 set nonu # 不显示行号 微信公众号:喵哥解说 公众号介 ...

  2. 关于Vue data对象赋值的问题

    遇到这么一个问题: 把data中的某个对象赋值给一个变量,修改变量,会同时把data中的对象也一同修改,所以,这个赋值应该就是引用了地址,贴个代码 <script> export defa ...

  3. css 盒模型、box-sizing 学习笔记

    默认情况下,给元素设置的高度和宽度是元素内容区的宽度和高度,给元素加padding 和 border ,元素的实际宽度和高度的计算方式是下面的两个公式: 元素的宽度= 元素的内容区宽度 + 内边距宽度 ...

  4. Web缓存欺骗

    该漏洞主要是cdn安全配置的问题,cdn主要存储以下文件,加快访问速度 class, css, jar, js, jpg, jpeg, gif, ico, png, bmp, pict, csv, d ...

  5. prism.js——让网页中的代码更好看

    粗放的代码展示 有时候,网页中会插入代码.直接把代码放入<pre></pre>标签和<code></code>标签里,也算是可以在页面中显示出来. 比如 ...

  6. Java实现 LeetCode 504 七进制数

    504. 七进制数 给定一个整数,将其转化为7进制,并以字符串形式输出. 示例 1: 输入: 100 输出: "202" 示例 2: 输入: -7 输出: "-10&qu ...

  7. Java实现 LeetCode 30 串联所有单词的子串

    30. 串联所有单词的子串 给定一个字符串 s 和一些长度相同的单词 words.找出 s 中恰好可以由 words 中所有单词串联形成的子串的起始位置. 注意子串要与 words 中的单词完全匹配, ...

  8. 第六届蓝桥杯JavaC组国(决)赛真题

    解题代码部分来自网友,如果有不对的地方,欢迎各位大佬评论 题目1.机器人数目 少年宫新近邮购了小机器人配件,共有3类,其中, A类含有:8个轮子,1个传感器 B类含有: 6个轮子,3个传感器 C类含有 ...

  9. java实现第四届蓝桥杯金蝉素数

    金蝉素数 考古发现某古墓石碑上刻着一个数字:13597,后研究发现: 这是一个素数! 并且,去掉首尾数字仍是素数! 并且,最中间的数字也是素数! 这样特征的数字还有哪些呢?通过以下程序的帮助可以轻松解 ...

  10. 07.Django-缓存

    目录 缓存 一.如何提高网站并发量? 二.缓存方式 1. 开发调式缓存 2. 内存缓存 3. 文件缓存 4. 数据库缓存 5. Memcache缓存 5.1 使用python-memcached模块 ...