本人邮箱: [email protected]
欢迎转载,转载请注明网址 http://blog.csdn.net/tianshi_kco
github: https://github.com/kco1989/kco
代码已经全部托管github有需要的同学自行下载
做java的同学们或多或少的接触过Java集合框架.在java集合框架中,大多的集合类是线程不安全的.比如我们常用的ArrayList
等等.我们写一个例子看,为什么说ArrayList
是不安全的.
我们开启100个线程.每个线程向List
加100
个数据,那么当所有线程执行完成之后应该是10000
条,然后就对比一下结果,看看是否为10000
条.
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
List list = new ArrayList<>();
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i ++){
threads[i] = new Thread(() -> {
for (int j = 0;j < 100; j ++){
list.add(Thread.currentThread().getName() + ":" + j);
}
});
threads[i].start();
}
for (Thread thread : threads){
thread.join();
}
System.out.println(list.size());
}
}
thread.join();
是让主线程等待所有的子线程执行完,才执行接下来的语句
运行结果为9979
,而且每次运行结果还不一定是这个数.
当然,我们可以通过学过的知识,在执行list.add
是给它加锁,比如将list.add(Thread.currentThread().getName() + ":" + j);
改为synchronized (list){list.add(Thread.currentThread().getName() + ":" + j);}
这样就能保证线程同步了.
其实java中已经提供了可直接用于并发的集合类,它们可以在多线程中进行CURD
1操作,而且不需要程序员手动加lock
或synchronized
来保证同步.一般来说,它们分以下两种:
Null
或抛出异常,但调用该方法的线程不会被阻塞.这节课我将重点讲ArrayBlockingQueue
,首先先看一下ArrayBlockingQueue
的api,以及区分这些的差别
add(E)
,offer(E)
,pub(E)
都是这队列尾部加入元素E,如果队列不满,则加入成功,并立即返回.如果队列满了,那么
add
会抛出IllegalStateException
异常offer
立刻返回false
put
会让调用的线程一直等待,直到方法执行成功offer(E e, long timeout, TimeUnit unit)
,offer
另一种方法,当集合已满,如果等待时间小于等于0,那么会离开返回false,否则等到指定的时间
poll()
,take()
,获取队列的数据,如果队列为空,那么
poll
立刻返回nulltake
线程等待,直到获取到数据,或被中断poll(long timeout, TimeUnit unit)
,如队列为空,当指定时间小于等于,立刻返回null,否则等待指定的时间
peek()
: 看一下队列当前的数据,如果队列为空,则立即返回null
之前我们写过山治和路飞的故事,在(十二)java多线程之Exchanger的例子中,其实山治和路飞是一个简单的生产者-消费者模式,只是山治和路飞都要等对方吃完或做完一个才能继续下一个.现在路飞想出另一个办法,在厨房和餐桌之间弄一个传送带,山治把食物做好之后,直接放传送带上,路飞就直接从传送带拿食物.传送带最多只能放10个食物.ok,开始编码..
Food
没有改.LuFeiRunnable
改为public class LuFeiRunnable implements Runnable{
ArrayBlockingQueue queue;
Random random = new Random();
public LuFeiRunnable(ArrayBlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true){
try {
String take = queue.take();
System.out.println("-->路飞拿到 " + take);
Thread.sleep(random.nextInt(500));
System.out.println("-->路飞吃完 " + take);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
ShanZhiRunnable
改为public class ShanZhiRunnable implements Runnable{
ArrayBlockingQueue queue;
Random random = new Random();
public ShanZhiRunnable(ArrayBlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true){
try {
String food = Food.getRandomFood();
System.out.println("==>山治开始做 " + food);
Thread.sleep(random.nextInt(500));
System.out.println("==>山治做好了 " + food);
queue.put(food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TestMain {
public static void main(String[] args) {
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(10);
new Thread(new LuFeiRunnable(queue)).start();
new Thread(new ShanZhiRunnable(queue)).start();
}
}
运行结果:
==>山治开始做 椒麻鸡
==>山治做好了 椒麻鸡
==>山治开始做 牛腩煲
-->路飞拿到 椒麻鸡
==>山治做好了 牛腩煲
==>山治开始做 豆苗炒虾片
==>山治做好了 豆苗炒虾片
==>山治开始做 火鞭牛肉
-->路飞吃完 椒麻鸡
-->路飞拿到 牛腩煲
==>山治做好了 火鞭牛肉
==>山治开始做 桃酥鸡糕
-->路飞吃完 牛腩煲
-->路飞拿到 豆苗炒虾片
==>山治做好了 桃酥鸡糕
==>山治开始做 鸡汤煮干丝
-->路飞吃完 豆苗炒虾片
-->路飞拿到 火鞭牛肉
==>山治做好了 鸡汤煮干丝
==>山治开始做 菊花猪肝汤
-->路飞吃完 火鞭牛肉
-->路飞拿到 桃酥鸡糕
==>山治做好了 菊花猪肝汤
==>山治开始做 清香炒悟鸡
-->路飞吃完 桃酥鸡糕
-->路飞拿到 鸡汤煮干丝
-->路飞吃完 鸡汤煮干丝
-->路飞拿到 菊花猪肝汤
==>山治做好了 清香炒悟鸡
==>山治开始做 槐花猪肠汤
-->路飞吃完 菊花猪肝汤
-->路飞拿到 清香炒悟鸡
-->路飞吃完 清香炒悟鸡
==>山治做好了 槐花猪肠汤
==>山治开始做 瑞士排骨
-->路飞拿到 槐花猪肠汤
-->路飞吃完 槐花猪肠汤
==>山治做好了 瑞士排骨
-->路飞拿到 瑞士排骨
==>山治开始做 芝麻鱼球
-->路飞吃完 瑞士排骨
山治和路飞都很happy,各自都在做自己喜欢做的事情,但是,如果路飞吃到一定阶段,吃不下了,会发生什么呢?比如把LuFeiRunnable
中的Thread.sleep(random.nextInt(500));
改为Thread.sleep(random.nextInt(5000));
,然后运行结果,会发现山治做菜速度也变慢了,因为传送带的食物放不下,山治必须等路飞吃掉一些,这样传送带才能放下食物.
这就想我们去银行排队,医院取号,或者去各种部门排队办业务的时候.如果人来的太多了,那么他们一般的做法就是没号,要么在网上预约,要么下次早点来.这就是生产者(排队的人)生产太快,消费者(银行,医院等)消费太慢了
ArrayBlockingQueue
的父类是BlockingQueue
,通过查找BlockingQueue
的子类,我们能找到以下这几个类.
* SynchronousQueue
跟ArrayBlockingQueue
类似,只不过它除了可以用put
,offer
,take
,add
,poll
,take
这几个方法外,其余的例如isEmpty
,size
,clear
,contains
,remove
等等,都是无效的,大部分都是直接返回一个固定值.这是因为它是一个没有容量的队列.甚至连一个容量都没有.因此在每次做插入操作的时候,都必须等其他线程做删除操作.
* LinkedBlockingQueue
跟ArrayBlockingQueue
类似,只是ArrayBlockingQueue
是通过数组的方式实现队列,而LinkedBlockingQueue
是通过列表的方式实现队列.
* LinkedBlockingDeque
跟LinkedBlockingQueue
一样是用链表实现队形,只是LinkedBlockingDeque
为双向链表,可以在头部或尾部进行添加和删除操作.
+ add*
,offer*
,put*
这些增加操作跟LinkedBlockingQueue
和LinkedBlockingQueue
的add
,offer
,put
是类似的,如果这些方法不带*
,则都是等价与*Last
+ poll*
,take*
这些获取数据操作跟LinkedBlockingQueue
和LinkedBlockingQueue
的poll
,take
类似的,如果不带*
,则等价于*Frist
在学习中,我们要能举一反三.这样我们就会学的比较快.
如果觉得我的文章写的还过得去的话,有钱就捧个钱场,没钱给我捧个人场(帮我点赞或推荐一下)