java中的线程高级

目录

一、锁的介绍

1.重入锁

2.读写锁

二、线程安全集合

1.Collection同步

2.并发ArrayList

3.并发ArraySet

4.并发的HashMap

三、并发的队列

1.ConcurrentLinkedQueue

四、阻塞队列

BlockingQueue

模拟生产者消费者

五、总结



一、锁的介绍

1.重入锁

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

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

案例:模拟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));
	}
}

2.读写锁

读写锁: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);

二、线程安全集合

1.Collection同步

线程安全的集合的案例:

//线程安全的集合: 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 写;
                           //但是我可以通过t,虽然t也是final不可变的,
                           //但是每次循环的时候我都创建一个新的t,并不是改变原来的t

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

2.并发ArrayList

//并发集合:CopyOnWriteArrayList,可以并发的读和写
//原理:在内部拷贝了一份副本进行写;可以在原始的数组中读;即可做到读写分离
//读不加锁,写有锁;读写之间不互斥;只有写与写会阻塞
//相对Collections的同步锁,性能会更高,比读写锁也更高


并发ArrayList案例:

读不加锁,写加锁,所以读写不互斥      只有写写互斥

所以开启五个线程 每个线程都有可能其他线程在写完成还未读的时候,进行读写,因为读写不互斥,因为这五个线程是给cpu调度的,具体谁先执行这是无法预估的

但是在写未结束的时候,其他线程不可写

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个线程,每个线程存数据的位置是不同的

3.并发ArraySet

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

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

简单使用:

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

4.并发的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队列接口  

1.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);

四、阻塞队列

BlockingQueue

是Queue的子接口,有两个实现类:ArrayBlockingQueue何LinkedBlockingQueue

ArrayBlockingQueue是有界队列;队列中存储的元素可以限制(推荐)

LinkedBlockingQueue是无界队列;队列中存储的元素无限制,可以一直存储

阻塞队列中提供了存储阻塞的方法:take和put

案例:

//阻塞队列接口:BlockingQueue; 两个实现类:有界和无界队列
BlockingQueue<String> queue = new ArrayBlockingQueue<String>(3);
queue.put("zs");  //存储-超过个数则阻塞
queue.put("zs");
queue.put("ww");
//queue.put("zl");
System.out.println(queue);
System.out.println(queue.take());  //获取,
System.out.println(queue.take());  //获取,
System.out.println(queue.take());  //获取,
System.out.println(queue.take());  //获取,超过了,则阻塞

模拟生产者消费者

//通过阻塞队列模拟生产者和消费者模型
BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(5);
new Thread(new Runnable() {
    @Override
    public void run() {
        //一直负责生产的线程
        for(int i=1;i<=100;i++) {
            try {
                queue.put(i);
            } catch (InterruptedException e) {}
            System.out.println("正在生产第"+i+"件货");
        }
    }
}).start();
new Thread(new Runnable() {
    @Override
    public void run() {
        //一直负责消费的线程
        for(int i=1;i<=100;i++) {
            try {
                System.out.println("正在消费第"+queue.take()+"件货");
            } catch (InterruptedException e) {}
        }
    }
}).start();

五、总结

总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值