1、线程池
I. 线程容器,可设定线程分配的数量上限
II. 将预先创建的线程对象存入池中,实现重用池中的线程对象
III. 避免频繁的创建和销毁
2、线程池原理
I. 将任务提交给线程池,由线程池分配线程、运行任务。并在结束任务后,复用线程。
3、获取线程池
I. Executor 线程池定级接口
II. ExecutorService 线程池接口,可通过submit(Runnable task) 提交任务代码
III. Executors工厂类:可以获得一个线程池
(1) . newFixedThreadPool(int nThreads) 获取固定数量线程池
(2) newCachedThreadPool() 获取动态数量线程池。
public class TestThreadPool {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//线程池(引用)---------工具类(工厂类)
ExecutorService es2 = Executors.newFixedThreadPool(3);
MyTask1 mt1 = new MyTask1(); //创建任务
MyTask2 mt2 = new MyTask2();
Future<Integer> result1 = es2.submit(mt1); //任务传递给线程池,返回future对象
Future<Integer> result2 = es2.submit(mt1);
Integer value1 =result1.get(); //拿到返回值
Integer value2 =result2.get();
Integer value = value1 + value2;
System.out.println(value);
}
}
4、Callable接口
I. JDk5加入,与Runnable接口类似,实现之后代表一个线程任务
II. Call方法具有泛型返回值,可以声明异常
class MyTask1 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
Integer sum = 0;
for(int i = 51;i<=100;i++) {
sum = sum + i;
}
return sum;
}
}
5、Future接口
I. 异步接收ExecutorService.submit()所返回的状态结果,当中包含了call()方法的返回值
II. V get()以阻塞形式等待Future中的异步处理结果
6、同步
I. 形容一次方法调用,同步一旦开始,调用者必须等待该方法返回,才能继续
II. 单条执行路径
7、异步
I. 形容一次方法调用,异步一旦开始,像是一次消息传递,调用者告知后立刻返回,二者竞争时间片,并发执行
II. 多条执行路径
8、Lock接口
I. JDK5加入,与synchronized比较,显示定义,结构更灵活
II. 提供更多实用性方法,功能更强大,性能更优越
Lock lock = new ReentrantLock();
//第一、使用Lock,需要明确的写上锁和释放锁
//第二、为了避免拿到锁的线程出现异常没有释放锁,需要在finally来保证释放
public void method() {
System.out.println(Thread.currentThread().getName()+"进入到上锁的方法里!");
try {
lock.lock(); //上锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"退出了上锁的方法");
}finally {
lock.unlock(); //解锁
}
}
9、重入锁
I. ReentrantLock:Lock接口的实现类,与synchronized一样具有互斥锁功能
10、读写锁
I. ReentrantReadWriteLock:一种支持一写多读的同步锁,读写分离,可分别分配读锁、写锁
II. 支持多次分配读锁,多个读操作可以并发执行
III. 互斥规则:
(1) .写-写:互斥,阻塞
(2) 读-写:互斥,读阻塞写、写阻塞读
(3) 读-读:不互斥,不阻塞
(4) 在读操作远远大于写操作的环境中,可在保障线程安全的情况下,提高运行效率
class Student{
private int age;
ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock(); //读写锁
ReadLock rl = rrwl.readLock();
WriteLock wl = rrwl.writeLock();
public void setAge(int age) {
wl.lock();
try {
this.age = age;
}finally {
wl.unlock();
}
}
public int getAge() {
rl.lock();
try {
return this.age;
}finally {
rl.unlock();
}
}
}
11、线程安全的集合:
List<String> list = new ArrayList<String>();
List<String> list1 = Collections.synchronizedList(list);
CopyOnWriteArrayList
CopyOnWriteArraySet
ConcurrentHashMap
public static void main(String[] args) {
//写有锁,读无锁的集合
List<String> alist = new CopyOnWriteArrayList<String>();
alist.add("A"); //每调用一次,底层方法扩容一次
alist.add("B");//每次将底层数组做了一次复制,新数组赋值后,将新数组替换掉旧数组
alist.get(1);
Set<String> aset = new CopyOnWriteArraySet<String>();
aset.add("a");//表面使用的是add方法,实际底层用的CopyOnWriteArrayList的addIfAbsent()来判断要插入的新值是否存在
aset.add("b");
for (String string : aset) {
System.out.println(string);
}
//分段锁设计 Segment JDK1.7的做法
//CAS交换算法和同步锁 同步锁锁的是表头对象,拿到的锁的对象要先做结点遍历。查看有没有相同的key,相同覆盖,不同,则挂在最后一个节点的next上
Map<String,String> ch = new ConcurrentHashMap<String,String>();
ch.put("a", "ww");
System.out.println(ch.keySet());
}
public class ConcurrentHashMap<K, V> extends AbstractMap<K, V>
implements ConcurrentMap<K, V>, Serializable {
// 将整个hashmap分成几个小的map,每个segment都是一个锁;与hashtable相比,这么设计的目的是对于put, remove等操作,可以减少并发冲突,对
// 不属于同一个片段的节点可以并发操作,大大提高了性能
final Segment<K,V>[] segments;
// 本质上Segment类就是一个小的hashmap,里面table数组存储了各个节点的数据,继承了ReentrantLock, 可以作为互拆锁使用
static final class Segment<K,V> extends ReentrantLock implements Serializable {
transient volatile HashEntry<K,V>[] table;
transient int count;
}
// 基本节点,存储Key, Value值
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
}
}
JDK1.8版本:做了2点修改,见下图:
取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,并发控制使用Synchronized和CAS来操作
将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构.
queue
ConcurrentLiskedQueue
//线程安全,采用cas交换算法
Queue<String> q = new ConcurrentLinkedQueue<String>();
q.offer("A");
q.offer("B");
q.offer("c");
System.out.println(q.peek());
阻塞队列
//手动固定队列上限
BlockingQueue<String> bq = new ArrayBlockingQueue<>(10);
//无界队列 最大有Integer.MAX_VALUE
BlockingQueue<String> lbq = new LinkedBlockingQueue<String>();
讲 interrupt() 方法:
interrupt() 它基于「一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。」思想,是一个比较温柔的做法,它更类似一个标志位。其实作用不是中断线程,而是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。
interrupt 中断操作时,非自身打断需要先检测是否有中断权限,这由jvm的安全机制配置;
如果线程处于sleep, wait, join 等状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常;
如果线程处于I/O阻塞状态,将会抛出ClosedByInterruptException(IOException的子类)异常;
如果线程在Selector上被阻塞,select方法将立即返回;
如果非以上情况,将直接标记 interrupt 状态;
中断的相关方法
public void interrupt()
将调用者线程的中断状态设为true。
public boolean isInterrupted()
判断调用者线程的中断状态。
public static boolean interrupted
只能通过Thread.interrupted()调用。
它会做两步操作:
返回当前线程的中断状态;
将当前线程的中断状态设为false;