线程池
1.线程池
-
问题:
- 线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。
- 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。
-
线程池:
- 线程容器,可设定线程分配的数量上限。
- 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
- 避免频繁的创建和销毁。
-
线程池工作原理:将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程。
-
常用的线程池接口和类(所在包java.util.concurrent):·
Executor//线程池的顶级接口,只有一个方法execute() ExecutorService//线程池接口,可通过submit(Runnable task)提交任务代码;有两个实现类 ThreadPoolExecutor ScheduledThreadPoolExecutor Executors工具类//通过此类可以获得一个线程池。 //通过newFixedThreadPool(int nThreads)获取固定数量的线程池。参数:指定线程池中线程的数量。 //通过newCachedThreadPool()获得动态数量的线程池,如不够则创建新的,没有上限 //创建单线程池 //创建调度线程池,调度:周期,定时执行/** * 演示线程池的创建 */ public class Demo01 { public static void main(String[] args) { //1.创建线程池 //1.1创建固定线程个数的线程池 // ExecutorService executorService = Executors.newFixedThreadPool(4); //1.2创建缓冲线程池,线程个数由任务个数确定 ExecutorService executorService = Executors.newCachedThreadPool(); //1.3创建单线程池 // Executors.newSingleThreadExecutor(); //1.4创建调度线程池 // Executors.newScheduledThreadPool(); //2.提交任务 Runnable runnable=new Runnable() { private int ticket=100; @Override public void run() { while (true) { synchronized (this){ if (ticket <=0){ break; } System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票"); ticket--; } } } }; for (int i=0;i<5;i++) { executorService.submit(runnable); } //3.关闭线程池 executorService.shutdown();//等待所有任务执行完毕,然后关闭线程池 } }
2.Callable接口
public interface Callable<V> {
public V call() throws Exception;
}
- JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
- Callable具有泛型返回值、可以声明异常。
- Callable和Runnable接口的区别:
- (1)Callable接口中call方法有返回值, Runnable接口中run方法没有返回值
- (2)Callable接口中call方法有声明异常,Runnable接口中run方法没有异常
/**
* 演示Callable接口的使用
* Callable和Runnable接口的区别
* (1)Callable接口中call方法有返回值, Runnable接口中run方法没有返回值
* (2)Callable接口中call方法有声明异常,Runnable接口中run方法没有异常
*/
public class Demo02 {
public static void main(String[] args) throws Exception{
//功能要求:使用Callable实现1-100的和
//1.创建Callable对象
Callable<Integer> callable=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始计算:");
int sum=0;
for (int i = 0; i < 100; i++) {
sum+=i;
Thread.sleep(100);
}
return sum;
}
};
//2.把Callable对象转换成一个可执行的任务
FutureTask<Integer> task=new FutureTask<>(callable);
//3.创建线程
Thread thread=new Thread(task);
//4.启动线程
thread.start();
//5.获取结果(等待call()方法执行完毕才会返回)
Integer sum=task.get();
System.out.println("结果是:"+sum);
}
}
/**
* 使用线程池计算1-100的和
*/
public class Demo03 {
public static void main(String[] args) throws Exception{
//1.创建线程池
ExecutorService es= Executors.newFixedThreadPool(1);
//2.提交任务
Future<Integer> future=es.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始计算:");
int sum=0;
for (int i = 0; i < 100; i++) {
sum+=i;
}
return sum;
}
});
//3.获取结果,等待任务执行完毕才会返回
System.out.println(future.get());
//4.关闭线程池
es.shutdown();
}
}
3.Future接口
- 表示将要完成任务的结果
- 表示ExecutorService.submit()所返回的状态结果,就是call()的返回值
- 方法: V get()以阻塞形式等待Future中的异步处理结果(call()的返回值)
需求:使用两个线程,并发计算150、51100的和,再进行汇总统计
/**
* 需求:使用两个线程,并发计算1~50、51~100的和,再进行汇总统计
*/
public class Demo04 {
public static void main(String[] args) throws Exception{
ExecutorService es= Executors.newFixedThreadPool(2);
Callable<Integer> callable1=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始计算1-49:");
int sum=0;
for (int i = 0; i < 50; i++) {
sum+=i;
}
return sum;
}
};
Callable<Integer> callable2=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始计算50-100:");
int sum=0;
for (int i = 50; i < 101; i++) {
sum+=i;
}
return sum;
}
};
Future<Integer> submit1 = es.submit(callable1);
Future<Integer> submit2 = es.submit(callable2);
//获取结果get()
Integer integer1 = submit1.get();
Integer integer2 = submit2.get();
System.out.println("结果是:"+(integer1+integer2));
//关闭线程池
es.shutdown();
}
}
4.线程的同步与异步
- 同步:形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。
- 同步是单条执行路径。
- 异步:形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。二者竞争时间片,并发执行。
5.Lock接口
-
JDK5加入,与synchronized比较,显示定义,结构更灵活。
-
提供更多实用性方法,功能更强大、性能更优越。
-
常用方法:
void lock()//获取锁,如锁被占用,则等待。 boolean tryLock()//尝试获取锁(成功返回true。失败返回false,不阻塞) void unlock()//释放锁
5.1重入锁
ReentrantLock: Lock接口的实现类,与synchronized一样具有互斥锁功能。
/**
* 演示ReentrantLock的使用
*/
public class MyList {
private Lock lock=new ReentrantLock();
private String[] str={"A","B","","",""};
private int count=2;
public void add(String value){
lock.lock();
try {
str[count]=value;
count++;
}finally {
lock.unlock();
}
System.out.println(Thread.currentThread().getName()+"添加了:"+value);
}
public String[] getStr() {
return str;
}
}
//测试类
public class TestMyList {
public static void main(String[] args) throws Exception{
MyList list=new MyList();
Runnable runnable1=new Runnable() {
@Override
public void run() {
list.add("hello");
}
};
Runnable runnable2=new Runnable() {
@Override
public void run() {
list.add("world");
}
};
Thread thread1=new Thread(runnable1);
Thread thread2=new Thread(runnable2);
thread1.start();
thread2.start();
thread1.join();//保证在输出str之前把thread2执行完
thread2.join();//保证在输出str之前把thread2执行完
System.out.println(Arrays.toString(list.getStr()));
}
}
//卖票
public class Ticket implements Runnable{
private int ticket=100;
Lock lock=new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
ticket--;
}finally {
lock.unlock();
}
}
}
}
//测试类
public class Test {
public static void main(String[] args) {
Ticket ticket=new Ticket();
ExecutorService executorService = Executors.newFixedThreadPool(4);
for (int i = 0; i < 4; i++) {
executorService.submit(ticket);
}
executorService.shutdown();
}
}
5.2读写锁
-
ReentrantReadWriteLock:
- 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
- 支持多次分配读锁,使多个读操作可以并发执行。
-
互斥规则:
- 写-写:互斥,阻塞。
- 读-写:互斥,读阻塞写、写阻塞读。。读-读:不互斥、不阻塞。
- 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
/**
* 演示读写锁的使用
* ReentrantReadWriteLock
*/
public class ReadWriteDemo0 {
//创建读写锁
private ReentrantReadWriteLock rrw=new ReentrantReadWriteLock();
//获取读锁
private ReentrantReadWriteLock.ReadLock readLock=rrw.readLock();
//获取写锁
private String valve;
//创建互斥锁
private ReentrantLock rl=new ReentrantLock();
private ReentrantReadWriteLock.WriteLock writeLock=rrw.writeLock();
//读取
public String getValve() {
//使用读锁上锁
readLock.lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("读取"+valve);
return valve;
} finally {
readLock.unlock();
}
}
//写入
public void setValve(String valve) {
rl.lock();
try {
System.out.println("写入"+valve);
this.valve = valve;
} finally {
rl.unlock();
}
}
}
//测试类
public class Test {
public static void main(String[] args) {
ReadWriteDemo0 rw=new ReadWriteDemo0();
//创建线程池
ExecutorService executorService = Executors.newFixedThreadPool(20);
Runnable runnableRead=new Runnable() {
@Override
public void run() {
rw.getValve();
}
};
Runnable runnableWrite=new Runnable() {
@Override
public void run() {
rw.setValve("张三:"+new Random().nextInt(100));
}
};
long start=System.currentTimeMillis();
//分配两个写的任务
for (int i = 0; i < 2; i++) {
executorService.submit(runnableWrite);
}
//分配任务,18个读取任务
for (int i = 0; i < 18; i++) {
executorService.submit(runnableRead);
}
executorService.shutdown();
while (!executorService.isTerminated()) {//空转
}
long end=System.currentTimeMillis();
System.out.println("用时:"+(end-start));
}
}
6.线程安全的集合
- Collection体系集合中,除Vector以外的线程安全集合(蓝色是线程安全)
- Map体系集合(蓝色是线程安全)
- Collections中的工具方法
//Collections工具类中提供了多个可以获得线程安全集合的方法。
public static <T> Collection<T> synchronizedCollection(Collection<T> c) public static <T> List<T> synchronizedList(List<T> list)
public static <T> Set<T> synchronizedSet(Set<T> s)
public static <K,V> Map<K, V> synchronizedMap(Map<K,V> m)
public static <T>SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
public static<K,V> SortedMap<K, V> synchronizedSortedMap(SortedMap<K,V> m)
//JDK1.2提供,接口统一、维护性高,但性能没有提升,均以synchonized实现。
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 使用多线程操作线程不安全集合
* 把线程不安全的集合转为线程安全的集合
*/
public class Demo01 {
public static void main(String[] args) {
//1.创建ArrayList
// ArrayList<String> arrayList=new ArrayList<>();
//1.1.使用Collections钟德线程安全方法转成线程安全的集合
// List<String> strings = Collections.synchronizedList(arrayList);
//1.2.使用CopyOnWriteArrayList
CopyOnWriteArrayList<String> copy=new CopyOnWriteArrayList<>();
//2.创建线程
for (int i = 0; i < 30; i++) {
int temp=i;
new Thread(new Runnable(){
@Override
public void run() {
for (int j = 0; j <2 ; j++) {
copy.add(Thread.currentThread().getName()+"====="+temp+"====="+j);
System.out.println(copy.toString());
}
}
}).start();
}
}
}
6.1CopyOnWriteArrayList
- 线程安全的ArrayList,加强版的读写分离。
- 写有锁,读无锁,读写之间不阻塞,优于读写锁。
- 写入时,先copy一个容器副本、再添加新元素,最后替换引用。
- 使用方式与ArrayList无异。
/**
* 使用多线程来操作 CopyOnWriteArrayList
*/
public class Demo02 {
public static void main(String[] args) {
//1.创建集合
CopyOnWriteArrayList<String> list=new CopyOnWriteArrayList<>();
//2.使用线程池来操作
ExecutorService executorService = Executors.newFixedThreadPool(5);
//3.提交任务
for (int i = 0; i < 5; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
list.add(Thread.currentThread().getName()+"...."+new Random().nextInt(1000));
}
}
});
}
//4.关闭线程池
executorService.shutdown();
while (!executorService.isTerminated()){
}
//5.打印结果
System.out.println(list.toString());
}
}
6.2CopyOnWriteArraySet
- 线程安全的Set,底层使用CopyOnWriteArrayList实现。
- 唯一不同在于,使用addlfAbsent()添加元素,会遍历数组,如存在元素,则不添加(扔掉副本)。
/**
* 演示CopyOnWriteArraySet的使用
* 有序,不会添加重复元素
*/
public class Demo03 {
public static void main(String[] args) {
//1.创建集合
CopyOnWriteArraySet<String> cowas=new CopyOnWriteArraySet<>();
//2.添加元素
cowas.add("pingguo");
cowas.add("huawei");
cowas.add("xiaomi");
cowas.add("lianxiang");
cowas.add("lianxiang");
//3.打印
System.out.println("元素个数:"+cowas.size());
System.out.println(cowas.toString());
}
}
6.3Queue接口
-
Collection的子接口,表示队列FIFO (First In First Out)先进先出
-
常用方法:
//抛出异常: boolean add(E e)//顺序添加一个元素(到达上限后,再添加则会抛出异常)。 E remove()//获得第一个元素并移除(如果队列没有元素时,则抛异常)。 E element()//获得第一个元素但不移除(如果队列没有元素时,则抛异常) -
返回特殊值:推荐使用
boolean offer(E e)//顺序添加一个元素(到达上限后,再添加则会返回false)。 E poll()//获得第一个元素并移除(如果队列没有元素时,则返回null) E peek()//获得第一个元素但不移除(如果队列没有元素时,则返回nul1)/** * Queue接口的使用 */ public class Demo04 { public static void main(String[] args) { //1.创建队列 Queue<String> queue=new LinkedList<>(); //2.入队 queue.offer("苹果"); queue.offer("橘子"); queue.offer("西瓜"); queue.offer("榴莲"); System.out.println(queue.peek()); //3.出队 System.out.println("==========="); int size=queue.size(); for (int i = 0; i < size; i++) { System.out.println(queue.poll()); } } }
6.4ConcurrentLinkedQueue
- 线程安全、可高效读写的队列,高并发下性能最好的队列。
- 无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)。
- V:要更新的变量、E:预期值、N:新值。
- 只有当V==E时,V=N;否则表示已被更新过,则取消当前操作。
/**
* 演示ConcurrentLinkedQueue的使用
*/
public class Demo05 {
public static void main(String[] args) throws Exception{
//1.创建线程安全的队列
ConcurrentLinkedQueue<Integer> queue=new ConcurrentLinkedQueue<>();
//2.入队操作
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
queue.offer(i);
}
}
});
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
for (int i = 6; i < 10; i++) {
queue.offer(i);
}
}
});
//3.启动线程
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("---------出队--------");
//4.出队操作
int size=queue.size();
for (int i = 0; i < size; i++) {
System.out.println(queue.poll());
}
}
}
6.5BlockingQueue接口(阻塞队列)
-
Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。
-
方法:
void put(E e)//将指定元素插入此队列中,如果没有可用空间,则等待。 E take()//获取并移除此队列头部元素,如果没有可用元素,则等待。 -
实现类:
//ArrayBlockingQueue:数组结构实现,有界队列。(手工固定上限)
public class TestArrayB1ockingQueue {
public static void main(String[]args) {
BlockingQueue<String> abq = new ArrayBlockingQueue<String>(10);
}
}
// LinkedBlockingQueue:链表结构实现,有界队列。(默认上限Integer.MAX_VALUE)
public class TestLinkedBlockingQueue {
public static void main(String[]args) {
BlockingQueue<String> lbg = new LinkedBlockingQueue<String>;
}
}
/**
* 阻塞队列的使用
* 案例1:创建一个有界队列,添加数据
* 案例2:使用阻塞队列实现生产者和消费者
* @author NG
*/
public class Demo06 {
public static void main(String[] args) throws Exception{
//创建一个有界队列,添加数据
ArrayBlockingQueue<String> queue=new ArrayBlockingQueue<>(5);
//添加元素
queue.put("aaa");
queue.put("bbb");
queue.put("ccc");
queue.put("ddd");
queue.put("eee");
queue.take();
System.out.println("已经添加了5个元素");
queue.put("xyz");
System.out.println("已经添加了6个元素");
System.out.println(queue.toString());
}
}
/**
* 案例2:使用阻塞队列实现生产者和消费者
*/
public class Demo07 {
public static void main(String[] args) {
//1.创建队列
ArrayBlockingQueue<Integer> queue=new ArrayBlockingQueue<>(6);
//2.创建2个线程
Thread t1=new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
queue.put(i);
System.out.println(Thread.currentThread().getName()+"生产了"+i+"号面包");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"晨晨");
Thread t2=new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
try {
Integer num=queue.take();
System.out.println(Thread.currentThread().getName()+"消费了"+i+"号面包");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
},"冰冰");
//3.启动线程
t1.start();
t2.start();
}
}
6.6ConcurrentHashMap
- 初始容量默认为16段(Segment),使用分段锁设计。
- 不对整个Map加锁,而是为每个Segment加锁。
- 当多个对象存入同一个Segment时,才需要互斥。
- 最理想状态为16个对象分别存入16个Segment,并行数量16。使用方式与HashMap无异。
/**
*ConcurrentHashMap的使用
*/
public class Demo08 {
public static void main(String[] args) {
//1.创建集合
ConcurrentHashMap<String,String> hashMap=new ConcurrentHashMap<>();
//2.使用多线程添加数据
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j <10 ; j++) {
hashMap.put(Thread.currentThread().getName()+"--"+j, j+"");
System.out.println(hashMap);
}
}
}).start();
}
}
}

浙公网安备 33010602011771号