并发下 ArrayList 不安全

出现 java.util.ConcurrentModificationException 并发修改异常!

  1. /**
  2. * 解决方案;
  3. * 1、List<String> list = new Vector<>();
  4. * 2、List<String> list = Collections.synchronizedList(new ArrayList<>
  5. ());
  6. * 3、List<String> list = new CopyOnWriteArrayList<>();
  7. */
  8. // CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略;
  9. // 多个线程调用的时候,list,读取的时候,固定的,写入(覆盖)
  10. // 在写入的时候避免覆盖,造成数据问题!
  11. // 读写分离
  12. // CopyOnWriteArrayList 比 Vector Nb 在哪里?

CopyOnWrite 写入时复制 COW

1.什么是COW?

Copy-On-Write简称COW。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。

2COW的应用场景

CopyOnWrite并发容器用于读多写少的并发场景

3.COW的缺陷

很明显,在写场合,复制整个数组的开销是很大的,因此需要注意

  1. package cn.maidaotech.java07.syncaddlock.listunsafe;
  2. import java.util.ArrayList;
  3. import java.util.Collections;
  4. import java.util.List;
  5. import java.util.UUID;
  6. import java.util.Vector;
  7. import java.util.concurrent.CopyOnWriteArrayList;
  8. import java.util.concurrent.TimeUnit;
  9. //java.util.ConcurrentModificationException 并发修改异常!
  10. public class ListTest {
  11. public static void main(String[] args) {
  12. // !并发下 ArrayList 不安全
  13. // List<String> list = new ArrayList<>();//java.util.ConcurrentModificationException并发修改异常
  14. // for (int i = 1; i <=10; i++) {
  15. // new Thread(()->{
  16. // list.add(UUID.randomUUID().toString().substring(0,5));
  17. // System.out.println(list);
  18. // }).start();
  19. // }
  20. // System.out.println(list.size());
  21. /**
  22. * 解决方案:
  23. * 1、List<String> list = new Vector<>();
  24. */
  25. // List<String> list = new Vector<>();
  26. // for (int i = 1; i <=10; i++) {
  27. // new Thread(()->{
  28. // list.add(UUID.randomUUID().toString().substring(0,5));
  29. // System.out.println(list);
  30. // },String.valueOf(i)).start();
  31. // }
  32. // try {
  33. // TimeUnit.SECONDS.sleep(1);
  34. // System.out.println(list.size());
  35. // } catch (Exception e) {
  36. // e.printStackTrace();
  37. // }
  38. /**
  39. * 解决方案:
  40. * 2. List<String> list = Collections.synchronizedList(new ArrayList<>());
  41. */
  42. // List<String> list = Collections.synchronizedList(new ArrayList<>());
  43. // for (int i = 0; i < 10; i++) {
  44. // new Thread(()->{
  45. // list.add(UUID.randomUUID().toString().substring(0, 5));
  46. // System.out.println(list);
  47. // },String.valueOf(i)).start();
  48. // }
  49. // try {
  50. // TimeUnit.SECONDS.sleep(1);
  51. // System.out.println(list.size());
  52. // } catch (Exception e) {
  53. // e.printStackTrace();
  54. // }
  55. /**
  56. * 解决方案:
  57. * 3.List<String> list = new CopyOnWriteArrayList<>(); COW(写时复制)
  58. */
  59. // CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略;
  60. // 多个线程调用的时候,list,读取的时候,固定的,写入(覆盖)
  61. // 在写入的时候避免覆盖,造成数据问题!
  62. // 读写分离
  63. // CopyOnWriteArrayList 比 Vector Nb 在哪里?
  64. List list = new CopyOnWriteArrayList<>();
  65. for (int i = 0; i < 10; i++) {
  66. new Thread(()->{
  67. list.add(UUID.randomUUID().toString().substring(0, 5));
  68. System.out.println(list);
  69. },String.valueOf(i)).start();
  70. }
  71. try {
  72. TimeUnit.SECONDS.sleep(1);
  73. System.out.println(list.size());
  74. } catch (Exception e) {
  75. e.printStackTrace();
  76. }
  77. }
  78. }

CopyOnWriteArrayList 比 Vector Nb 在哪里?

CopyOnWriteArrayList 使用的lock锁的机制
image.png
Vector 使用的是synchronized锁image.png

synchronized 和 lock锁的区别

synchronized 是java内置的关键字,lock是一个java类

synchronized 无法获取锁的状态,lock 可以判断是否获得了锁

synchronized 会自动释放锁,lock不会自动释放锁,需要自己手动释放锁,如果不释放锁,会造成死锁

synchronized 线程一(获得锁,然后锁阻塞了),线程二(等待,然后还是傻傻的等待),lock锁就不一定会等待下去

synchronized 可重入锁,不可以被中断,非公平,lock 可重入锁,可判断锁,是否为公平锁,可以自行设置

synchronized 适合少量的代码同步问题,lock适合大量的代码同步问题

Vector 和 CopyOnWriteArrayList

Vector 的曾删改查方法都加上了synchronized锁,保证同步的情况下,因为每个方法都要去获得锁,所以性能就会大大下降。

CopyOnWriteArrayList 方法只是在增删改方法上增加了ReentrantLock锁,但是他的读方法不加锁,所以在读的方面就要比Vector性能要好,CopyOnWriteArrayList适合读多写少的并发情况,读写分离,在写的时候复制出一个新的数组,完成插入、修改、删除操作,在完成操作后,将这个新的数组赋值给一个array。


set不安全

  1. package cn.maidaotech.java07.syncaddlock.listandsetunsafe;
  2. import java.util.Collections;
  3. import java.util.HashSet;
  4. import java.util.Set;
  5. import java.util.UUID;
  6. import java.util.concurrent.TimeUnit;
  7. /**
  8. * 同理可证 : ConcurrentModificationException
  9. * 1、Set<String> set = Collections.synchronizedSet(new HashSet<>());
  10. * 2、
  11. */
  12. //java.util.ConcurrentModificationException 并发修改异常
  13. public class SetTest {
  14. public static void main(String[] args) {
  15. // Set set = new HashSet<>();
  16. // for (int i = 0; i < 10; i++) {
  17. // new Thread(()->{
  18. // set.add(UUID.randomUUID().toString().substring(0, 5));
  19. // System.out.println(set);
  20. // },String.valueOf(i)).start();
  21. // }
  22. // System.out.println(set.size());
  23. /**
  24. * 解决方案
  25. * 1.Set set = Collections.synchronizedSet(new HashSet<>());
  26. */
  27. // Set set = Collections.synchronizedSet(new HashSet<>());
  28. // for (int i = 0; i < 10; i++) {
  29. // new Thread(()->{
  30. // set.add(UUID.randomUUID().toString().substring(0, 5));
  31. // System.out.println(set);
  32. // },String.valueOf(i)).start();
  33. // }
  34. // try {
  35. // TimeUnit.SECONDS.sleep(1);
  36. // System.out.println(set.size());
  37. // } catch (Exception e) {
  38. // e.printStackTrace();
  39. // }
  40. }
  41. }

HashSet的底层:

  1. public HashSet() {
  2. map = new HashMap<>();
  3. }
  4. // add set 本质就是 map key是无法重复的!
  5. public boolean add(E e) {
  6. return map.put(e, PRESENT)==null;
  7. }
  8. private static final Object PRESENT = new Object(); // 不变得值

Map不安全

  1. package com.kuang.unsafe;
  2. import java.util.Collections;
  3. import java.util.HashMap;
  4. import java.util.Map;
  5. import java.util.UUID;
  6. import java.util.concurrent.ConcurrentHashMap;
  7. // ConcurrentModificationException
  8. public class MapTest {
  9. public static void main(String[] args) {
  10. // map 是这样用的吗? 不是,工作中不用 HashMap
  11. // 默认等价于什么? new HashMap<>(16,0.75);
  12. // Map<String, String> map = new HashMap<>();
  13. // 唯一的一个家庭作业:研究ConcurrentHashMap的原理
  14. Map<String, String> map = new ConcurrentHashMap<>();
  15. for (int i = 1; i <=30; i++) {
  16. new Thread(()->{
  17. map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(
  18. 0,5));
  19. System.out.println(map);
  20. },String.valueOf(i)).start();
  21. }
  22. }
  23. }

Callable接口

1、可以有返回值 2、可以抛出异常 3、方法不同,run()/ call()

  1. package cn.maidaotech.java07.syncaddlock.callable;
  2. import java.util.concurrent.Callable;
  3. import java.util.concurrent.ExecutionException;
  4. import java.util.concurrent.FutureTask;
  5. public class CallableTest {
  6. public static void main(String[] args) throws InterruptedException, ExecutionException {
  7. MyThread thread =new MyThread();
  8. FutureTask futureTask = new FutureTask<>(thread);//适配类
  9. new Thread(futureTask,"A").start();
  10. new Thread(futureTask,"B").start();// 结果会被缓存,效率高
  11. Integer o = (Integer) futureTask.get();//这个get 方法可能会产生阻塞!把他放到最后
  12. // 或者使用异步通信来处理!
  13. System.out.println(o);
  14. }
  15. }
  16. class MyThread implements Callable<Integer>{
  17. @Override
  18. public Integer call() throws Exception {
  19. System.out.println("call()");
  20. //耗时操作
  21. return 1024;
  22. }
  23. }

细节: 1、有缓存 2、结果可能需要等待,会阻塞!

常用的辅助类

CountDownLath(倒计时锁)

image.png

  1. package cn.maidaotech.java07.syncaddlock.assistclass;
  2. import java.util.concurrent.CountDownLatch;
  3. public class CountDownLathDemo {
  4. public static void main(String[] args) throws InterruptedException {
  5. // 总数是6,必须要执行任务的时候,再使用!
  6. CountDownLatch countDownLatch = new CountDownLatch(6);
  7. for (int i = 1; i <=6; i++) {
  8. new Thread(()->{
  9. System.out.println(Thread.currentThread().getName()+"====>go out");
  10. countDownLatch.countDown();//数量-1
  11. },String.valueOf(i)).start();
  12. }
  13. countDownLatch.await(); // 等待计数器归零,然后再向下执行
  14. System.out.println("Close door");// 等待计数器归零,然后再向下执行
  15. //每次有线程调用 countDown() 数量-1,假设计数器变为0,countDownLatch.await() 就会被唤醒,继续
  16. //执行!
  17. }
  18. }

原理:
countDownLatch.countDown(); // 数量-1
countDownLatch.await(); // 等待计数器归零,然后再向下执行 每次有线程调用 countDown() 数量-1,假设计数器 变为0,countDownLatch.await() 就会被唤醒,继续 执行!

CyclicBarrier (可用于加法计数器)

image.png
加法计数器

  1. package cn.maidaotech.java07.syncaddlock.assistclass;
  2. import java.util.concurrent.BrokenBarrierException;
  3. import java.util.concurrent.CyclicBarrier;
  4. public class CyclicBarrirDemo {
  5. public static void main(String[] args) {
  6. /**
  7. * 集齐7颗龙珠召唤神龙
  8. */
  9. // 召唤龙珠的线程
  10. CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
  11. System.out.println("召唤神龙成功");
  12. });
  13. for (int i = 1; i < 8; i++) {
  14. final int temp = i;
  15. new Thread(() -> {
  16. System.out.println(Thread.currentThread().getName() + "召唤了第" + temp + "颗龙珠");
  17. try {
  18. cyclicBarrier.await();
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. } catch (BrokenBarrierException e) {
  22. e.printStackTrace();
  23. }
  24. },String.valueOf(i)).start();
  25. }
  26. }
  27. }

Semaphore (信号量)

image.png
抢车位! 6车—-3个停车位置

  1. package cn.maidaotech.java07.syncaddlock.assistclass;
  2. import java.util.concurrent.Semaphore;
  3. import java.util.concurrent.TimeUnit;
  4. public class SemaphoreDemo {
  5. public static void main(String[] args) {
  6. // 抢车位
  7. Semaphore semaphore = new Semaphore(3);
  8. for (int i = 1; i < 7; i++) {
  9. new Thread(() -> {
  10. try {
  11. semaphore.acquire();// 得到
  12. System.out.println(Thread.currentThread().getName()+"抢到车位");
  13. TimeUnit.SECONDS.sleep(2);
  14. System.out.println(Thread.currentThread().getName()+"离开车位");
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }finally{
  18. semaphore.release();//释放
  19. }
  20. }).start();
  21. }
  22. //原理:semaphore.acquire() 获得,假设如果已经满了,等待,等待被释放为止!
  23. //semaphore.release(); 释放,会将当前的信号量释放 + 1,然后唤醒等待的线程!
  24. //作用: 多个共享资源互斥的使用!并发限流,控制最大的线程数!
  25. }
  26. }

volatile

在Java并发编程中常用于保持内存可见性和防止指令重排序。

ReadWriteLock(读写锁)

image.png

  1. package cn.maidaotech.java07.syncaddlock.readwritelock;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import java.util.concurrent.locks.Lock;
  5. import java.util.concurrent.locks.ReadWriteLock;
  6. import java.util.concurrent.locks.ReentrantLock;
  7. import java.util.concurrent.locks.ReentrantReadWriteLock;
  8. public class ReadWriteLockDemo {
  9. public static void main(String[] args) {
  10. // MyCahe myCahe = new MyCahe();
  11. MyCaheLock myCahe = new MyCaheLock();
  12. //写入
  13. for (int i = 1; i < 6; i++) {
  14. final int temp = i;
  15. new Thread(()->{
  16. myCahe.put(temp+"", temp+"");
  17. },String.valueOf(i)).start();
  18. }
  19. //读取
  20. for (int i = 1; i < 6; i++) {
  21. final int temp = i;
  22. new Thread(()->{
  23. myCahe.get(temp+"");
  24. },String.valueOf(i)).start();
  25. }
  26. }
  27. }
  28. /**
  29. * 自定义缓存 加锁
  30. */
  31. class MyCaheLock{
  32. private volatile Map<String,Object> map = new HashMap<>();
  33. // 读写锁: 更加细粒度的控制
  34. private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  35. private Lock lock = new ReentrantLock();
  36. //存,写入的时候,只希望同时只有一个线程写
  37. public void put(String key,Object value){
  38. readWriteLock.writeLock().lock();
  39. try {
  40. System.out.println(Thread.currentThread().getName()+"写入"+key);
  41. map.put(key, value);
  42. System.out.println(Thread.currentThread().getName()+"写入ok");
  43. } catch (Exception e) {
  44. e.printStackTrace();
  45. }finally{
  46. readWriteLock.writeLock().unlock();
  47. }
  48. }
  49. //读,取
  50. public void get(String key){
  51. readWriteLock.readLock().lock();
  52. try {
  53. System.out.println(Thread.currentThread().getName()+"读取key");
  54. map.get(key);
  55. System.out.println(Thread.currentThread().getName()+"读取ok");
  56. } catch (Exception e) {
  57. e.printStackTrace();
  58. }finally{
  59. readWriteLock.readLock().unlock();
  60. }
  61. }
  62. }
  63. /**
  64. * 自定义缓存
  65. */
  66. class MyCahe{
  67. private volatile Map<String,Object> map = new HashMap<>();
  68. //存,写
  69. public void put(String key,Object value){
  70. System.out.println(Thread.currentThread().getName()+"写入"+key);
  71. map.put(key, value);
  72. System.out.println(Thread.currentThread().getName()+"写入ok");
  73. }
  74. //读,取所有人都可以读!
  75. public void get(String key){
  76. System.out.println(Thread.currentThread().getName()+"读取key");
  77. Object o = map.get(key);
  78. System.out.println(Thread.currentThread().getName()+"读取ok");
  79. }
  80. }

阻塞队列

image.png

image.png
BlockingQueue BlockingQueue 不是新的东西
image.png
什么情况下我们会使用 阻塞队列:多线程并发处理,线程池!
学会使用队列
添加、移除
四组API
image.png

  1. package cn.maidaotech.java07.syncaddlock.queue;
  2. import java.util.concurrent.ArrayBlockingQueue;
  3. /**
  4. * 抛出异常
  5. */
  6. public class Test1 {
  7. public static void main(String[] args) {
  8. //队列的大小
  9. ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
  10. //添加
  11. System.out.println(blockingQueue.add("a"));
  12. System.out.println(blockingQueue.add("b"));;
  13. System.out.println(blockingQueue.add("c"));;
  14. //Exception in thread "main" java.lang.IllegalStateException: Queue full
  15. //System.out.println(blockingQueue.add("d"));
  16. System.out.println("============");
  17. System.out.println(blockingQueue.remove());
  18. System.out.println(blockingQueue.remove());
  19. System.out.println(blockingQueue.remove());
  20. // java.util.NoSuchElementException 抛出异常!
  21. //blockingQueue.remove();
  22. }
  23. }
  1. package cn.maidaotech.java07.syncaddlock.queue;
  2. import java.util.concurrent.ArrayBlockingQueue;
  3. /**
  4. * 有返回值,没有异常
  5. */
  6. public class Tets2 {
  7. public static void main(String[] args) {
  8. //队列的大小
  9. ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
  10. System.out.println(blockingQueue.offer("a"));
  11. System.out.println(blockingQueue.offer("b"));
  12. System.out.println(blockingQueue.offer("c"));
  13. // System.out.println(blockingQueue.offer("d")); // false 不抛出异常!
  14. System.out.println("================");
  15. System.out.println(blockingQueue.poll());
  16. System.out.println(blockingQueue.poll());
  17. System.out.println(blockingQueue.poll());
  18. System.out.println(blockingQueue.poll()); // null 不抛出异常!
  19. }
  20. }
  1. package cn.maidaotech.java07.syncaddlock.queue;
  2. import java.util.concurrent.ArrayBlockingQueue;
  3. import java.util.concurrent.TimeUnit;
  4. /**
  5. * 等待,阻塞(一直阻塞)
  6. */
  7. public class Test3 {
  8. public static void main(String[] args) throws InterruptedException {
  9. //队列大小
  10. ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
  11. //一直阻塞
  12. blockingQueue.put("a");
  13. blockingQueue.put("b");
  14. blockingQueue.put("c");
  15. //blockingQueue.put("d"); // 队列没有位置了,一直阻塞
  16. System.out.println(blockingQueue.take());
  17. System.out.println(blockingQueue.take());
  18. System.out.println(blockingQueue.take());
  19. System.out.println(blockingQueue.take()); // 没有这个元素,一直阻塞
  20. }
  21. }
  1. package cn.maidaotech.java07.syncaddlock.queue;
  2. import java.util.concurrent.ArrayBlockingQueue;
  3. import java.util.concurrent.TimeUnit;
  4. /**
  5. * 等待,阻塞,(等待超时)
  6. */
  7. public class Test4 {
  8. public static void main(String[] args) throws InterruptedException {
  9. //队列的大小
  10. ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
  11. blockingQueue.offer("a");
  12. blockingQueue.offer("b");
  13. blockingQueue.offer("c");
  14. blockingQueue.offer("d",2,TimeUnit.SECONDS); // 等待超过2秒就退出
  15. System.out.println("===========");
  16. System.out.println(blockingQueue.poll());
  17. System.out.println(blockingQueue.poll());
  18. System.out.println(blockingQueue.poll());
  19. blockingQueue.poll(2,TimeUnit.SECONDS); // 等待超过2秒就退出
  20. }
  21. }

SynchronousQueue 同步队列

  1. package cn.maidaotech.java07.syncaddlock.queue;
  2. import java.util.concurrent.BlockingQueue;
  3. import java.util.concurrent.SynchronousQueue;
  4. import java.util.concurrent.TimeUnit;
  5. /**
  6. * 同步队列 和其他的BlockingQueue 不一样, SynchronousQueue 不存储元素
  7. * put了一个元素,必须从里面先take取出来,否则不能在put进去值! 没有容量, 进去一个元素,必须等待取出来之后,才能再往里面放一个元素!
  8. * put、take
  9. */
  10. public class SynchronousQueueDemo {
  11. public static void main(String[] args) {
  12. BlockingQueue blockingQueue = new SynchronousQueue<>();// 同步队列
  13. new Thread(() -> {
  14. try {
  15. System.out.println(Thread.currentThread().getName() + "put 1");
  16. blockingQueue.put("1");
  17. System.out.println(Thread.currentThread().getName() + "put 2");
  18. blockingQueue.put("2");
  19. System.out.println(Thread.currentThread().getName() + "put 3");
  20. blockingQueue.put("3");
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. },"T1").start();
  25. new Thread(()->{
  26. try {
  27. TimeUnit.SECONDS.sleep(3);
  28. System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
  29. TimeUnit.SECONDS.sleep(3);
  30. System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
  31. TimeUnit.SECONDS.sleep(3);
  32. System.out.println(Thread.currentThread().getName()+"=>"+blockingQueue.take());
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. },"T2").start();
  37. }
  38. }

OOM

为out of memory的简称,称之为内存溢出。

Java应用程序在启动时会指定所需要的内存大小,其主要被分割成两个不同的部分,分别为Head space(堆空间-Xmx指定)和Permegen(永久代-XX:MaxPermSize指定),
通常来说,造成如上图异常的基本上程序代码问题而造成的内存泄露。这种异常,通过dump+EMA可以轻松定位。(EMA虽功能强大,但对机器性能内存要求极高)

java.lang.OutOfMemeoryError:GC overhead limit exceeded(超出GC开销限制)
如上异常,即程序在垃圾回收上花费了98%的时间,却收集不回2%的空间,通常这样的异常伴随着CPU的冲高。定位方法同上

Java.lang.OutOfMemoryError: PermGen space(JAVA8引入了Metaspace区域) Metaspace(原空间)
永久代内存被耗尽,永久代的作用是存储每个类的信息,如类加载器引用、运行池常量池、字段数据、方法数据、方法代码、方法字节码等。基本可以推断PermGen占用大小取决于被加载的数量以及类的大小。定位方法同上。

Java遇见的问题以及知识点 - 图11
产生这种异常的原因是由于系统在不停地创建大量的线程,且不进行释放。系统的内存是有限的,分配给JAVA应用的程序也是有限的,系统自身能允许创建的最大线程数计算规则:
(MaxProcessMemory-JVMMemory-ReservedOsMemory)/ThreadStackSize

其中

MaxProcessMemory:指的是一个进程的最大内存

JVMMemory :JVM内存

ReservedOsMemory:保留的操作系统内存

ThreadStackSize:线程栈的大小

从公式中可以得出结论,系统可创建线程数量与分配给JVM内存大小成反比。

总结:本人故障定位中查到的N多OOM问题,原因不外乎以下3类
1、线程池不管理式滥用

2、本地缓存滥用(如只需要使用Node的id,但将整个Node所有信息都缓存)

3、特殊场景考虑不足(如采用单线程处理复杂业务,环境震荡+多设备下积压任务暴增)

===利用gc.log进行OOM监控===

针对JVM的监听,JDK默认提供了如jconsole、jvisualVM工具都非常好用,本节介绍利用打开gc的日志功能,进行OOM的监控。

首先需要对JAVA程序添加执行参数:

-XX:+PrintGC 输出GC日志

-XX:+PrintGCDetails 输出GC的详细日志

-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)

-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)

-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息

-Xloggc:../logs/gc.log 日志文件的输出路径

示例参数配置:

-xmx100M -XX:+PrintGCDetails -Xloggc:../logs/gc.log -XX:+PrintGCDetails -Xmx设置堆内存

输出日志的详细介绍:Java遇见的问题以及知识点 - 图12
总共分配了100M堆内存空间,老年代+年轻代=堆
就堆内存,此次GC共回收了79186-75828=3356=3.279M内存,能回收空间已经非常少了。

同时从PSYoungGen回收10612-7254=3558=3.474M内存

3558-3356=202K,说明有202K的空间在年轻代释放,却传入年老代继续占用

下一条日志输出:

2018-01-03T10:53:52.281+0800: 11.052: [Full GC (Allocation Failure) [PSYoungGen: 7254K->7254K(24064K)] [ParOldGen: 68574K->68486K(68608K)] 75828K->75740K(92672K), [Metaspace: 3357K->3357K(1056768K)], 0.6010057 secs] [Times: user=2.08 sys=0.00, real=0.60 secs]

68574-68486=88K

75828-75740=88K

此次FullGC 只能释放老年代的88K空间

ps.对于分析gc.log,提供一种图形化工具辅助分析

gcviewer-1.3.6

线程池:

三大方法
















image.png[

](https://blog.csdn.net/sunquan291/article/details/79109197)

  1. package cn.maidaotech.java07.syncaddlock.threadpool;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.TimeUnit;
  5. // Executors 工具类、3大方法
  6. public class Demo01 {
  7. public static void main(String[] args) {
  8. ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
  9. // ExecutorService threadPool = Executors.newFixedThreadPool(5); //创建一个固定的线程池的大小
  10. // ExecutorService threadPool = Executors.newCachedThreadPool(); // 可伸缩的,遇强则强,遇弱则弱
  11. try {
  12. for (int i = 0; i < 100; i++) {
  13. final int temp = i;
  14. // 使用了线程池之后,使用线程池创建线程
  15. threadPool.execute(()->{
  16. System.out.println(Thread.currentThread().getName()+"ok========>"+temp);
  17. });
  18. }
  19. } catch (Exception e) {
  20. e.printStackTrace();
  21. }finally{
  22. // 线程池用完,程序结束,关闭线程池
  23. threadPool.shutdown();
  24. }
  25. }
  26. }


7大参数

源码分析

  1. public static ExecutorService newSingleThreadExecutor() {
  2. return new FinalizableDelegatedExecutorService
  3. (new ThreadPoolExecutor(1, 1,
  4. 0L, TimeUnit.MILLISECONDS,
  5. new LinkedBlockingQueue<Runnable>()));
  6. }
  7. public static ExecutorService newFixedThreadPool(int nThreads) {
  8. return new ThreadPoolExecutor(nThreads, nThreads,
  9. 0L, TimeUnit.MILLISECONDS,
  10. new LinkedBlockingQueue<Runnable>());
  11. }
  12. public static ExecutorService newCachedThreadPool() {
  13. return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
  14. 60L, TimeUnit.SECONDS,
  15. new SynchronousQueue<Runnable>());
  16. }
  17. // 本质ThreadPoolExecutor()
  18. public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
  19. int maximumPoolSize, //最大核心线程池大小
  20. long keepAliveTime, //超时了没有人调用就会释放
  21. TimeUnit unit, //超时单位
  22. BlockingQueue<Runnable> workQueue, //阻塞队列
  23. ThreadFactory threadFactory,//线程工厂,创建线程的,一般不用动
  24. RejectedExecutionHandler handle // 拒绝策略)) {
  25. this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  26. threadFactory, defaultHandler);
  27. }

// 本质ThreadPoolExecutor()
public ThreadPoolExecutor(int corePoolSize, //核心线程池大小
int maximumPoolSize, //最大核心线程池大小
long keepAliveTime, //超时了没有人调用就会释放
TimeUnit unit, //超时单位
BlockingQueue workQueue, //阻塞队列
ThreadFactory threadFactory,//线程工厂,创建线程的,一般不用动
RejectedExecutionHandler handle // 拒绝策略)) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
image.png

  1. package cn.maidaotech.java07.syncaddlock.threadpool;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.LinkedBlockingDeque;
  5. import java.util.concurrent.ThreadPoolExecutor;
  6. import java.util.concurrent.TimeUnit;
  7. // Executors 工具类、3大方法
  8. /**
  9. * new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异常
  10. * new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!
  11. * new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
  12. * new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会
  13. 抛出异常!
  14. */
  15. public class Demo2 {
  16. public static void main(String[] args) {
  17. /**
  18. * 拒绝策略
  19. * 1.new ThreadPoolExecutor.DiscardOldestPolicy() // 队列满了,尝试和最早的竞争,也不会抛出异常
  20. * 2.new ThreadPoolExecutor.AbortPlicy() // 银行满了,还有人进来,不处理这个人的,抛出异常
  21. * 会抛出异常 java.util.concurrent.RejectedExecutionException:
  22. * 3.new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!
  23. * 4.new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
  24. */
  25. ExecutorService threadPool = new ThreadPoolExecutor(
  26. 2,
  27. 5,
  28. 3,
  29. TimeUnit.SECONDS,
  30. new LinkedBlockingDeque<>(3),
  31. Executors.defaultThreadFactory(),
  32. new ThreadPoolExecutor.DiscardPolicy());
  33. try {
  34. // 最大承载:Deque + max
  35. // 超过 RejectedExecutionException
  36. for (int i = 1; i < 10; i++) {
  37. final int temp = i;
  38. // 使用了线程池之后,使用线程池来创建线程
  39. threadPool.execute(()->{
  40. System.out.println(Thread.currentThread().getName()+"ok====>"+temp);
  41. });
  42. }
  43. } catch (Exception e) {
  44. e.printStackTrace();
  45. }finally{
  46. // 线程池用完,程序结束,关闭线程池
  47. threadPool.shutdown();
  48. }
  49. }
  50. }

四种拒绝策略:

  1. /**
  2. * new ThreadPoolExecutor.AbortPolicy() // 银行满了,还有人进来,不处理这个人的,抛出异
  3. * new ThreadPoolExecutor.CallerRunsPolicy() // 哪来的去哪里!
  4. * new ThreadPoolExecutor.DiscardPolicy() //队列满了,丢掉任务,不会抛出异常!
  5. * new ThreadPoolExecutor.DiscardOldestPolicy() //队列满了,尝试去和最早的竞争,也不会
  6. 抛出异常!
  7. */

池的最大的大小如何去设置!

了解:IO密集型,CPU密集型:(调优)

  1. package cn.maidaotech.java07.syncaddlock.threadpool;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.LinkedBlockingDeque;
  5. import java.util.concurrent.ThreadPoolExecutor;
  6. import java.util.concurrent.TimeUnit;
  7. /**
  8. * 池的最大的大小如何去设置!
  9. * 了解:IO密集型,CPU密集型:(调优)
  10. */
  11. public class Demo3 {
  12. // 自定义线程池!工作 ThreadPoolExecutor
  13. // 最大线程到底该如何定义
  14. // 1、CPU 密集型,几核,就是几,可以保持CPu的效率最高!
  15. // 2、IO 密集型 > 判断你程序中十分耗IO的线程,
  16. // 程序 15个大型任务 io十分占用资源!
  17. // 获取CPU的核数
  18. public static void main(String[] args) {
  19. //创建线程池
  20. ExecutorService threadPool = new ThreadPoolExecutor(
  21. 2,
  22. Runtime.getRuntime().availableProcessors(),
  23. 3,
  24. TimeUnit.SECONDS,
  25. new LinkedBlockingDeque<>(3),
  26. Executors.defaultThreadFactory(),
  27. new ThreadPoolExecutor.DiscardOldestPolicy());
  28. try {
  29. //用线程池创建线程
  30. for (int i = 1; i < 10; i++) {
  31. threadPool.execute(()->{
  32. System.out.println(Thread.currentThread().getName()+"ok");
  33. });
  34. }
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. }finally{
  38. // 线程池用完,程序结束,关闭线程池
  39. threadPool.shutdown();
  40. }
  41. }
  42. }

理解 HashMap 加载因子 loadFactor

一、何为加载因子?

加载因子是表示Hsah表中元素的填满的程度.若:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.

冲突的机会越大,则查找的成本越高.反之,查找的成本越小.因而,查找时间就越小.

因此,必须在 “冲突的机会”与”空间利用率”之间寻找一种平衡与折衷. 这种平衡与折衷本质上是数据结构中有名的”时-空”矛盾的平衡与折衷.

二、HashMap中的加载因子

HashMap默认的加载因子是0.75,最大容量是16,因此可以得出HashMap的默认容量是:0.75*16=12。
用户可以自定义最大容量和加载因子。
HashMap 包含如下几个构造器:

  1. - HashMap():构建一个初始容量为 16,负载因子为 0.75 HashMap
  2. - HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 HashMap
  3. - HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap

必须掌握的hashcode()方法

一、hashcode是什么?

想要知道这个hashcode,首先得知道hash,通过百度百科看一下:

image.png
hash是一个函数,该函数中的实现就是一种算法,就是通过一系列的算法来得到一个hash值。这个时候,我们就需要知道另一个东西,hash表,通过hash算法得到的hash值就在这张hash表中,也就是说,hash表就是所有的hash值组成的,有很多种hash函数,也就代表着有很多种算法得到hash值,如上面截图的三种,等会我们就拿第一种来说

2、hashcode

有了前面的基础,这里讲解就简单了,hashcode就是通过hash函数得来的,通俗的说,就是通过某一种算法得到的,hashcode就是在hash表中有对应的位置。
每个对象都有hashcode,对象的hashcode怎么得来的呢?
首先一个对象肯定有物理地址,在别的博文中会hashcode说成是代表对象的地址,这里肯定会让读者形成误区,对象的物理地址跟这个hashcode地址不一样,hashcode代表对象的地址说的是对象在hash表中的位置,物理地址说的对象存放在内存中的地址,那么对象如何得到hashcode呢?
通过对象的内部地址(也就是物理地址)转换成一个整数,然后该整数通过hash函数的算法就得到了hashcode。所以,hashcode是什么呢?就是在hash表中对应的位置。
这里如果还不是很清楚的话,举个例子,hash表中有 hashcode为1、hashcode为2、(…)3、4、5、6、7、8这样八个位置,有一个对象A,A的物理地址转换为一个整数17(这是假如),就通过直接取余算法,17%8=1,那么A的hashcode就为1,且A就在hash表中1的位置。
肯定会有其他疑问,接着看下面,这里只是举个例子来让你们知道什么是hashcode的意义。

二、hashcode有什么作用呢?

前面说了这么多关于hash函数,和hashcode是怎么得来的,还有hashcode对应的是hash表中的位置,可能大家就有疑问,为什么hashcode不直接写物理地址呢,还要另外用一张hash表来代表对象的地址?接下来就告诉你hashcode的作用,
1、HashCode的存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的(后半句说的用hashcode来代表对象就是在hash表中的位置)
为什么hashcode就查找的更快,比如:我们有一个能存放1000个数这样大的内存中,在其中要存放1000个不一样的数字,用最笨的方法,就是存一个数字,就遍历一遍,看有没有相同得数,当存了900个数字,开始存901个数字的时候,就需要跟900个数字进行对比,这样就很麻烦,很是消耗时间,用hashcode来记录对象的位置,来看一下。
hash表中有1、2、3、4、5、6、7、8个位置,存第一个数,hashcode为1,该数就放在hash表中1的位置,存到100个数字,hash表中8个位置会有很多数字了,1中可能有20个数字,存101个数字时,他先查hashcode值对应的位置,假设为1,那么就有20个数字和他的hashcode相同,他只需要跟这20个数字相比较(equals),如果每一个相同,那么就放在1这个位置,这样比较的次数就少了很多,实际上hash表中有很多位置,这里只是举例只有8个,所以比较的次数会让你觉得也挺多的,实际上,如果hash表很大,那么比较的次数就很少很少了。
通过对原始方法和使用hashcode方法进行对比,我们就知道了hashcode的作用,并且为什么要使用hashcode了

三、equals方法和hashcode的关系?

通过前面这个例子,大概可以知道,先通过hashcode来比较,如果hashcode相等,那么就用equals方法来比较两个对象是否相等。
用个例子说明:上面说的hash表中的8个位置,就好比8个桶,每个桶里能装很多的对象,对象A通过hash函数算法得到将它放到1号桶中,当然肯定有别的对象也会放到1号桶中,如果对象B也通过算法分到了1号桶,那么它如何识别桶中其他对象是否和它一样呢,这时候就需要equals方法来进行筛选了。
1、如果两个对象equals相等,那么这两个对象的HashCode一定也相同
2、如果两个对象的HashCode相同,不代表两个对象就相同,只能说明这两个对象在散列存储结构中,存放于同一个位置
这两条你们就能够理解了。

四、为什么equals方法重写的话,建议也一起重写hashcode方法?

如果对象的equals方法被重写,那么对象的HashCode方法也尽量重写
举个例子,其实就明白了这个道理,
比如:有个A类重写了equals方法,但是没有重写hashCode方法,看输出结果,对象a1和对象a2使用equals方法相等,按照上面的hashcode的用法,那么他们两个的hashcode肯定相等,但是这里由于没重写hashcode方法,他们两个hashcode并不一样,所以,我们在重写了equals方法后,尽量也重写了hashcode方法,通过一定的算法,使他们在equals相等时,也会有相同的hashcode值。

四大函数式接口(必需掌握)

新时代的程序员:lambda表达式、链式编程、函数式接口、Stream流式计算

函数式接口: 只有一个方法的接口

  1. @FunctionalInterface
  2. public interface Runnable {
  3. public abstract void run();
  4. }
  5. // 泛型、枚举、反射
  6. // lambda表达式、链式编程、函数式接口、Stream流式计算
  7. // 超级多FunctionalInterface
  8. // 简化编程模型,在新版本的框架底层大量应用!
  9. // foreach(消费者类的函数式接口)

Function函数式接口

  1. package cn.maidaotech.java07.syncaddlock.forefunctioninterface;
  2. import java.util.function.Function;
  3. /**
  4. * Function 函数型接口, 有一个输入参数,有一个输出 只要是 函数型接口 可以 用 lambda表达式简化
  5. */
  6. public class Demo01 {
  7. public static void main(String[] args) {
  8. // Function<String, String> function = new Function<String, String>() {
  9. // @Override
  10. // public String apply(String str) {
  11. // return str;
  12. // }
  13. // };
  14. //lamdar简写
  15. Function function = str->{return str;};
  16. System.out.println(function.apply("cccccc"));
  17. }
  18. }

Predicate 断定型接口:

有一个输入参数,返回值只能是 布尔值!

  1. package cn.maidaotech.java07.syncaddlock.forefunctioninterface;
  2. import java.util.function.Predicate;
  3. /**
  4. * 断定型接口(Predicate):有一个输入参数,返回值只能是 布尔值!
  5. */
  6. public class Demo02 {
  7. public static void main(String[] args) {
  8. //判断字符串是合法为空
  9. // Predicate<String> predicate = new Predicate<String>(){
  10. // @Override
  11. // public boolean test(String t) {
  12. // return t.isEmpty();
  13. // }
  14. // };
  15. Predicate<String> predicate = str->{return str.isEmpty();};
  16. System.out.println(predicate.test("as"));
  17. }
  18. }

Consumer 消费型接口

  1. package cn.maidaotech.java07.syncaddlock.forefunctioninterface;
  2. import java.util.function.Consumer;
  3. /**
  4. * Consumer 消费型接口: 只有输入,没有返回值
  5. */
  6. public class Demo03 {
  7. public static void main(String[] args) {
  8. // Consumer<String> consumer = new Consumer<String>(){
  9. // @Override
  10. // public void accept(String t) {
  11. // System.out.println(t);
  12. // }
  13. // };
  14. Consumer consumer = str->{System.out.println(str);};
  15. consumer.accept("asass");
  16. }
  17. }

Supplier 供给型接口

  1. package cn.maidaotech.java07.syncaddlock.forefunctioninterface;
  2. import java.util.function.Supplier;
  3. /**
  4. * Supplier 供给型接口 没有参数,只有返回值
  5. */
  6. public class Demo04 {
  7. public static void main(String[] args) {
  8. // Supplier<Integer> supplier = new Supplier<Integer>(){
  9. // @Override
  10. // public Integer get() {
  11. // System.out.println("get()");
  12. // return 1024;
  13. // }
  14. // };
  15. Supplier supplier = ()->{return 1024;};
  16. System.out.println(supplier.get());
  17. }
  18. }

springboot配置集成log4j

1.添加log4j依赖

  1. <!-- pom.xml添加log4j依赖,最新版本可以去maven网站下载 -->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-log4j</artifactId>
  5. <version>1.3.8.RELEASE</version>
  6. </dependency>

2. log4j配置

log4j日志的配置可以用log4j.xml,log4j.properties这2种形式配置。log4j的配置文件一般放在resources文件夹下。

  1. //简单log4j.properties配置
  2. log4j.rootLogger=INFO,Console,File
  3. log4j.appender.Console=org.apache.log4j.ConsoleAppender
  4. log4j.appender.Console.Target=System.out
  5. log4j.appender.Console.layout = org.apache.log4j.PatternLayout
  6. log4j.appender.Console.layout.ConversionPattern=[%p] [%d{yyyy-MM-dd HH\:mm\:ss}][%c - %L]%m%n
  7. log4j.appender.File = org.apache.log4j.RollingFileAppender
  8. log4j.appender.File.File = C:/Users/10301/Desktop/test/logs/info/info.log
  9. log4j.appender.File.MaxFileSize = 10MB
  10. log4j.appender.File.Threshold = ALL
  11. log4j.appender.File.layout = org.apache.log4j.PatternLayout
  12. log4j.appender.File.layout.ConversionPattern =[%p] [%d{yyyy-MM-dd HH\:mm\:ss}][%c - %L]%m%n

3. 配置完成,Controller中使用log4j

  1. @RestController //声明Rest风格的控制器
  2. @RequestMapping("test")
  3. public class TestController {
  4. Logger logger=Logger.getLogger(TestController.class);
  5. @RequestMapping("logtest")
  6. @ResponseBody
  7. public String logtest(){
  8. logger.fatal("致命错误");
  9. logger.error("严重警告");
  10. logger.warn("警告");
  11. logger.info("普通信息");
  12. logger.debug("调试信息");
  13. return "";
  14. }
  15. }

java stream中Collectors的用法

https://www.cnblogs.com/flydean/p/java-stream-collection.html#collectorscounting

一、什么是泛型

“泛型” 意味着编写的代码可以被不同类型的对象所重用。泛型的提出是为了编写重用性更好的代码。泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数
https://www.cnblogs.com/whatlonelytear/p/11055126.html

可以看到,使用 Object 来实现通用、不同类型的处理,有这么两个缺点:

  1. 每次使用时都需要强制转换成想要的类型
  2. 在编译时编译器并不知道类型转换是否正常,运行时才知道,不安全

实际上引入泛型的主要目标有以下几点:
类型安全

  • 泛型的主要目标是提高 Java 程序的类型安全
  • 编译时期就可以检查出因 Java 类型不正确导致的 ClassCastException 异常
  • 符合越早出错代价越小原则

消除强制类型转换

  • 泛型的一个附带好处是,使用时直接得到目标类型,消除许多强制类型转换
  • 所得即所需,这使得代码更加可读,并且减少了出错机会

潜在的性能收益

  • 由于泛型的实现方式,支持泛型(几乎)不需要 JVM 或类文件更改
  • 所有工作都在编译器中完成
  • 编译器生成的代码跟不使用泛型(和强制类型转换)时所写的代码几乎一致,只是更能确保类型安全而已

当编译器对带有泛型的java代码进行编译时,它会去执行类型检查和类型推断,然后生成普通的不带泛型的字节码,这种普通的字节码可以被一般的 Java 虚拟机接收并执行,这在就叫做 类型擦除(type erasure)。

这证明了,static变量也不认识泛型,其实不仅仅是staic方法、static变量、static块,也不认识泛型,可以自己试一下。总结起来就是一句话:静态资源不认识泛型。