Java - J.U.C体系进阶

作者:Kerwin

邮箱:806857264@qq.com

说到做到,就是我的忍道!

juc-sync 同步器框架

同步器名称 作用
CountDownLatch 倒数计数器,构造时设定计数值,当计数值归零后,所有阻塞线程恢复执行;其内部实现了AQS框架
CyclicBarrier 循环栅栏,构造时设定等待线程数,当所有线程都到达栅栏后,栅栏放行;其内部通过ReentrantLock和Condition实现同步
Semaphore 信号量,类似于“令牌”,用于控制共享资源的访问数量;其内部实现了AQS框架
Exchanger 交换器,类似于双向栅栏,用于线程之间的配对和数据交换;其内部根据并发情况有“单槽交换”和“多槽交换”之分
Phaser 多阶段栅栏,相当于CyclicBarrier的升级版,可用于分阶段任务的并发控制执行;其内部比较复杂,支持树形结构,以减少并发带来的竞争

CountDownLatch

注意:CountDownLatch和CyclicBarrier非常相似,且CyclicBarrier是可以重用的,根据具体的场景不同,代码结构不同,其实两者之间可以相互转化,详见CyclicBarrier模块,下文是CountDownLatch-Demo

  1. // 用法比较简单,直接上代码即可
  2. // 1.CountDownLatch的同一对象传递
  3. // 2.构造参数的默认值需要指定
  4. // 3.线程完成的countDown()->会使默认值减一
  5. // 4.主线程awiw()等待,所有线程都countDown之后,主线程执行
  6. // 应用场景:比如五个子线程文件输出导出数据,主线程等所有子线程都完成之后开始压缩操作,上传文件
  7. public class TestCountDownLatch {
  8. /***
  9. * 关键点:面向对象的方式->参数传递,把CountDownLatch进行传递,使其共用同一个参数
  10. * @param args
  11. */
  12. public static void main(String[] args) {
  13. CountDownLatch latch = new CountDownLatch(5);
  14. ExecutorService executorService = Executors.newCachedThreadPool();
  15. MyWoker m1 = new MyWoker("work1", latch);
  16. MyWoker m2 = new MyWoker("work2", latch);
  17. MyWoker m3 = new MyWoker("work3", latch);
  18. MyWoker m4 = new MyWoker("work4", latch);
  19. MyWoker m5 = new MyWoker("work5", latch);
  20. Boss boss = new Boss("boos", latch);
  21. executorService.submit(m1);
  22. executorService.submit(m2);
  23. executorService.submit(m3);
  24. executorService.submit(m4);
  25. executorService.submit(m5);
  26. executorService.submit(boss);
  27. executorService.shutdown();
  28. }
  29. }
  30. class MyWoker implements Callable<String> {
  31. private String name;
  32. private CountDownLatch latch;
  33. public MyWoker (String name, CountDownLatch latch) {
  34. this.name = name;
  35. this.latch = latch;
  36. }
  37. @Override
  38. public String call() throws Exception {
  39. System.out.println(name + " 工人开始工作");
  40. int time = (int)(Math.random() * 100) * 50;
  41. Thread.sleep(time);
  42. System.out.println(name + " 工人已经完成任务!");
  43. latch.countDown();
  44. return "successful";
  45. }
  46. public String getName() {
  47. return name;
  48. }
  49. public void setName(String name) {
  50. this.name = name;
  51. }
  52. public CountDownLatch getLatch() {
  53. return latch;
  54. }
  55. public void setLatch(CountDownLatch latch) {
  56. this.latch = latch;
  57. }
  58. }
  59. class Boss implements Callable<String> {
  60. private String name;
  61. private CountDownLatch latch;
  62. public Boss (String name, CountDownLatch latch) {
  63. this.name = name;
  64. this.latch = latch;
  65. }
  66. @Override
  67. public String call() throws Exception {
  68. System.out.println("老板准备就绪,等工人都完成了就来视察~");
  69. latch.await();
  70. System.out.println("老板来了,快跑啊~");
  71. return "successful";
  72. }
  73. public String getName() {
  74. return name;
  75. }
  76. public void setName(String name) {
  77. this.name = name;
  78. }
  79. public CountDownLatch getLatch() {
  80. return latch;
  81. }
  82. public void setLatch(CountDownLatch latch) {
  83. this.latch = latch;
  84. }
  85. }

CyclicBarrier

CyclicBarrier是一个同步辅助类,它允许一组线程相互等待直到所有线程都到达一个公共的屏障点

一句话概述就是:“人满发车”

重点理解:

CountDownLatch主要用于主线程阻塞,等待子线程执行完毕后,主线程执行,例如报表导出压缩上传,子线程处理报表,主线程等都执行完毕后,压缩,上传

CyclicBarrier侧重点是人满发车,比如LOL,需要等待是个用户都加载好了之后,再开启主线程执行工作,值得注意的是,这是一般意义的CyclicBarrier

但是,CyclicBarrier提供了另一个构造方法,即可以指定默认额外的执行线程

  1. CyclicBarrier barrier = new CyclicBarrier(5, new TotalTask(totalService));

这意味着,在很多情况CyclicBarrier可以代替CountDownLatch,主要看代码的结构设计

比如刚才的问题:报表导出压缩上传,子线程处理报表,主线程等都执行完毕后,压缩,上传

如果我用着这种构造方法,配合awit()的位置,让压缩上传线程默认作为最后执行的线程,即可保证执行的顺序,来看个Demo吧:

Demo-1 CyclicBarrier普通使用方法:

  1. public class TestCyclicBarrier {
  2. public static void main(String[] args) throws InterruptedException {
  3. CyclicBarrier cycli = new CyclicBarrier(10);
  4. for (int i = 0; i < 9; i++) {
  5. new Thread(new BarrierThread("张" + i, cycli)).start();
  6. }
  7. Thread.sleep(3000);
  8. new Thread(new BarrierThread("张" + 10, cycli)).start();
  9. Thread.sleep(5000);
  10. }
  11. }
  12. class BarrierThread implements Runnable{
  13. private String name;
  14. private CyclicBarrier cycli;
  15. public BarrierThread(String name, CyclicBarrier cycli) {
  16. super();
  17. this.name = name;
  18. this.cycli = cycli;
  19. }
  20. @Override
  21. public void run() {
  22. System.out.println(name + " 准备就绪");
  23. try {
  24. cycli.await();
  25. } catch (InterruptedException | BrokenBarrierException e) {
  26. e.printStackTrace();
  27. }
  28. System.out.println(name + " 开始执行");
  29. }
  30. public String getName() {
  31. return name;
  32. }
  33. public void setName(String name) {
  34. this.name = name;
  35. }
  36. public CyclicBarrier getCycli() {
  37. return cycli;
  38. }
  39. public void setCycli(CyclicBarrier cycli) {
  40. this.cycli = cycli;
  41. }
  42. }

Demo-2 CyclicBarrier 另一种构造方法的使用:

注意 CyclicBarrier barrier = new CyclicBarrier(5, new TotalTask(totalService));

再注意子线程中,awit等待的代码位置,这个代码位置在程序的最后面,因此CyclicBarrier 的灵活性完全可以由我们来把控,到底在哪一点阻塞,完全是我们自己控制的,这样的变化,就可以在一定程度上替代CountDownLatch,达到更加灵活的目的,CyclicBarrier 且是可重复使用的,细节可以再去深入了解

  1. /**
  2. * 各省数据独立,分库存偖。为了提高计算性能,统计时采用每个省开一个线程先计算单省结果,最后汇总。
  3. *
  4. * @author guangbo email:weigbo@163.com
  5. *
  6. */
  7. public class Total {
  8. // private ConcurrentHashMap result = new ConcurrentHashMap();
  9. public static void main(String[] args) {
  10. TotalService totalService = new TotalServiceImpl();
  11. CyclicBarrier barrier = new CyclicBarrier(5,
  12. new TotalTask(totalService));
  13. // 实际系统是查出所有省编码code的列表,然后循环,每个code生成一个线程。
  14. new BillTask(new BillServiceImpl(), barrier, "北京").start();
  15. new BillTask(new BillServiceImpl(), barrier, "上海").start();
  16. new BillTask(new BillServiceImpl(), barrier, "广西").start();
  17. new BillTask(new BillServiceImpl(), barrier, "四川").start();
  18. new BillTask(new BillServiceImpl(), barrier, "黑龙江").start();
  19. }
  20. }
  21. /**
  22. * 主任务:汇总任务
  23. */
  24. class TotalTask implements Runnable {
  25. private TotalService totalService;
  26. TotalTask(TotalService totalService) {
  27. this.totalService = totalService;
  28. }
  29. public void run() {
  30. // 读取内存中各省的数据汇总,过程略。
  31. totalService.count();
  32. System.out.println("=======================================");
  33. System.out.println("开始全国汇总");
  34. }
  35. }
  36. /**
  37. * 子任务:计费任务
  38. */
  39. class BillTask extends Thread {
  40. // 计费服务
  41. private BillService billService;
  42. private CyclicBarrier barrier;
  43. // 代码,按省代码分类,各省数据库独立。
  44. private String code;
  45. BillTask(BillService billService, CyclicBarrier barrier, String code) {
  46. this.billService = billService;
  47. this.barrier = barrier;
  48. this.code = code;
  49. }
  50. public void run() {
  51. System.out.println("开始计算--" + code + "省--数据!");
  52. billService.bill(code);
  53. // 把bill方法结果存入内存,如ConcurrentHashMap,vector等,代码略
  54. System.out.println(code + "省已经计算完成,并通知汇总Service!");
  55. try {
  56. // 通知barrier已经完成
  57. barrier.await();
  58. } catch (InterruptedException e) {
  59. e.printStackTrace();
  60. } catch (BrokenBarrierException e) {
  61. e.printStackTrace();
  62. }
  63. }
  64. }

相关方法:

— getParties()

获取CyclicBarrier打开屏障的线程数量,也成为方数。

— getNumberWaiting()

获取正在CyclicBarrier上等待的线程数量。

—await()

—await(timeout,TimeUnit)

—isBroken()

获取是否破损标志位broken的值,此值有以下几种情况:

CyclicBarrier初始化时,broken=false,表示屏障未破损。

如果正在等待的线程被中断,则broken=true,表示屏障破损。

如果正在等待的线程超时,则broken=true,表示屏障破损。

如果有线程调用CyclicBarrier.reset()方法,则broken=false,表示屏障回到未破损状态。

—reset()

使得CyclicBarrier回归初始状态,直观来看它做了两件事:

如果有正在等待的线程,则会抛出BrokenBarrierException异常,且这些线程停止等待,继续执行。

将是否破损标志位broken置为false。

CountDownLatch和CyclicBarrier的主要联系和区别如下:

1.闭锁CountDownLatch做减计数,而栅栏CyclicBarrier则是加计数。

2.CountDownLatch是一次性的,CyclicBarrier可以重用。

3.CountDownLatch强调一个线程等多个线程完成某件事情。CyclicBarrier是多个线程互等,等大家都完成。

4.鉴于上面的描述,CyclicBarrier在一些场景中可以替代CountDownLatch实现类似的功能

Semaphore

信号量Semaphore是一个并发工具类,用来控制可同时并发的线程数,其内部维护了一组虚拟许可,通过构造器指定许可的数量,每次线程执行操作时先通过acquire方法获得许可,执行完毕再通过release方法释放许可。如果无可用许可,那么acquire方法将一直阻塞,直到其它线程释放许可

Semaphore用来控制并发线程数,但有个问题FixedThreadPool也可以控制最大并发数,那两者有何不一样呢?首先,量级来看,Semaphore轻量级,是一个并发工具类,线程池重量级无疑,其次特点来讲,Semaphore内的线程是我们实实在在自己创建的,FixedThreadPool是分配给我们的线程池里面的线程

另外,Semaphore如果默认大小为1的时候,还可以当作互斥锁使用,且有公平锁和非公平锁之分(是否按顺序执行,是则就是公平的,但是非常耗性能)

代码Demo,线程队列和Semaphore配合使用

  1. public class TestQueeThread_2 {
  2. static Semaphore semaphore = new Semaphore(1);
  3. public static void main(String[] args) throws InterruptedException {
  4. System.out.println("begin:" + (System.currentTimeMillis() / 1000));
  5. BlockingQueue<String> myQueue = new ArrayBlockingQueue<String>(1);
  6. for (int i = 0; i < 100; i++) {
  7. new Thread(new Runnable() {
  8. @Override
  9. public void run() {
  10. try {
  11. semaphore.acquire();
  12. String log = myQueue.take();
  13. System.out.println(Thread.currentThread().getName() + ":" + doSome(log));
  14. semaphore.release();
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }).start();
  20. }
  21. for (int i = 0; i < 100; i++) { // 这行代码不能改动
  22. String input = i + "";
  23. myQueue.put(input);
  24. }
  25. }
  26. public static String doSome(String input) {
  27. String output = null;
  28. try {
  29. Thread.sleep(1000);
  30. output = input + ":" + (System.currentTimeMillis() / 1000);
  31. return output;
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. return output;
  36. }
  37. }
  1. // 这种用法可能违背了oracle的本意,我们来看看oracle的官方Demo
  2. // Semaphore是用来约束线程使用共享资源的,控制数据一致性,当然还得是锁
  3. class Pool {
  4. private static final int MAX_AVAILABLE = 100; // 可同时访问资源的最大线程数
  5. private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
  6. protected Object[] items = new Object[MAX_AVAILABLE]; //共享资源
  7. protected boolean[] used = new boolean[MAX_AVAILABLE];
  8. public Object getItem() throws InterruptedException {
  9. available.acquire();
  10. return getNextAvailableItem();
  11. }
  12. public void putItem(Object x) {
  13. if (markAsUnused(x))
  14. available.release();
  15. }
  16. private synchronized Object getNextAvailableItem() {
  17. for (int i = 0; i < MAX_AVAILABLE; ++i) {
  18. if (!used[i]) {
  19. used[i] = true;
  20. return items[i];
  21. }
  22. }
  23. return null;
  24. }
  25. private synchronized boolean markAsUnused(Object item) {
  26. for (int i = 0; i < MAX_AVAILABLE; ++i) {
  27. if (item == items[i]) {
  28. if (used[i]) {
  29. used[i] = false;
  30. return true;
  31. } else
  32. return false;
  33. }
  34. }
  35. return false;
  36. }
  37. }

Exchanger

Exchanger是用作线程并发协作的工具类,简单一句话讲,如果A,B线程都拥有Exchanger对象,如果某一个调用Exchanger的交换方法exchange时候,快的那个会主动等慢的那个(等的意思就是挂起),然后都到位之后,互相唤醒交换数据

代码Demo:

  1. public class ExchangerTest {
  2. static class Producer extends Thread {
  3. private Exchanger<Integer> exchanger;
  4. private static int data = 0;
  5. Producer(String name, Exchanger<Integer> exchanger) {
  6. super("Producer-" + name);
  7. this.exchanger = exchanger;
  8. }
  9. @Override
  10. public void run() {
  11. for (int i = 1; i < 5; i++) {
  12. try {
  13. TimeUnit.SECONDS.sleep(1);
  14. data = i;
  15. System.out.println(getName() + " 交换前:" + data);
  16. data = exchanger.exchange(data);
  17. System.out.println(getName() + " 交换后:" + data);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. }
  23. }
  24. static class Consumer extends Thread {
  25. private Exchanger<Integer> exchanger;
  26. private static int data = 0;
  27. Consumer(String name, Exchanger<Integer> exchanger) {
  28. super("Consumer-" + name);
  29. this.exchanger = exchanger;
  30. }
  31. @Override
  32. public void run() {
  33. while (true) {
  34. data = 0;
  35. System.out.println(getName() + " 交换前:" + data);
  36. try {
  37. TimeUnit.SECONDS.sleep(1);
  38. data = exchanger.exchange(data);
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. }
  42. System.out.println(getName() + " 交换后:" + data);
  43. }
  44. }
  45. }
  46. public static void main(String[] args) throws InterruptedException {
  47. Exchanger<Integer> exchanger = new Exchanger<Integer>();
  48. new Producer("", exchanger).start();
  49. new Consumer("", exchanger).start();
  50. TimeUnit.SECONDS.sleep(7);
  51. System.exit(-1);
  52. }
  53. }
  54. 结果打印:
  55. Consumer- 交换前:0
  56. Producer- 交换前:1
  57. Consumer- 交换后:1
  58. Producer- 交换后:0
  59. Consumer- 交换前:0
  60. Producer- 交换前:2
  61. Producer- 交换后:0
  62. Consumer- 交换后:2
  63. Consumer- 交换前:0
  64. Producer- 交换前:3
  65. Producer- 交换后:0
  66. Consumer- 交换后:3
  67. Consumer- 交换前:0
  68. Producer- 交换前:4
  69. Producer- 交换后:0
  70. Consumer- 交换后:4
  71. Consumer- 交换前:0

我暂时没有碰到用需要此特点的需求,不过很显然它可以用作生产者消费者模式,通过网上的解释来看,Exchanger的实现是非常复杂的,主要是依赖CAS自旋操作

Phase

Phaser 是一个多栅栏的同步工具

phase(阶段) - Phaser也有栅栏,在Phaser中,栅栏的名称叫做phase(阶段),在任意时间点,Phaser只处于某一个phase(阶段),初始阶段为0,最大达到Integerr.MAX_VALUE,然后再次归零。当所有parties参与者都到达后,phase值会递增

parties(参与者) - 其实就是CyclicBarrier中的参与线程的概念,CyclicBarrier中的参与者在初始构造指定后就不能变更,而Phaser既可以在初始构造时指定参与者的数量,也可以中途通过register、bulkRegister、arriveAndDeregister等方法注册/注销参与者

arrive(到达) / advance(进阶) - Phaser注册完parties(参与者)之后,参与者的初始状态是unarrived的,当参与者到达(arrive)当前阶段(phase)后,状态就会变成arrived。当阶段的到达参与者数满足条件后(注册的数量等于到达的数量),阶段就会发生进阶(advance)——也就是phase值+1

  1. public class SwimmerTest {
  2. // 游泳选手个数
  3. private static int swimmerNum = 6;
  4. public static void main(String[] args) {
  5. Phaser phaser = new Phaser(7){
  6. @Override
  7. protected boolean onAdvance(int phase, int registeredParties) {
  8. System.out.println("---------------PHASE[" + phase + "],Parties[" + registeredParties + "] ---------------");
  9. return phase >= 2 || registeredParties == 0;
  10. }
  11. };
  12. for (int i = 0; i < swimmerNum; i++) {
  13. new Thread(new Swimmer(phaser), "swimmer" + i).start();
  14. }
  15. // 主线程到达,开启第二阶段
  16. phaser.arriveAndAwaitAdvance();
  17. // 主线程销毁,开启第三阶段
  18. phaser.arriveAndDeregister();
  19. // 加while是为了防止其它线程没结束就打印了"比赛结束"
  20. while (!phaser.isTerminated()) {}
  21. System.out.println("===== 比赛结束 =====");
  22. }
  23. }
  24. class Swimmer implements Runnable {
  25. private Phaser phaser;
  26. public Swimmer(Phaser phaser) {
  27. this.phaser = phaser;
  28. }
  29. @Override
  30. public void run() {
  31. // 从这里到第一个phaser.arriveAndAwaitAdvance()是第一阶段做的事
  32. System.out.println("游泳选手-" + Thread.currentThread().getName() + ":已到达赛场");
  33. phaser.arriveAndAwaitAdvance();
  34. // 从这里到第二个phaser.arriveAndAwaitAdvance()是第二阶段做的事
  35. System.out.println("游泳选手-" + Thread.currentThread().getName() + ":已准备好");
  36. phaser.arriveAndAwaitAdvance();
  37. // 从这里到第三个phaser.arriveAndAwaitAdvance()是第三阶段做的事
  38. System.out.println("游泳选手-" + Thread.currentThread().getName() + ":完成比赛");
  39. phaser.arriveAndAwaitAdvance();
  40. }
  41. }

上文说到,Phase阶段概念,且注册数量和到达数量一致的之后,就会进入下一个阶段,代码中即是如此,一开始注册七个指标,游泳子线程会运行到达6个,然后由主线程控制到达,进入到下一个阶段,注意的是参与的线程可以注册也可以销毁,所以主线程阶段二是到达,阶段三测试了销毁

Phaser 的onAdvance方法:

  1. Phaser phaser = new Phaser(7){
  2. @Override
  3. protected boolean onAdvance(int phase, int registeredParties) {
  4. System.out.println("---------------PHASE[" + phase + "],Parties[" + registeredParties + "] ---------------");
  5. return phase >= 2 || registeredParties == 0;
  6. }
  7. };

当前阶段,最后一个线程到达后,会触发onAdvance方法,此处是打印了信息,且写明了Phaser终止的标志,注册线程数为0或阶段数到达2 (0,1,2)

J.U.C体系进阶(四):juc-sync 同步器框架的更多相关文章

  1. J.U.C体系进阶(二):juc-locks 锁框架

    Java - J.U.C体系进阶 作者:Kerwin 邮箱:806857264@qq.com 说到做到,就是我的忍道! juc-locks 锁框架 接口说明 Lock接口 类型 名称 void loc ...

  2. J.U.C体系进阶(五):juc-collections 集合框架

    Java - J.U.C体系进阶 作者:Kerwin 邮箱:806857264@qq.com 说到做到,就是我的忍道! juc-collections 集合框架 ConcurrentHashMap C ...

  3. J.U.C体系进阶(三)- juc-atomic 原子类框架

    Java - J.U.C体系进阶 作者:Kerwin 邮箱:806857264@qq.com 说到做到,就是我的忍道! juc-atomic 原子类框架 AtomicInteger AtomicInt ...

  4. J.U.C体系进阶(一):juc-executors 执行器框架

    Java - J.U.C体系进阶 作者:Kerwin 邮箱:806857264@qq.com 说到做到,就是我的忍道! 主要内容: juc-executors 执行器框架 juc-locks 锁框架 ...

  5. 网站开发进阶(四十四)input type="submit" 和"button"的区别

    网站开发进阶(四十四)input type="submit" 和"button"的区别   在一个页面上画一个按钮,有四种办法: 这就是一个按钮.如果你不写ja ...

  6. mxgraph进阶(四)mxGraph再启程

    mxgraph进阶(四)mxGraph再启程 前言   小论文Constructing User Interaction Behaviors Net from System Log. (AICE 20 ...

  7. Java进阶(四十七)Socket通信

    Java进阶(四十七)Socket通信   今天讲解一个 Hello Word 级别的 Java Socket 通信的例子.具体通讯过程如下: 先启动Server端,进入一个死循环以便一直监听某端口是 ...

  8. Java进阶(四十三)线程与进程的区别

    Java进阶(四十三)线程与进程的区别 1.线程的基本概念   概念:线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必 ...

  9. Java进阶(四十二)Java中多线程使用匿名内部类的方式进行创建3种方式

    Java中多线程使用匿名内部类的方式进行创建3种方式 package cn.edu.ujn.demo; // 匿名内部类的格式: public class ThreadDemo { public st ...

随机推荐

  1. 涨姿势了解一下Kafka消费位移可好?

    摘要:Kafka中的位移是个极其重要的概念,因为数据一致性.准确性是一个很重要的语义,我们都不希望消息重复消费或者丢失.而位移就是控制消费进度的大佬.本文就详细聊聊kafka消费位移的那些事,包括: ...

  2. C#结构体struct -0029

    结构体 有时候我们仅需要一个小的数据结构,类提供的功能多于我们需要的功能:考虑到性能原因,最好使用结构体. 结构体是值类型,存储在栈中或存储为内联(如果结构体是存储在堆中的另一个对象的一部分). 例如 ...

  3. 07.Easymock的实际应用

    第一步下载对应的java包添加到工程中 并静态导入所需要的j类 import static org.easymock.EasyMock.*; 这里有的注意点 package com.fjnu.serv ...

  4. java中HashMap和Hashtable的区别

    1.HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,在只有一个线程访问的情况下, ...

  5. java多线程并发执行demo,主线程阻塞

    其中有四个知识点我单独罗列了出来,属于多线程编程中需要知道的知识: 知识点1:X,T为泛型,为什么要用泛型,泛型和Object的区别请看:https://www.cnblogs.com/xiaoxio ...

  6. java代理,静态代理、jdk代理、cglib代理、Aspectj

    我实在接触spring的时候才接触到代理这个东西的,一直想整理一下笔记. 什么是代理模式:代理模式是通过代理对象访问目标对象,这样可以在目标对象基础上增强额外的功能.简单来说就是要创建一个新的对象,我 ...

  7. MAC安装VMware fusion

    1.下载VMware fusion 11 https://www.vmware.com/cn/products/fusion/fusion-evaluation.html 2.安装后启用输入注册码 V ...

  8. slow SQL

    一.介绍 慢查询日志可用于查找需要很长时间才能执行的查询,因此是优化的候选者.但是,检查长慢的查询日志可能是一项耗时的任务. 二.配置 # 查看: slow_query_log 慢SQL开关 slow ...

  9. spring boot 整合Thymeleaf模板

    SpringBoot 是为了简化 Spring 应用的创建.运行.调试.部署等一系列问题而诞生的产物,自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置,我们只需遵循规范,引入相关的依赖 ...

  10. 虚拟机 - NAT模式下设置静态 IP 地址

    背景 如果不给虚拟机设置静态 IP 地址的话,每次重启机器都会自动分配一个新的 IP 如果有多台虚拟机的话,也会动态获取 IP 动态IP的话,每次 设置静态 IP 的步骤 查看本机 IP 和网关 cm ...