例子:卖火车票
/**
* 有N张火车票,每张火车票都有一个编号
* 同时有10个窗口对外售票
* 模拟程序
*
* 第一个程序会重复销售, 超量销售
* 第二个程序使用Vector 是个同步容器, 所有方法都是加锁的, 还是有问题, 判断size()和同步remove(0方法是分离的,中间还是有问题。
*/
public class TicketSeller2 {
//Vector 是个同步容器, 所有方法都是加锁的
static Vector<String> tickets = new Vector<>();
static {
for (int i = 0; i < 10000; i++) {
tickets.add("票编号: "+i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
while (tickets.size() >0){
try {
TimeUnit.MILLISECONDS.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("销售了---"+tickets.remove(0));
}
}).start();
}
}
}
/**
* 第一个程序会重复销售, 超量销售
* 第二个程序使用Vector 是个同步容器, 所有方法都是加锁的, 还是有问题, 判断size()和同步remove(0方法是分离的,中间还是有问题。
* 第三个程序改用LinkedList 但是每次售卖的时候,tickets对象加把锁,锁住了真个队列。效率低下
*/
public class TicketSeller3 {
//Vector 是个同步容器, 所有方法都是加锁的
static List<String> tickets = new LinkedList<>();
static {
for (int i = 0; i < 10000; i++) {
tickets.add("票编号: "+i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
while (true){
synchronized (tickets){
if(tickets.size() <= 0){
break;
}
try {
TimeUnit.MILLISECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("销售了---"+tickets.remove(0));
}
}
}).start();
}
}
}
/*
* 第一个程序会重复销售, 超量销售
* 第二个程序使用Vector 是个同步容器, 所有方法都是加锁的, 还是有问题, 判断size()和同步remove(0方法是分离的,中间还是有问题。
* 第三个程序改用LinkedList 但是每次售卖的时候,tickets对象加吧锁,锁住了真个队列。效率低下
* 第四个程序改用并发容器Queue队列。 并发容器:ConcurrentLinkedQueue 并发的链表实现的Queue 支持多线程的。 这里是没有问题的。 因为判断后面没有再做修改队列的操作。
* poll操作的底层实现是cas .不是加锁的实现, 效率会高很多。
*/
public class TicketSeller4 {
//Vector 是个同步容器, 所有方法都是加锁的
static Queue<String> tickets = new ConcurrentLinkedQueue<>();
static {
for (int i = 0; i < 10000; i++) {
tickets.add("票编号: "+i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
while (true){
String s = tickets.poll(); //取数据,在队列的头上拿出一个数据。poll是原子性的
//poll操作的底层实现是cas .不是加锁的实现, 效率会高很多。
//这里为什么没有例子二的问题(长度判断和操作分开的),因为没有再对队列做任何修改的操作,例子二判断长度之后,然后又做了remove操作。这里poll之后如果被其他线程打断,另外线程把队列取空了, 最多出现再取一次poll,拿到为空的情况
if(null == s){
break;
}else{
System.out.println("销售了---"+s);
}
}
}).start();
}
}
}
并发容器有哪些?
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CountDownLatch;
/**
* 并发容器
* 跳表(SkipList)及ConcurrentSkipListMap源码解析 http://blog.csdn.net/sunxianghuang/article/details/52221913
* http://www.educity.cn/java/498061.html
*
* 总结 对于Map/Set 的选择使用 这里所有的...Map都有对应的Set . ConcurrentHashMap 对应有ConcurrentHashSet . hash和set本质上是一回事
* 如果不加锁 hashmap treemap linkedhashmap
* 如果加锁 Hashtable 并发不是很高的情况下可以使用Collections.synchronizedXXX
* 如果加锁 并发很高的情况下使用ConcurrentHashMap
* 如果加锁 并发很高 而且要求排序 使用ConcurrentSkipListMap
*/
public class T01_ConcurrentMap {
public static void main(String[] args) {
Map<String, String> map = new ConcurrentHashMap<>(); //默认把容器分成16段,每次插入时只锁定一个部分。这样可以并发的 插入,就是把大锁分成了小锁,理论上,多线程操作效率比Hashtable,Collections.synchronizedXXX高
//并发的时候排好序: 跳表
// Map<String, String> map = new ConcurrentSkipListMap<>(); //跳表,排好顺序的 高并发并且插入数据排好序
//非并发的时候排好序使用TreeMap
// Map<String, String> map = new Hashtable<>(); //加数据时,所有实现默认加锁的, 锁定整个对象。效率低下,用的很少
// Map<String, String> map = new HashMap<>(); //往上加锁:Collections.synchronizedXXX
Random r = new Random();
Thread[] ths = new Thread[100];
CountDownLatch latch = new CountDownLatch(ths.length);
long start = System.currentTimeMillis();
for (int i = 0; i < ths.length; i++) { //起100个线程
ths[i] = new Thread(()->{ //每个线程往map里放了1万个随机字符串
for (int j = 0; j < 10000; j++) {
map.put("a"+r.nextInt(100000), "a"+r.nextInt(100000));
}
latch.countDown();
});
}
Arrays.asList(ths).forEach(t->t.start()); //起100个线程
try {
latch.await();
}catch (InterruptedException e){
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
import java.util.*;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
/**
* 写时复制容器 copy on write:当往容器添加元素时,把容器复制一份,再后面加新的元素,把引用指过去。适合写少读多的环境。例如事件监听器。
* 多线程环境下,写时效率低,读时效率高. 读的时候不用加锁,因为读的时候是从新的引用读
*/
public class T02_CopyOnWriteList {
public static void main(String[] args) {
// List<String> lists = new ArrayList<>(); //这个会出并发问题, ArrayList没有锁,可能add之后,size只加了一次。
// List<String> lists = new Vector<>(); 这个没问题,加了锁的。
List<String> lists = new CopyOnWriteArrayList<>(); //效率最低, 读的时候不用加锁。 适用于读的很多,写的很少的场景。
Random r = new Random();
Thread[] ths = new Thread[100];
for (int i = 0; i < ths.length; i++) {
Runnable task = new Runnable() {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
lists.add("a"+r.nextInt(10000));
}
}
};
ths[i] = new Thread(task);
}
runAndComputeTime(ths);
System.out.println(lists.size());
}
private static void runAndComputeTime(Thread[] ths) {
long s1 = System.currentTimeMillis();
Arrays.asList(ths).forEach(t->t.start());
Arrays.asList(ths).forEach(t->{
try{
t.join();
}catch (InterruptedException e){
e.printStackTrace();
}
});
long s2 = System.currentTimeMillis();
System.out.println(s2-s1);
}
}
public class T03_SynchronizedList {
public static void main(String[] args) {
List<String> strs = new ArrayList<>(); //strs是没锁的
List<String> strsSync = Collections.synchronizedList(strs); //如果想给strs这个ArrayList加把锁,返回的加了锁的容器strsSync
}
}
队列:在并发容器里是最重要的也是应用最多的容器
** Queue分两种: 高并发下可以使用两种队列,
第1种: ConcurrentLinkedQueue(内部加锁) . 无界队列,内存耗完之前可以一直加。
第2种: BlockingQueue(阻塞式队列:生产者,消费者模式, 生产者满了会等待 消费者空了会等待):
LinkedBlockingQueue(链表实现的阻塞式容器)无界队列,内存耗完之前可以一直加。
ArrayBlockingQueue(数组实现) 有界队列
DelayQueue 无界队列 可以用来执行定时任务
LinkedTransferQueue 适用于并发更高的情况下
SynchronousQueue是一种特殊的transferQueue , 容量为0, 放入的东西,必须消费者消费,不然出问题。
**
ConcurrentLinkedQueue
public class T04_ConcurrentQueue {
public static void main(String[] args) {
Queue<String> strs = new ConcurrentLinkedQueue<>(); //无界队列,内存耗完之前可以一直加
for (int i = 0; i < 10; i++) {
strs.offer("a"+i); //类似于原来的add方法,有容量限制时,add会抛异常,offer不会,但有个返回值。
}
System.out.println(strs);
System.out.println(strs.size());
System.out.println(strs.poll()); //poll从头拿一个并删掉
System.out.println(strs.size());
System.out.println(strs.peek()); //拿一个不删
System.out.println(strs.size());
System.out.println(strs.peek());
}
}
LinkedBlockingQueue 无界队列
/* 该例子使用LinkedBlockingQueue实现生产者,消费者模式, 生产者满了会等待 消费者空了会等待
*/
public class T05_LinkedBlockingQueue {
static BlockingQueue<String> strs = new LinkedBlockingQueue<>();
static Random r = new Random();
public static void main(String[] args) {
new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
strs.put("a"+i); //如果满了就会等待
TimeUnit.MILLISECONDS.sleep(r.nextInt(1000));
}catch (InterruptedException e){
e.printStackTrace();
}
}
}, "p1").start();
for (int i = 0; i < 5; i++) {
new Thread(()->{
for (;;){
try {
System.out.println(Thread.currentThread().getName()+" take -"+strs.take()); //如果空了就会等待
}catch (InterruptedException e){
e.printStackTrace();
}
}
}, "c"+i).start();
}
}
}
ArrayBlockingQueue: 有界队列
public class T06_ArrayBlockingQueue {
static BlockingQueue<String> strs = new ArrayBlockingQueue<>(10);
static Random r = new Random();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
strs.put("a"+i); //如果满了就会等待
}
// strs.add("aaa"); //报异常,队列满了
// boolean b = strs.offer("aaa"); //offer不会报异常
// System.out.println(b);
// strs.offer("aaa", 1, TimeUnit.SECONDS); //按时间段阻塞
strs.put("aaa"); //容器满了put方法会阻塞
System.out.println(strs);
}
}
DelayQueue 无界队列
加进去的每一个元素,消费者拿每一个元素的时候是有个等待时间的,每一个元素自己记载着还有多少时间可以从队列中被消费者拿走, 队列默认是排好顺序的。 等待时间最长的排在最前面,先往外拿。 DelayQueue put方法往里加元素的时候,元素必须要实现Delayed接口
* DelayQueue可以用来执行定时任务
*/
public class T07_DelayQueue {
static BlockingQueue<MyTask> tasks = new DelayQueue<>();
static Random r = new Random();
static class MyTask implements Delayed{
long runningTime;
public MyTask(long runningTime) {
this.runningTime = runningTime;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(runningTime-System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
if(this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)){
return -1;
}else if(this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)){
return 1;
}else {
return 0;
}
}
@Override
public String toString() {
return ""+runningTime;
}
}
public static void main(String[] args) throws InterruptedException {
long now = System.currentTimeMillis();
MyTask t1 = new MyTask(now +1000);
MyTask t2 = new MyTask(now +2000);
MyTask t3 = new MyTask(now +1500);
MyTask t4 = new MyTask(now +2500);
MyTask t5 = new MyTask(now +500);
tasks.put(t1);
tasks.put(t2);
tasks.put(t3);
tasks.put(t4);
tasks.put(t5);
System.out.println(tasks);
for (int i = 0; i < 5; i++) {
System.out.println(tasks.take());
}
}
}
LinkedTransferQueue 适用于并发更高的情况下。提供了特殊的方法
* transfer 就是生成者生产了如果有消费者正在消费,则不放队列,直接扔给消费者。如果没有消费者,则阻塞。
*/
public class T08_TransQueue {
public static void main(String[] args) throws InterruptedException {
LinkedTransferQueue<String> strs = new LinkedTransferQueue<>();
//先起消费者,程序没问题
// new Thread(()->{
// try {
// System.out.println(strs.take());
// }catch (InterruptedException e){
// e.printStackTrace();
// }
// }).start();
System.out.println("aaaaaaaaaaaaaaaaaaaa---");
// strs.transfer("aaa"); //只有用transfer会阻塞在这里。
strs.put("aaa"); //put 或者 add 没有问题。
//后启消费者,前面transfer会阻塞。
new Thread(()->{
try {
System.out.println(strs.take());
}catch (InterruptedException e){
e.printStackTrace();
}
}).start();
}
}
**
* transfer 就是生成者生产了如果有消费者正在消费,则不放队列,直接扔给消费者。如果没有消费者,则阻塞。
* SynchronousQueue是一种特殊的transferQueue , 容量为0, 放入的东西,必须消费者消费,不然出问题。
*/
public class T09_SynchronusQueue {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> strs = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(strs.take());
}catch (InterruptedException e){
e.printStackTrace();
}
}).start();
strs.put("aaa"); //阻塞等待消费者消费。 必须得有消费者等待处理,相当于直接给消费者,消费者直接处理。
//如果没消费者,则阻塞。 调用add报错。
// strs.add("aaa"); //报错
System.out.println(strs.size());
}
}