一、ArrayBlockingQueue在logback异步日志打印中的使用
在高并发、高流量并且响应要求比较小的系统中同步打印日志已经满足不了需求,打印日志本身是需要写磁盘的,写操作会短暂的阻塞业务线程。
同步日志打印:就是将日志写入磁盘与业务线程同步调用完成。
异步日志打印:将日志打印的任务放入一个队列后直接返回,然后使用一个线程专门负责从队列中获取日志任务并写入磁盘。通过使用队列将业务逻辑与日志逻辑解耦。
logback的异步日志模型是一个多生产者-单消费者模型
二、ConcurrentLinkedQueue在Tomcat的NioEndPoint中的使用
三、并发组件ConcurrentHashMap使用注意事项
put(K key,V value)方法判断如果key已经存在,则使用value覆盖原来的值并返回原来的值,如果不存在则把value放入并返回null
putIfAbsent(K key,V value)方法判断如果key已经存在则直接返回原来对应的值并且不覆盖,如果key不存在则把value放入并返回null,并且判断key是否存在和放入时原子性操作。
public class ConcurrentHashMapTest { private static final ConcurrentHashMap<String,List<String>> map = new ConcurrentHashMap<>(); @Test public void putTest(){ ExecutorService pool = Executors.newFixedThreadPool(3); pool.submit(new Runnable() { @Override public void run() { List<String> list1 = new CopyOnWriteArrayList<>(); list1.add("device1"); list1.add("device2"); map.put("topic1", list1); System.out.println(map); } }); pool.submit(new Runnable() { @Override public void run() { List<String> list1 = new CopyOnWriteArrayList<>(); list1.add("device11"); list1.add("device22"); map.put("topic1", list1); System.out.println(map); } }); pool.submit(new Runnable() { @Override public void run() { List<String> list1 = new CopyOnWriteArrayList<>(); list1.add("device111"); list1.add("device222"); map.put("topic2", list1); System.out.println(map); } }); pool.shutdown(); } @Test public void putIfAbsentTest(){ ExecutorService pool = Executors.newFixedThreadPool(3); pool.submit(new Runnable() { @Override public void run() { List<String> list1 = new CopyOnWriteArrayList<>(); list1.add("device1"); list1.add("device2"); List<String> oldList = map.putIfAbsent("topic1", list1); if(null != oldList){ oldList.addAll(list1); } System.out.println(map); } }); pool.submit(new Runnable() { @Override public void run() { List<String> list1 = new CopyOnWriteArrayList<>(); list1.add("device11"); list1.add("device22"); List<String> oldList = map.putIfAbsent("topic1", list1); if(null != oldList){ oldList.addAll(list1); } System.out.println(map); } }); pool.submit(new Runnable() { @Override public void run() { List<String> list1 = new CopyOnWriteArrayList<>(); list1.add("device111"); list1.add("device222"); List<String> oldList = map.putIfAbsent("topic2", list1); if(null != oldList){ oldList.addAll(list1); } System.out.println(map); } }); pool.shutdown(); } }
四、SimpleDateFormat是线程不安全的
public class SimpleDateFormatTest { private static final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static void main(String[] args){ ExecutorService pool = Executors.newFixedThreadPool(10); for (int i = 0; i < 10; i++) { pool.submit(new Runnable() { @Override public void run() { try { System.out.println(format.parse("2020-02-06 17:32:00")); } catch (ParseException e) { e.printStackTrace(); } } }); } pool.shutdown(); } }
五、使用Timer时需要注意的事情
当一个Timer运行多个TimerTask时,只要其中一个TimerTask在执行中向run方法外抛出了异常,则其他任务也会自动终止。
六、对需要复用但是会被下游修改的参数要进行深复制
七、创建线程和线程池时要指定与业务相关的名称
八、使用线程池的情况下当程序结束时记得调用shutdown关闭线程池
九、线程池使用FutureTask时需要注意的事情
十、使用ThreadLocal不当可能导致内存泄漏
参考《Java并发编程之美》