Java JUC并发编程 (线程状态 / ReentrantLock / CopyOnWriteArrayList / ConcurrentHashMap / 常用集合类)

java.util.concurrent

线程六种状态

  • NEW:刚通过new创建的线程对象
  • RUNNABLE:通过start()方法开启执行的线程
  • BLOCKED:阻塞在synchronized代码块或者方法出的线程,可以理解为锁池中的线程,锁池中的线程在其他线程释放锁之后可以开始竞争
  • WAITING:无限等待notify(),notifyAll()唤醒的线程,在等待池中,如果没有唤醒则一直等待,不能竞争锁,如果唤醒则进入锁池
  • TIMED_WAITING:睡眠一定时间后自动苏醒
  • TERMINATED:线程终结

java.util.concurrent.locks

Lock是一个借口,三个实现方式
在这里插入图片描述

ReentrantLock

ReentrantLock默认非公平锁,可以插队,构造时传入布尔值可以改变公平锁,则不可以插队

/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
 */
public ReentrantLock() {
    sync = new NonfairSync();
}

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

使用锁

Lock l = ...; l.lock(); 
try { // access the resource protected by this lock } 
finally { l.unlock(); } 

Locks 和 Synchronized区别

  1. Synchnorized 内置java关键字, Lock是一个java类
  2. Synchronized 无法判断获取锁的状态, Lock 可以判断是否获取到了锁
  3. Synchronized 无需手动解锁,Lock必须手动解锁,不释放会死锁
  4. Synchronized 如果获取不到锁,线程会等待,Lock不一定会等待
  5. Synchronized 可重入锁,不可以中断,非公平,Lock可以重入,可以判断锁,公平与否可以设置
SynchronizedLock
传入锁对象加锁,在方法上加锁lock() / unlock()
锁对象.wait()通过lock.newConfition()获取condition对象,通过condition.await()
notify() notifyAll()signal() signalAll()

集合类不安全

List集合

ArrayList 和 LinkedList都是线程不安全的,在多线程情况下,写入数据可能会产生ConcurrentModificationException

public class CopyOnWriteArrayListTest {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            list.add(String.valueOf(new Random().nextInt(10)));
        }
        Iterator<String> iterator = list.iterator();

        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                3,
                3,
                Integer.MAX_VALUE,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>());

        for (int i = 0; i < 10; i++) {
            executor.execute(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            });
        }
    }
}
[4, 6, 3, 78b45, 3fc80, 56124]
[4, 6, 3, 78b45, 3fc80, 56124]
[4, 6, 3, 78b45, 3fc80, 56124, 7ee6c]
[4, 6, 3, 78b45, 3fc80, 56124, 7ee6c, d6802]
[4, 6, 3, 78b45, 3fc80, 56124, 7ee6c, d6802, 6e603]
[4, 6, 3, 78b45, 3fc80, 56124, 7ee6c, d6802, 6e603, 96f58]
[4, 6, 3, 78b45, 3fc80, 56124, 7ee6c, d6802, 6e603, 96f58, 8ce32]
[4, 6, 3, 78b45, 3fc80, 56124, 7ee6c, d6802, 6e603, 96f58, 8ce32, 2cd90, d8844]
Exception in thread "pool-1-thread-1" Exception in thread "pool-1-thread-3" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at juc.CopyOnWriteArrayListTest.lambda$main$0(CopyOnWriteArrayListTest.java:34)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.io.PrintStream.println(PrintStream.java:821)
	at juc.CopyOnWriteArrayListTest.lambda$main$0(CopyOnWriteArrayListTest.java:34)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)

为了解决这个问题,有三种集合可以保证list多线程安全

  1. Vector
  2. Collections.synchronizedList
  3. CopyOnWriteArrayList

Vector

JDK1.0开始Vector便存在于JDK中,它是一个安全的列表,底层采用数组实现,线程安全的是因为它在每一个方法上都加上了synchronized关键字,但是会影响效率,因此不推荐使用。如果不需要线程安全则使用ArrayList代替。


Collections.synchronizedList

JDK提供Collections.synchronizedList静态方法,将一个不安全的List传入,包装为安全的List

List list = Collections.synchronizedList(new ArrayList());

通过加mutex锁,来对操作进行线程同步,可以实现对add,get,remove的线程安全操作,例如

public E get(int index) {
            synchronized (mutex) {return list.get(index);}
        }

但是对于迭代操作而言,没有线程同步,需要对包装后的list进行加锁,对其他对象加锁不行,锁不一致则不能与其他方法保持一致。


CopyOnWriteArrayList

CopyOnWriteArrayListjava.util.concurrent包下面的一个安全List,在进行add,remove,set等修改操作时,使用COW理念:先将原集合复制一份,对复制集合进行更改,然后将集合指针指向新集合。因此这种操作的代价十分昂贵,这是一种读写分离的思想保持最终一致

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

性能比较
  • Vector读写能可以和Collections.synchronizedList比肩,但对迭代器的操作非常差
  • Collections.synchronizedList不仅可以包装ArrayList还可以包装其他集合,兼容性更好,性能较为均衡。但是在迭代器便利中,性能稍差于CopyOnWriteArrayList
  • CopyOnWriteArrayList 读操作性能较好,写操作性能较差

HashMap

HashTable

使用synchronized方法保证线程安全,但是效率低下。当一个线程访问同步方法时,其他线程的访问就会进入阻塞或轮询状态,例如一个线程put另一个线程不能put或者get。类似于数据库的表锁

public synchronized V get(Object key) {
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            return (V)e.value;
        }
    }
    return null;
}

Collections.SynchronizedMap

通过传入Map,返回一个包装过的线程安全的map。方法中使用synchronized同步代码块,保证线程安全,效率一样很低

public V get(Object key) {
    synchronized (mutex) {return m.get(key);}
}
ConcurrentHashMap

1.7之前采用分段锁Segment。1.8之后使用CAS+Synchronized来保证并发更新的安全,synchronized只锁链表或者红黑树的首节点,只要hash不冲突,就不会产生并发,效率提升。相对于锁整个Map来说,只锁自己要用的部分效率极大提升。

常用集合类

CountDownLatch

利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

public CountDownLatch(int count){}; //count是计数值
public void await() throws InterruptedException { };   //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public void countDown() { };  //将count值减1

CyclicBarrier

CyclicBarrier

让一定数量线程都等待(到达共同屏障点)之后,同时指继续执行,不然就等待还没有到达屏障点的线程执行。
两个构造器,parties表示让多少线程阻塞在屏障点,barrierAction指所有线程都到达屏障后,要执行的内容,挑一个线程进行执行。

public CyclicBarrier(int parties, Runnable barrierAction) {}
public CyclicBarrier(int parties) {}

两个常用方法,第一个挂起线程,知道所有线程到达屏障后同时继续执行,第二个方法,挂起线程等待一定时间,如果还有线程没有到达,则让到达的线程继续执行后续任务

public int await() throws InterruptedException, BrokenBarrierException { };
public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { };

Semaphore

Semaphore翻译成字面意思为 信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。permits表示同时允许多少线程进行访问,加锁只能允许一个线程访问,信号量同时允许多个,fair表示是否公平,等待时间越长越先获取许可

public Semaphore(int permits) {          //参数permits表示许可数目,即同时可以允许多少线程进行访问
    sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {    //这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可
    sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
}
public void acquire() throws InterruptedException {  }     //获取一个许可
public void acquire(int permits) throws InterruptedException { }    //获取permits个许可
public void release() { }          //释放一个许可
public void release(int permits) { }    //释放permits个许可
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值