二十一、高级多线程

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;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值