五、8锁现象
六、不安全集合类
1.ArryList集合
多线程下不安全;可能会报错:java.util.ConcurrentModificationException(并发修改异常)
// java.util.ConcurrentModificationException:并发修改异常 public class Test11 { public static void main(String[] args) { List<String> strings = new ArrayList<>(); for (int i = 0; i < 10; i++) { new Thread(()->{ strings.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(strings); }).start(); } } }
解决方案:
List list = new Vector<>(); List strings = Collections.synchronizedList(new ArrayList<>()); List strings = new CopyOnWriteArrayList<>()
2.HashSet集合
HashSet集合的底层是hashmap的key;多线程下不安全;可能会报错:java.util.ConcurrentModificationException(并发修改异常)
// java.util.ConcurrentModificationException:并发修改异常 public class Test11 { public static void main(String[] args) { // Set<String> strings = Collections.synchronizedSet(new HashSet<>()); HashSet<String> strings = new HashSet<>(); for (int i = 0; i < 10; i++) { new Thread(()->{ strings.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(strings); }).start(); } } }
解决方案:
• Set strings = Collections.synchronizedSet(new HashSet<>()); • Set strings = new CopyOnWriteArraySet<>()
3.HashMap集合
多线程下不安全;可能会报错:java.util.ConcurrentModificationException(并发修改异常)
// java.util.ConcurrentModificationException:并发修改异常 public class Test11 { public static void main(String[] args) { // 默认相当于 Map<String, String> map = new HashMap<>(16, 0.75F); for (int i = 0; i < 10; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5)); System.out.println(map); }).start(); } } }
解决方案:
- 使用Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
七、Callable接口
Callable接口类似于Runnable接口,是线程第三种创建方式,有以下特点:
可以抛出异常。
可以有返回值。
方法不同与Runnable接口。用的是Call方法。
public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask futureTask = new FutureTask(new MyThread());// 适配类 new Thread(futureTask,"A").start(); new Thread(futureTask,"B").start();// 打印一个Call,结果会被缓存,提高效率 Integer s = (Integer) futureTask.get();// get方法可能会产生阻塞 System.out.println(s); } } class MyThread implements Callable<Integer>{ @Override public Integer call(){ System.out.println("Call"); return 1024; } }
八、常用辅助类
1.CountDownLatch
应用场景:
- 多线程任务汇总。
- 多线程任务阻塞住,等待发令枪响,一起执行。
每次有线程调用,数量-1,当计数器归零,countDownLatch.await()就会被唤醒向下执行。
// 计数器 public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { // 总数是6,必须要是执行任务的时候使用 CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 0; i < 6; i++) { new Thread(()->{ System.out.println(Thread.currentThread().getName()+"=>Go Out"); countDownLatch.countDown();// 数量-1 }).start(); } countDownLatch.await();// 等待计数器归零,然后再往下执行 System.out.println("关门"); } } // 打印: // Thread-0=>Go Out // Thread-5=>Go Out // Thread-4=>Go Out // Thread-2=>Go Out // Thread-3=>Go Out // Thread-1=>Go Out // 关门
2.CyclicBarrier
应用场景:允许一组线程全部等待彼此达到共同屏障点的同步辅助。
// 相当于加法计数器 public class CyclicBarrierDemo { public static void main(String[] args) { // 集齐七颗龙珠召唤神龙 CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {// 如果计数器为7,线程只有6个,则会等待,不进行召唤神龙 System.out.println("召唤神龙"); }); for (int i = 0; i < 7; i++) { final int temp = i; new Thread(() -> { System.out.println(Thread.currentThread().getName() + "收集" + temp + "个龙珠!"); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }).start(); } } }
3.Semaphore
Semaphore:信号量
public class SemaphoreDemo { public static void main(String[] args) { // 线程数量:停车位!限流 Semaphore semaphore = new Semaphore(3); for (int i = 0; i < 6; i++) { new Thread(()->{ try { semaphore.acquire();// 得到 System.out.println(Thread.currentThread().getName()+"抢到车位!"); TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName()+"离开车位!"); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release();// 释放 } }).start(); } } } // 打印: // Thread-0抢到车位! // Thread-2抢到车位! // Thread-1抢到车位! // Thread-2离开车位! // Thread-1离开车位! // Thread-0离开车位! // Thread-3抢到车位! // Thread-5抢到车位! // Thread-4抢到车位! // Thread-5离开车位! // Thread-4离开车位! // Thread-3离开车位!
原理:
- semaphore.acquire();获得,假设已经满了则等待,等待其他线程释放。
- semaphore.release();释放,会将当前的信号量释放+1,然后唤醒等待的线程。
九、读写锁
- ReadWriteLock接口有一个实现类ReentrantReadWriteLock类。
- 读可以被多个线程同时读,写的时候只能有一个线程去写。
/** * 独占锁(写锁):一次只能被一个线程占有 * 共享锁(读锁):多个线程可以同时占有 * ReentrantLock: * 读-读:可以共存 * 读-写:不可以共存 * 写-写:不可以共存 */ public class ReentrantLockDemo { public static void main(String[] args) { MyCacheLock myCache = new MyCacheLock(); // 5个线程写 for (int i = 1; i <= 5; i++) { final int temp = i; new Thread(() -> { myCache.put(temp + "", temp + ""); }, String.valueOf(i)).start(); } // 5个线程读 for (int i = 1; i <= 5; i++) { final int temp = i; new Thread(() -> { myCache.get(temp + ""); }, String.valueOf(i)).start(); } } } class MyCacheLock { private volatile Map<String, Object> map = new HashMap<>(); // 读写锁,更加细粒度的控制 private ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 写,同时只有一个线程写 public void put(String key, Object obj) { readWriteLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + "写入"); map.put(key, obj); System.out.println(Thread.currentThread().getName() + "写入OK"); } catch (Exception e) { e.printStackTrace(); } finally { readWriteLock.writeLock().unlock(); } } // 读,所有线程都可以读 public void get(String key) { readWriteLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + "读取"); map.get(key); System.out.println(Thread.currentThread().getName() + "读取OK"); } catch (Exception e) { e.printStackTrace(); } finally { readWriteLock.readLock().unlock(); } } }
后记
Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~