线程高级

一、锁的介绍

重入锁

昨天讲解了同步锁(同步代码块和同步方法);相比同步锁,重入锁的执行性能会更高,因为重入锁是手动进行加锁和释放锁,灵活性更强;但是重入锁是手动处理锁,容易出现死锁,需要谨慎使用。

应用:在使用上这些锁没有区别,锁的注意事项也是一致的

案例:模拟List的安全隐患问题,以及处理隐患。

//模拟List集合,使用重入锁处理---ReentrantLock
class MyList{
	Lock lock = new ReentrantLock(); //重入锁
	String[] a = {null,null};
	int index;   //记录下标,从0开始
	public void add(String value) {
		try {
			lock.lock();  //获取锁对象
			a[index] = value;
			index++;
		} finally { //无论如何都会释放
			lock.unlock();  //释放锁对象
		}
	}
}
public class Test1 {
	public static void main(String[] args) throws InterruptedException {
		MyList list = new MyList();
		Thread th1 = new Thread(new Runnable() {
			@Override
			public void run() {
				list.add("hello");
			}
		});
		th1.start();
		Thread th2 = new Thread(new Runnable() {
			@Override
			public void run() {
				list.add("world");
			}
		});
		th2.start();
		th1.join();  th2.join();
		
		System.out.println(Arrays.toString(list.a));
	}
}

读写锁

读写锁:ReentrantReadWriteLock (了解)

描述:将读锁和写锁进行分离,读与读之间不阻塞,读与写会阻塞,写与写也会阻塞

场景:只有读远远高于写的情况,才使用读写锁

后面线程安全中,会有并发集合(并发读和写,读不加锁,性能更高)的使用可以取代读写锁

什么是读? 什么是写? 读—-打印共享数据,获取值(查询); 写—-存储,修改,删除

//准备了20个线程---18个进行读,2个进行写
ExecutorService es = Executors.newFixedThreadPool(20);
long start = System.currentTimeMillis();
//20个线程进行读和写...
es.shutdown();  //关闭线程池
while(!es.isTerminated());  //类似join,阻塞,等待子线程都执行完主线程才能会自行
//下面的主线程操作需要等到20个子线程都执行完才能统计最终时间
System.out.println(System.currentTimeMillis()-start);

二、线程安全集合

Collections同步

//线程安全的集合: Collections创建同步锁(了解),直接加的锁,没有性能的优化提升
List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
for(int i=1;i<=5;i++) {
    final int t = i; //需要重新赋值,在匿名内部类中打印;
    new Thread(new Runnable() {
        @Override
        public void run() {
            list.add(t);//因为i会变更,这里不能打印i  写

            //System.out.println(list);  //读  不是并发读写,可能会出现异常
        }
    }).start();
}

并发ArrayList

//并发集合:CopyOnWriteArrayList,可以并发的读和写
//原理:在内部拷贝了一份副本进行写;可以在原始的数组中读;即可做到读写分离
//读不加锁,写有锁;读写之间不互斥;只有写与写会阻塞
//相对Collections的同步锁,性能会更高,比读写锁也更高
List<Integer> list = new CopyOnWriteArrayList<Integer>();
for(int i=1;i<=5;i++) {
    final int t = i;
    new Thread(new Runnable() {
        @Override
        public void run() {
            list.add(t);  //写

            System.out.println(list);  //读
        }
    }).start();
}

结论:最终确保5个线程,每个线程存数据的位置是不同的

并发ArraySet

底层通过并发的ArrayList实现的,只不过会判断存储元素是否相等,如果相等,则丢弃副本

存储特点:存储的元素唯一

Set<String> set = new CopyOnWriteArraySet<String>();
set.add("zs");
set.add("ls");
set.add("zs");
System.out.println(set);

并发的HashMap

描述:内部的hash表进行了锁分段机制,分了16段;每个hash表位置进行加锁。

理论上可以16个线程同时并发的写

Map<String, Integer> map = new ConcurrentHashMap<String, Integer>();
for(int i=1;i<=5;i++) {
    int t = i;
    new Thread(new Runnable() {
        @Override
        public void run() {
            map.put("线程"+t, t); //写

            System.out.println(map); //读
        }
    }).start();
}

三、并发的队列

队列与集合是类似的,也是个容器,底层也可以通过数组或链表实现队列的功能

特点:先进先出

接口: Queue队列接口

ConcurrentLinkedQueue

该队列是并发队列中,性能最高的队列;因为没有加锁

ConcurrentLinkedQueue实现类:内部通过CAS无锁交换算法进行线程安全的处理

//ConcurrentLinkedQueue实现类:内部采用CAS无锁交换算法-没有加锁也能确保安全
//队列的特点,先进先出
//内部实现:维护了三个参数   V-要替换的值   E-预期值     N-新值
//如果在执行中一直能匹配V==E;则新值可以替换旧值V=N;说明没有人更改过值,即可存进来;
//否则,舍弃N的存储
//CAS的三个参数的判断是基于原子性操作
Queue<String> queue = new ConcurrentLinkedQueue<String>();
queue.add("zs");
queue.add("ls");
//前面没有add元素,则后面的移除和获取都会报异常
System.out.println(queue.remove()); //返回并移除  移除zs
//System.out.println(queue.element()); //返回并不移除
System.out.println(queue); 

Queue<String> queue2 = new ConcurrentLinkedQueue<String>();
//queue2.offer("zs");
//queue2.offer("ls");
//没有存元素,不会报错,获取null
//System.out.println(queue2.poll()); //返回并移除  移除zs
System.out.println(queue2.peek()); //返回并不移除
System.out.println(queue2);

四、总结与作业

总结

1.锁的介绍
重入锁:与同步锁的区别;特点-手动操作锁(重点)
了解读写锁,读写分离-读与读不阻塞
2.线程安全集合
Collections同步锁(了解-性能低)
CopyOnWriteArrayList-并发读写的ArrayList,读无锁,读写之间不阻塞(重点)
CopyOnWriteArraySet-底层由CopyOnWriteArrayList完成,自身特点-存储唯一性
ConcurrentHashMap-并发HashMap,采用锁分段机制(锁hash表位置)-可以并发写(重点)
3.并发队列
队列接口:Queue
实现类:ConcurrentLinkedQueue-性能队列中最高
内部原理:CAS无锁交换算法;原子性判断

作业

1.理解安全锁:重入锁、同步代码块、同步方法的区别
2.理解线程安全集合:并发ArrayList,并发ArraySet,并发HashMap
3.理解线程安全队列: 并发队列(CAS算法)
提示:今天作业不交