Day15_多线程 ( 二 )

高级多线程

五. 线程池

线程池概念

现有问题

  • 线程时宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。
  • 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。

线程池

  • 容纳多个线程的容器,可设定线程分配的数量上限。
  • 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
  • 避免频繁的创建和销毁。

线程池原理

在这里插入图片描述

将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程。

好处:
  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止内存溢出。

获取线程池

常用的线程池接口和类

(所在包java.util.concurrent):

  • Executor:线程池的顶级接口。
  • ExcutorService:线程池接口,可通过 submit(Runnable task) 提交任务代码。
  • Excutors工厂类:通过此类可以获得一个线程池。
    • 通过 newFixedThreadPool(int nThreads) 获取固定数量的线程池。参数:指定线程池中线程的数量。
    • 通过 newCachedThreadPool() 获得动态数量的线程池,如不够则创建新的,无上限。
【例】:多窗口独立售票
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class DemoThreadPool {
	public static void main(String[] args) {
		// 1.1 创建固定线程个数的线程池
		ExecutorService es = Executors.newFixedThreadPool(4);
		// 1.2 创建动态扩容的线程池
//		ExecutorService es = Executors.newCachedThreadPool();
		// 2. 创建任务
		Runnable runnable = new Runnable() {
			private int ticket = 1;
			@Override
			public void run() {
				while (true) {
					if (ticket > 100) {
						break;
					}
					System.out.println(Thread.currentThread().getName()+ "卖了"+ ticket+"张票");
					ticket++;
				}
			}
		};
		// 3. 提交任务
		for (int i = 0; i < 4; i++) {
			es.submit(runnable);
		}
		// 4. 关闭线程池
		es.shutdown(); // 等待所有任务执行完毕,然后关闭线程池,不接受新任务。
	}
}

Callable接口

public inteface Callable<V> {
    public V call() throws Exception;
}
  • JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
  • Callable具有泛型返回值、可以声明异常。
【例】:方式三,Callable接口实现
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

public class DemoCallable {
	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 = 1; i <= 100; i++) {
					sum += i;
					Thread.sleep(10);
				}
				return sum;
			}
		};
		// 2 把Callable对象转成可执行任务
		FutureTask<Integer> task = new FutureTask<Integer>(callable);
		// 3 创建线程
		Thread thread = new Thread(task);
		// 4 启动线程
		thread.start();
		// 5 获取结果(等待call执行完毕才会返回)
		Integer sum = task.get(); 
		System.out.println("结果是" + sum);
	}
}

Future接口

概念:异步接受 ExecutorService.submit() 所返回的状态结果,当中包含了call() 的返回值。

方法:V get() 以阻塞形式等待Future中的异步处理结果(call() 的返回值)。

思考:什么是异步,什么是同步

【例】:方式四,使用ExecutorService、Callable、Future实现
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class DemoCallableAndThreadPool {
	public static void main(String[] args) throws Exception {
		// 功能要求: 使用Callable 实现1-100和
		// 1 创建线程池
		ExecutorService es = Executors.newFixedThreadPool(1);
		// 2 提交任务Future: 表示将要执行完任务的结果
		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 = 1; i <= 100; i++) {
					sum += i;
					Thread.sleep(10);
				}
				return sum;
			}
		});
		// 3 获取任务结果,等待任务执行完毕才会返回。
		System.out.println("结果是" + future.get());
		// 4 关闭线程池
		es.shutdown();
	}
}

Lock接口

JDK5加入,java.util.concurrent.locks.Lock 机制与synchronized比较,显式定义,结构更灵活。

提供更多实用性的方法,功能更强大、性能更优越。

常用方法:
void lock()	// 获取锁,如锁被占用,则等待。
boolean tryLock()	// 尝试获取锁(成功返回true。失败返回false,不阻塞)
void unlock()	// 释放锁
重入锁

ReentrantLock

Lock接口的实现类,与synchronized一样具有互斥锁功能。

【例】
public class DemoReentrantLock {
	public static void main(String[] args) throws InterruptedException {
		final MyList myList = new MyList();
		Runnable myRunnable1 = new Runnable() {
			@Override
			public void run() {
				myList.add("a");
			}
		};
		Runnable myRunnable2 = new Runnable() {
			@Override
			public void run() {
				myList.add("b");
			}
		};
		
		Thread t1 = new Thread(myRunnable1);
		Thread t2 = new Thread(myRunnable2);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(Arrays.toString(myList.getStrs()));
	}
}
class MyList {
    private Lock locker = new ReentrantLock(); /* 创建重入锁对象 */
    private String[] strs = {"A", "B", "", "", ""};
    private int count = 2;	// 元素个数
    // 添加元素
    public void add(String value) {
        locker.lock();	/* 显式开启锁 */
        try {
            strs[count] = value;
            try {
                Thread.sleep(1000);	// 主动休眠1秒钟
            } catch (InterruptedException e) {}
            count++;
        } finally {
            locker.unlock();	/* 显式释放锁 ,考虑可能出现异常,释放锁必须放入finally代码块中,避免无法释放。 */
        }
    }
}
读写锁

ReentrantReadWriteLock

  • 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
  • 支持多次分配读锁,使多个读操作可以并发执行。

互斥规则

  • 写-写:互斥,阻塞
  • 读-写:互斥,读阻塞写、写阻塞读
  • 读-读:不互斥,不阻塞
  • 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率
【例】
class MyClass {
    ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    ReadLock readLock = rwl.readLock();
    WriteLock writeLock = rwl.writeLock();
    private int value;
    // 读方法
    public int getValue() throws Exception {
        readLock.lock();	// 开启读锁
        try {
            Thread.sleep(1000);	// 休眠1秒
            return value;
        } finally {
            readLock.unlock();	// 释放读锁
        }
    }
    // 写方法
    public void setValue(int value) throws Exception {
        readLock.lock();	// 开启写锁
        try {
            Thread.sleep(1000);	// 休眠1秒
            this.value = value;
        } finally {
            readLock.unlock();	// 释放写锁
        }
    }
}
public class TestResdWriteLock {
    public static void main(String[] args) {
        final MyClass mc = new MyClass();
        Runnable task1 = new Runnable() {
            public void run(){
                try { mc.setValue(1); } catch(Exception e) {}
            }
        };
        Runnable task2 = new Runnable() {
            public void run(){
                try { mc.getValue(); } catch(Exception e) {}
            }
        };
        ExecutorService es = Executors.newFixedThreadPool(20);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 2; i++) {
            es.submit(task1);	// 提交2次写任务
        }
        for (int i = 0; i < 18; i++) {
            es.submit(task2);	// 提交18次读任务
        }
        es.shutdown();	// 关闭线程池
        while(!es.isTerminated()) {}	// 如果线程未全部结束,则空转等待
        System.out.println(System.currenTimeMillis() - startTime);
    }
}

六. 线程安全的集合

在这里插入图片描述

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提供,接口统一、维护性高,但性能没有提升,均以synchronized实现

CopyOnWriteArrayList

  • 线程安全的ArrayList,加强版读写分离。
  • 写有锁,读无锁,读写之间不阻塞,优于读写锁。
  • 写入时,先copy一个容器副本、再添加新元素,最后替换引用。
  • 使用方式与ArrayList无异。
public class TestCopyOnWriteArrayList {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<String>();
    }
}
【例】
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class DemoCopyOnWriteArrayList {
	public static void main(String[] args) {
		// 1 创建集合
		final CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
		// 2 创建线程池
		ExecutorService es = Executors.newFixedThreadPool(5);
		// 3 提交任务
		for (int i = 0; i < 3; i++) {
			es.submit(new Runnable() {
				@Override
				public void run() {
					for (int j = 0; j < 5; j++) {
						list.add(Thread.currentThread().getName()+"==="+ new Random().nextInt(1000));
					}	
				}
			});
		}
		// 4 关闭线程池
		es.shutdown();
		while (!es.isTerminated()) {}
		// 5 打印结果
		System.out.println("元素个数 : " + list.size());
		for (String string : list) {
			System.out.println(string);
		}
	}
}

CopyOnWriteArraySet

  • 线程安全的Set,底层使用CopyOnWriteArrayList实现。
  • 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组。
  • 如存在元素,则不添加(扔掉副本)。
public class TestCopyOnWriteArraySet {
    public static void main(String[] args) {
        Set<String> set = new CopyOnWriteArraySet<String>();
    }
}

ConcurrentHashMap

  • 初始容量默认为16段(Segment),使用分段锁设计。
  • 不对整个Map加锁,而是为每个Segment加锁。
  • 当多个对象存入同一个Segment时,才需要互斥。
  • 最理想状态为16个对象分别存入16个Segment,并行数量16。
  • 使用方式与HashMap无异。
public class TestConcurrentHashMap {
    public static void main(String[] args) {
        Map<String,String> map = new ConcurrentHashMap<String,String>();
    }
}
【例】
import java.util.concurrent.ConcurrentHashMap;

public class DemoConcurrentHashMap {
	public static void main(String[] args) {
		// 1 创建集合
		final ConcurrentHashMap<String, String> hashMap = new ConcurrentHashMap<String, String>();
		// 2  添加数据
		for (int i = 0; i < 3; i++) {
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					for (int k = 0; k < 5; k++) {
						hashMap.put("【" + Thread.currentThread().getId() + "|" + k + "】" , k + "");						
					}	
				}
			}).start();	
		}
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(hashMap);
	}
}

Queue接口(队列)

Collection的子接口,表示队列FIFO(先进先出)

常用方法:
抛出异常:
boolean add(E e)	// 顺序添加一个元素(到达上限后,再添加则会抛出异常)
E remove()	// 获得第一个元素并移除(如果队列没有元素时,则抛出异常)
E element()	// 获得第一个元素但不移除(如果队列没有元素时,则抛出异常)
返回特殊值*:
boolean offer(E e)	// 顺序添加一个元素(到达上限后,再添加则会返回false)
E poll()	// 获得第一个元素并移除(如果队列没有元素时,则返回null)
E peek()	// 获得第一个元素但不移除(如果队列没有元素时,则返回null)
【例】
public class DemoQueue {
	public static void main(String[] args) {
		// 1 创建队列
		Queue<String> queue = new LinkedList<String>();
		// 2 入队
		queue.offer("榴莲");
		queue.offer("椰子");
		queue.offer("西瓜");
		queue.offer("葡萄");
		// 3 出队
		System.out.println(queue.peek());
		System.out.println("==============");
		System.out.println("元素个数:"+ queue.size());
		System.out.println(queue.poll());
		System.out.println("==============");
		for (String string : queue) {
			System.out.println(string);
		}
		System.out.println("出队完毕");
	}
}

ConcurrentLinkedQueue

  • 线程安全、可高效读写的队列,高并发下性能最好的队列。
  • 无锁、CAS比较交换算法,修改的方法包含三个核心参数 (V,E,N)
  • V:要更新的遍历,E:预期值,N:新值
  • 只有当V==E时,V=N;否则表示已被更新过,则取消当前操作。
public class TestConcurrentLinkedQueue {
	public static void main(String[] args) {
		Queue<String> queue = new ConcurrentLinkedQueue<String>();
		queue.offer("Hello");	// 插入
		queue.offer("Hello");	// 插入
		queue.poll("Hello");	// 删除Hello
		queue.peek("Hello");	// 获得World
	}
}

BlockingQueue接口(阻塞队列)

Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。

方法:
void put(E e)	// 将指定元素插入此队列中,如果没有可用空间,则等待
E take()	// 获取并移除此队列头部元素,如果没有可用元素,则等待

可用于解决生产者、消费者问题

阻塞队列

  • ArrayBlockingQueue:

    • 数组结构实现,有界队列。(手工固定上限)

    • public class TestArrayBlockingQueue {
      	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> lbq = new LinkedBlockingQueue<String>();
      	}
      }
      
【例】包子铺 和 吃货 (阻塞队列实现)
import java.util.concurrent.ArrayBlockingQueue;

public class DemoArrayBlockingQueue {

	public static void main(String[] args) {
		// 1 创建队列
		final ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(6);
		// 2 创建两个线程
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 1; i <= 30; i++) {
					try {
						queue.put(i-1);
						System.out.println(Thread.currentThread().getName() + "生产了第" + i + "个包子");
						Thread.sleep(100);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}		
			}
		}, "包子铺");
		Thread t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 1; i <= 30; i++) {
					try {
						Integer num = queue.take();
						System.out.println(Thread.currentThread().getName() + "吃了第" + i + "个包子");
						Thread.sleep(100);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}		
			}
		}, "吃货");
		t1.start();
		t2.start();
	}
}

总结

  • ExecutorService线程池接口、Executors工厂。
  • Callable线程任务、Future异步返回值。
  • Lock、ReentrantLock重入锁、ReentrantReadWriteLock读写锁。
  • CopyOnWriteArrayListt线程安全的ArrayList。
  • CopyOnWriteArraySet线程安全的Set。
  • ConcurrentHashMap线程安全的HashMap。
  • ConcurrentLinkedQueue线程安全的Queue。
  • ArrayBlockingQueue线程安全的阻塞Queue。(生产者、消费者)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值