线程池概念
• 现有问题:
• 线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。
• 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。
• 线程池:
• 线程容器,可设定线程分配的数量上限。
• 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
• 避免频繁的创建和销毁。
线程池原理
将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程。
获取线程池
• 常用的线程池接口和类(所在包java.util.concurrent):
• Executor:线程池的顶级接口。
• ExecutorService:线程池接口,可通过submit(Runnable task) 提交任务代码。
• Executors工厂类:通过此类可以获得一个线程池。
• 通过 newFixedThreadPool(int nThreads) 获取固定数量的线程池。
参数:指定线程池中 线程的数量。
• 通过newCachedThreadPool() 获得动态数量的线程池,如不够则创建新的,没有上限
Callable接口
public interface Callable<V>{
public V call() throws Exception;
}
• JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
• Callable具有泛型返回值、可以声明异常
Future接口
• 概念:
异步接收ExecutorService.submit()所返回的状态结果,当中包含了 call()的返回值
• 方法:
V get()以阻塞形式等待Future中的异步处理结果(call()的返回值)
线程的同步、异步
• 同步:
• 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续。
• 注:单条执行路径。
•异步:
• 形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知之后立刻返回。 二者竞争时间片,并发执行。
• 注:多条执行路径。
Lock接口
• JDK5加入,与synchronized比较,显示定义,结构更灵活。
• 提供更多实用性方法,功能更强大、性能更优越。
• 常用方法:
void lock() //获取锁, 如锁被占用,则等待。
boolean tryLock() //尝试获取锁( 成功返回true。失败返回false,不阻塞)
void unlock() //释放锁
重入锁
• ReentrantLock:
Lock接口的实现类,与synchronized一样具有互斥锁功能
读写锁
• ReentrantReadWriteLock:
• 一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁。
• 支持多次分配读锁,使多个读操作可以并发执行。
• 互斥规则:
• 写-写:互斥,阻塞。
• 读-写:互斥,读阻塞写、写阻塞读。
• 读-读:不互斥、不阻塞。
• 在读操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
public class TestReadWriteLock {
public static void main(String[] args) {
Student stu = new Student();//共享资源对象
ExecutorService es = Executors.newFixedThreadPool(20);
WriteTask1 write = new WriteTask1(stu);//写线程任务
ReadTask read = new ReadTask(stu);//读线程任务
//执行的两个赋值的线程任务
long start = System.currentTimeMillis();//开始时间 毫秒值
es.submit(write);
es.submit(write);
for(int i =1;i<=18;i++) {
es.submit(read);
}
//停止线程池,但是不停止已提交的任务!等已提交任务都执行完毕!
es.shutdown();
//询问线程池,任务结束了吗?
while(true) {
System.out.println("结束了吗?");
if(es.isTerminated() == true) {//就证明线程池里的任务都执行完毕了!
break;
}
}
long end = System.currentTimeMillis();//结束时间
System.out.println(end - start);
}
}
//写操作任务
class WriteTask1 implements Callable{
Student stu;
public WriteTask1(Student stu) {
this.stu = stu;
}
@Override
public Object call() throws Exception {
stu.setAge(100);
return null;
}
}
//读操作任务
class ReadTask implements Callable{
Student stu;
public ReadTask(Student stu) {
this.stu = stu;
}
@Override
public Object call() throws Exception {
stu.getAge();
return null;
}
}
class Student{
private int age;
// Lock lock = new ReentrantLock();//写和读的操作下,都加锁!性能过低!
ReentrantReadWriteLock rrwl = new
ReentrantReadWriteLock();//有两把锁
ReadLock read = rrwl.readLock();//读锁
WriteLock write = rrwl.writeLock();//写锁
//赋值---写操作!
public void setAge(int age) {
write.lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.age = age;
}finally {
write.unlock();
}
}
//取值---读操作
public int getAge() {
read.lock();
try {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return this.age;
}finally {
read.unlock();
}
}
}
线程安全的集合
• Collection体系集合下,除Vector以外的线程安全集合。
Collections中的工具方法
• Collections工具类中提供了多个可以获得线程安全集合的方法。
• public static < T > Collection< T > synchronizedCollection(Collection< T >c)
//返回指定 collection 支持的同步(线程安全的)collection
• public static < T > List< T > synchronizedList(List< T > list)
//返回指定 collection 支持的同步(线程安全的)collection
• public static < T > Set< T > synchronizedSet(Set< T > s)
//返回指定 set 支持的同步(线程安全的)set
• public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
//返回由指定映射支持的同步(线程安全的)映射
• public static < T> SortedSet< T > synchronizedSortedSet(SortedSet< T > s)
//返回指定有序 set 支持的同步(线程安全的)有序 set
• public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
//返回指定有序映射支持的同步(线程安全的)有序映射
• JDK1.2提供,接口统一、维护性高,但性能没有提升,均以synchonized实现。
CopyOnWriteArrayList
• 线程安全的ArrayList,加强版读写分离。
• 写有锁,读无锁,读写之间不阻塞,优于读写锁。
• 写入时,先copy一个容器副本、再添加新元素,最后替换引用。
• 使用方式与ArrayList无异。
public class TestCopyOnWriteArrayList {
public static void main(String[] args) {
//写有锁, 读无锁的集合。
CopyOnWriteArrayList<String> alist = new CopyOnWriteArrayList<String>();
//写操作 有锁
alist.add("A");//都将底层数组做了一次复制,写的是新数组,完成赋值后,再将新数组替换掉旧数组!
alist.add("B");//每调用一次,底层方法扩容一次!
//读操作 无锁
alist.get(1);//读的是写操作完成之前的旧数组!写完之后,才能读到的新数组的新值
}
}
CopyOnWriteArraySet
• 线程安全的Set,底层使用CopyOnWriteArrayList实现。
• 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组,
• 如存在元素,则不添加(扔掉副本)。
public class TestCopyOnWriteArraySet {
public static void main(String[] args) {
//无序、无下标、不允许重复
CopyOnWriteArraySet<String> aset = new CopyOnWriteArraySet<String>();
//写操作 表面使用的是add方法,底层实际是用的CopyOnWriteArrayList的 addIfAbsent()来判断要插入的新值是否存在
aset.add("A");
aset.add("B");
aset.add("C");
for(String s : aset) {
System.out.println(s);
}
}
}
ConcurrentHashMap
• 初始容量默认为16段(Segment),使用分段锁设计。
• 不对整个Map加锁,而是为每个Segment加锁。
• 当多个对象存入同一个Segment时,才需要互斥。
• 最理想状态为16个对象分别存入16个Segment,并行数量16。
• 使用方式与HashMap无异。
public class TestConcurrentHashMap {
public static void main(String[] args) {
HashMap<String,String> maps = new HashMap<String,String>();
/*分段锁设计 Segment JDK1.7的做法
CAS交换算法和同步锁
同步锁锁的是表头对象,拿到锁的对象要先做节点遍历。
查看有没有相同的key,相同覆盖;不同--则挂在最后一个节点的next上 JDK1.8的做法*/
ConcurrentHashMap<String, String> ch = new ConcurrentHashMap<String, String>();
ch.put("A","蓝轩");
ch.keySet();
}
}
Queue接口(队列)
• Collection的子接口,表示队列FIFO(First In First Out)
• 常用方法:
• 抛出异常:
• boolean add(E e) //顺序添加一个元素(到达上限后,再添加则会抛出异常)
• E remove() //获得第一个元素并移除(如果队列没有元素时,则抛异常)
• E element() //获得第一个元素但不移除(如果队列没有元素时,则抛异常)
• 返回特殊值:推荐使用
• boolean offer(E e) //顺序添加一个元素 (到达上限后,再添加则会返回false)
• E poll() //获得第一个元素并移除 (如果队列没有元素时,则返回null)
• E keep() //获得第一个元素但不移除 (如果队列没有元素时,则返回null)
ConcurrentLinkedQueue
• 线程安全、可高效读写的队列,高并发下性能最好的队列。
• 无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
• V:要更新的变量、E:预期值、N:新值。
• 只有当V==E时,V=N;否则表示已被更新过,则取消当前操作。
BlockingQueue接口(阻塞队列)
• Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法。
• 方法:
• void put(E e) //将指定元素插入此队列中,如果没有可用空间,则等待。
• E take() //获取并移除此队列头部元素,如果没有可用元素,则等待。
• 可用于解决生产生、消费者问题。
阻塞队列
• ArrayBlockingQueue: 数组结构实现,有界队列。(手工固定上限)
• LinkedBlockingQueue:
• 链表结构实现,无界队列。(默认上限Integer.MAX_VALUE)
public class TestQueue {
public static void main(String[] args) {
// Queue<String> q = new
//列表,尾部添加(指定下标)
//链表,头尾添加
//队列,FIFO
//如果要强制LinkedList只遵循队列的规则!
// Queue<String> link = new LinkedList<String>(); //遵循队列规则的链表
LinkedList<String> link = new LinkedList<String>();
link.offer("A");
link.offer("B");
link.offer("C");
//用列表的方式打乱了FIFO队列的规则
// link.add(0,"D");
//强制LinkedList后,不能调用带有下标的add方法
System.out.println(link.peek());//队列中的第一个元素!
//严格遵守了队列的规则,且是线程安全的,采用了CAS交换算法
Queue<String> q = new ConcurrentLinkedQueue<String>();
//1.抛出异常的 2.返回结果的
q.offer("A");
q.offer("B");
q.offer("C");
q.poll();//删除表头
System.out.println(q.peek());//获得表头
//手动固定队列上限
BlockingQueue<String> bq = new ArrayBlockingQueue<String>(3);
//无界队列 最大有 Integer.MAX_VALUE
BlockingQueue<String> lbq = new LinkedBlockingQueue<String>();
}
}