java——集合、多线程

2018-05-02  本文已影响0人  奕_然

集合

java中的集合一般分为List、Map、Set、Queue。

List

列表集合

  1. ArrayList:最常用的List,底层为Object数组,继承了RandomAccess, Cloneable, java.io.Serializable等接口。

继承RandomAccess表示该类支持快速随机访问,尽量用for(int i = 0; i < size; i++) 来遍历。反之使用Iterator迭代器。
继承Cloneable表示该类支持浅拷贝。
继承java.io.Serializable表示该类支持序列化

  1. LinkedList:底层实现为双向链表。不支持随机访问。实现Queue接口,支持队列操作。
  2. Vector:线程安全的ArrayList,所有方法全部使用synchronized标记。虽然方法都是线程安全的,但是如果使用Iterator迭代器进行遍历的话,还是线程不安全的。但是使用foreach()方法进行遍历是线程安全的。
  3. CopyOnWriteArrayList:底层实现为Object数组,方法是线程不安全的,但是遍历是线程安全的。通过拷贝List实现遍历安全。

Map

键值对集合

  1. HashMap:最常用的Map,线程不安全,允许key和value为null,内部使用Node数组保存k-v数据。默认大小为16,调节因子为0.75(表示当元素达到数组大小的0.75时,数据进行扩容,扩大两倍)。通过计算key的hash值来确定key-value的位置。如果出现位置冲突,jdk1.8前是形成链表,jdk1.8及以后引入链表+红黑树,默认链表元素大于8时红黑树化,树元素小于6时链表化。
  2. HashTable:线程不安全,key和value不允许null,内部使用Entry数组保存数据。不常用,为什么不用HashMap。
  3. SynchronizedMap:线程安全,由synchronizedMap创建,实现线程安全的方式是通过synchronized调用相关方法。并发性能差。
  4. ConcurrentHashMap:线程安全的HashMap,实现方式为CAS操作(jdk1.8之前为分段锁)。并发性能好。

Set

不重复的集合

  1. HashSet:最常用的Set,一般用来过滤数据、保存配置,底层使用HashMap的key保存数据(实现数据不重复)。
  2. TreeSet:可以按某个顺序保存数据,通过构造方法传入Comparator实现。

Queue

队列

  1. LinkedList:上面有
  2. BlockingQueue:阻塞队列,多线程中实现线程的通信,经常使用。常用LinkedBlockingQueue,通过分离读写锁实现并发。还有ArrayBlockingQueue,单锁实现并发。

多线程

synchronizd

java中,每个对象都自带监视器。synchronizd有三种使用方式:

  1. synchronizd(object){...},对任意代码块加锁。线程会尝试获取object的监视器,成功获取的线程进入临界区。其他线程阻塞。直到临界区代码执行完毕,释放object的监视器,其他线程再次进行竞争。保证了临界区一次只有一个线程进入。
  2. synchronizd修饰普通方法,线程会尝试获取该方法对象的监视器。如果两个不同对象调用同一个synchronizd修饰的方法,无法保证线程执行的唯一性,synchronizd不会发挥作用。
  3. synchronizd修饰静态方法,线程会尝试获取该方法类的监视器。Class对象的唯一性会保证synchronizd发挥作用。
    如果1中的object为Class对象,那么效果与3相同。
    synchronizd为可重入锁。可以在获取对象的监视器后,再次调用同对象的synchronizd。

volatile

volatile可以保证所有线程看到的变量值都是最新的。任何线程修改了volatile变量,会立刻反映到其他线程中。

如果变量不用volatile修饰,那么线程对变量的修改不会立刻反映到其他线程中,因为有本地缓存。如果不用volatile,可以用synchronizd(Lock)实现变量的刷新。离开synchronizd代码块时,所有的修改会强制刷新入主内存。
volatile保证可见性,不保证原子性。如果有对volatile变量的原子性操作,需要加锁。
volatile可以防止 JVM 进行指令重排优化。
禁止重排序的规则为:第一个操作为volatile读;第二个操作为volatile写;第一个操作为volatile写,第二个操作为volatile读。
禁止重排序可以防止因为重排序在多线程环境下引起的顺序一致性错误。

private static Map<String,String> value ;
private static volatile boolean flag = fasle ;
//以下方法发生在线程 A 中 初始化 Map
public void initMap(){
   //耗时操作
   value = getMapValue() ;//1
   flag = true ;//2
}
//发生在线程 B中 等到 Map 初始化成功进行其他操作
public void doSomeThing(){
   while(!flag){
       sleep() ;
   }
   //dosomething
   doSomeThing(value);
}

如果flag不用volatile修饰,那么线程A有可能会先执行2再执行1,这样会造成错误。同时volatile修饰的变量可以保证线程A改变flag的值之后,线程B立刻可见。否则容易因为缓存导致死循环。

Lock

锁接口,主要实现类为ReentrantLock、ReadLock和WriteLock。

  1. ReentrantLock:可重入锁,最普通的Lock实现类。
  2. ReentrantReadWriteLock:读写分离锁,继承自ReadWriteLock,内部实现了ReadLock和WriteLock,读锁和写锁。通过分离读写提高并发性。允许同时多个读线程,只允许同时单个写线程(此时不允许读)。适用于读多写少。

如果想用Lock进行同步操作,注意多个线程中操作同一个Lock。

ThreadPoolExecutor

ThreadPoolExecutor线程池,可以自定义线程池的所有参数。

推荐通过new ThreadPoolExecutor()创建线程池

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

线程池规则

线程池的线程执行规则跟任务队列有很大的关系。

下面都假设任务队列没有大小限制:

任务队列大小有限时:

多线程通信

  1. wait和notify:最基础的线程通信,wait使线程等待,notify唤醒等待线程。通过对象实现,类似synchronizd(object)
  2. CountDownLatch:倒计数器,await检查计数器是否为0,countDown使计数减1。
  3. CyclicBarrier:实现线程间的相互等待,当所有线程调用await后,所有线程开始向下执行。
  4. Callable:有返回值的Runnable,可以使用FutureTask接收返回值,会阻塞。如果使用线程池,则不会阻塞。
  5. BlockingQueue:阻塞队列。生产者-消费者模型中经常使用。
上一篇 下一篇

猜你喜欢

热点阅读