文章目录
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区别
- Synchnorized 内置java关键字, Lock是一个java类
- Synchronized 无法判断获取锁的状态, Lock 可以判断是否获取到了锁
- Synchronized 无需手动解锁,Lock必须手动解锁,不释放会死锁
- Synchronized 如果获取不到锁,线程会等待,Lock不一定会等待
- Synchronized 可重入锁,不可以中断,非公平,Lock可以重入,可以判断锁,公平与否可以设置
Synchronized | Lock |
---|---|
传入锁对象加锁,在方法上加锁 | 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多线程安全
- Vector
- Collections.synchronizedList
- 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
CopyOnWriteArrayList
是java.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个许可