多线程与高并发 synchronized 篇

进程 线程 协程/纤程(Quasur)


线程:一个程序里不同的执行路径

public static class T1 extends Thread{@Override public void run(){System.out.println("Override Theme 中的 run 方法");}
}
// 方法一
new MyThread().start();
// 方法二
new MyThread().start();
// 方法三
new Thread(()->{Sout("Hello World!");
})
  • 创建线程的两种方式:

    • 创建一个类,继承Thread,重写方法
    • 定义一个类,实现 Runnable 接口,然后重写 run 方法
  • 启动:

//方法一:
new MyThread().start();//方法二:
new Thread(new MyRun()).start();//方法三:
new Thread(()->{ Sout("Hello World!");});
  • 面试

    • 问:启动线程的三种方式是?
    • 答:
      • 1、从 Thread 继承
      • 2、实现 Runnable 接口
      • 3、从线程池中启动 Executors.newCachedThrad

线程的基本方法

在T1中调用 T2.join();则执行到 join 之后,T1进入等待模式,先执行完T2之后,再返回执行T1。


// sleep 睡眠
Thread.sleep(500); // 毫秒// Yield 让出一下CPU 进入等待队列(如果没有等待的则继续执行)
// 使用场景,较少
Thread.yield();// Join
Thread T1 = new Thread(()->{T1.join();
})
Thread T2 = new Thread(()->{Sout("T2");
})
  • 其他方法
// 暴力结束线程(不建议使用)
.stop()// 唤起线程
.intereptor( )// 获取线程状态。
.getState()

线程的锁


  1. 上锁
private int count = 10;
private Object o = new Object();public void m(){synchronized(0){ //任何县城要执行下面的代码,则必须先拿到ocount--;}
}
// synchronized(this) 等值于 synchronized(方法);
public class T{private static int count  = 10;public synchronized static void m(){ // 等同于synchronized(T.class)count --;}public static void mm(){synchronized(T.class){count--;}}
}

synchronized的特性

1、锁的是对象,不是进程 / 线程 ; 2、能不加synchronized()锁就不加,加锁之后效率极低;


  • 可重入性

    • 一个方法m1加锁,另一个方法m2也加了锁(同一把锁),那么m1是可以调用m2的。
  • 异常的锁

    • 程序中的锁的内容出现了异常,那么该锁将被释放。

synchronized 底层实现

synchronized(Object) ; 括号中一定要是Object对象,不能是String 或其他任何类型;


早期的锁:需要就去找操作系统申请、

发展后的:先乐观锁、后自旋锁、最后找系统实现(重量级锁 | 最浪费时间)

  • 抱着“没有线程跟我争用”的心态去申请一个资源:此时是 偏向锁,只记录ID,不锁(默认没有第二个线程来访问)
  • 如果有线程争用:升级为 自旋锁 循环10次(占用CPU)
  • 10次之后要访问的资源还被锁着?升级为重量级锁 去操作系统申请锁(不占用 CPU)

自旋锁:在用户态解决问题,不经过内核态。

执行时间长的用系统锁,(加锁代码)执行时间特别短,线程较少,用自旋锁。


总结


  • Lock( ) CAS使用自旋
  • synchronized 是一种锁,其锁的目标是 对象 而不是线程 / 进程(用对象代替进程更易操作)
  • 被锁的对象 必须是 Object 类型,不能是 String 或其他对象
  • 锁申请资源的时候一定是 :乐观锁
  • 锁第一次升级的时候一定是:自旋锁(自旋十次,耗CPU,不走内核)
  • 锁第二次升级的时候一定是:重量级锁(内核态,耗内核、耗时,不耗CPU)

多线程与高并发 2 代码优化 及 volatile修饰 篇


volatile 指令介绍

偏向锁 > 循环锁 > 重量级锁


  • 指令介绍

    • volatile // 可变的,易变的
  • 指令功能

    • 保证线程可见性,禁止指令重排序。

    • 保证线程可见性:一个类的值给两个类同时调用,里面的变量改变后无法轻易发现(线程之间不可见)。

      volatile可以让一个线程发生改变之后,另一个线程可以马上知道。

      // 原理:CPU的缓存一致性协议。

    • 禁止指令重排序:CPU迸发执行指令,所以会对指令重新排序,加了volatile来保证重排序。


举例介绍 及 代码优化


  • 饿汉式:(定义类的时候就实例化方法)
public class Mgr01{private static final Mgr01 INSTANCE = new Mgr01();private Mgr01(){};public static Mgr01 getInstance(){return INSTANCE;}public void m() {System.out.println("m");}public static void main(String[] args){Mgr01 m1 = Mgr01.getInstance();Mgr01 m2 = Mgr01.getInstance();System.out.println(m1 == m2);}
}
  • 懒汉式:什么时候调用方法什么时候初始化(类似于懒加载)
public class Lazy{private Lazy(){}//默认不会实例化,什么时候用什么时候newprivate static Lazy lazy=null;public static synchronized Lazy getInstance(){if(lazy==null){lazy=new Lazy();}return lazy;}
}
饿汉式 懒汉式
安全
节省内存
  • 懒汉饿汉合并:

类的定义:

public class Mgr01{
private /*volatile*/ static Mgr0x INSTANCE;
private Mgr0x(){};
public static Mgr01 getInstance(){//以下所有代码写的都是这一个方法
}
}

以下所有方法写的都是上面的 getInstance()方法。

以上方法没有加volatile,最后会写上。


  • 直接判断null
// 先判断是否为空 然后再那啥:
public static Mgr03 getInstance(){if(INSTANCE == null){try{Thread.sleep(1);}catch(InterruotedException e){e.printStackTrace();}INSTANCE = new Mgr03();}return INSTANCE;
}

↑ ↑ ↑ ↑ ↑ 这是一种错误的书写方式,自己抿;

  • 先锁再null
public static synchronized Mgr04 getInstance(){if(INSTANCE == null){try{Thread.sleep(1);}catch(InterruotedException e){e.printStackTrace();}INSTANCE = new Mgr04();}return INSTANCE;
}

↑ ↑ ↑ ↑ ↑ 修改正确,但是违背了 能不加锁就不加锁 原则。

  • 锁细化:
public static Mgr05 getInstance(){if(INSTANCE == null){synchronized (Mgr05.class){try{Thread.sleep(1);}catch(InterruotedException e){e.printStackTrace();}INSTANCE = new Mgr05();}}return INSTANCE;
}

↑ ↑ ↑ ↑ ↑ 这也是一种错误的书写方式(重复初始化);

  • 双重检查:
public static Mgr05 getInstance(){if(INSTANCE == null){synchronized (Mgr05.class){if(INSTANCE == null){try{Thread.sleep(1);}catch(InterruotedException e){e.printStackTrace();}INSTANCE = new Mgr05();}}}return INSTANCE;
}

↑ ↑ ↑ ↑ ↑ 修改正确.......而且锁不加载外面,效率增高~~

  • 关于volatile(主要是 指令重排序 )超高超高迸发的情况可能会发生:
// new对象的三步
INSTANCE = new Mgr06();1. 申请内存并给初始值(int = 0,String = null;)
2. 修改值
3. 将值给对象

volatile 防止第二步第三步会颠倒;

  • 一个求结果是 100000 的小程序
public class T{volatile int count = 0; // 加上vilatilesynchronized void m(){  // 加上 synchronizedfor(int i=0;i<10000;i++){count++;}}public static void main(String[] args){T t = new T();List<Thread> threads = new ArraysList<~>();for(int i=0;i<10;i++){threads.add(new Thread(t::m,"threads-"+i));}threads.forEach((o)->o.start());threads.forEach((o)->{try{o.join();} catch(InterruptedException e){e.printStackTrace();}});System.out.println(t.count);}
}

只有加上了 synchronized & volatile 才能运行出正确结果,其中 synchronized 用来保证原子性


锁优化场景


  • 锁力度变小(争用不是很激烈的话)

如果有一群要争用的代码,那么可以将方法上的 synchronized 写到 count++ 上;

  • 锁力度变大(争用很激烈很频繁的话)

假如一个方法里面 总共 20 行代码,加了19个锁,那不如直接用一个大的锁。


锁的对象被调用


public class = T{Object o = new Object();// 错误修改点synchronized(0){sout("123");}public void zbc(){T t = new T();t.o = "a";}

↑ ↑ ↑ 以上代码错误!以下为修改 ↓ ↓ ↓

final Object o = new Object();

有些类在创建的时候直接加了锁


Atomic 开头的 ( AtomicInteger count = new AtomicInteger( ); // 让count进行原子性加减)


CAS ( Compare And Set ) 无锁优化 乐观锁

在请求的时候就乐观的认为 代码里的值就是我的期望值


cas (V ,Expected,NewValue){if (V == Expected){V = NewValue;}else{tryAgain or fail;}
}

↑ ↑ ↑ 以上是在CPU 原语上的支持,不能被打断。


ABA 问题(与前女友复合之后,其实她已经经历了n个男人;)


有个对象 object == 1;想使用cas把它变成2:

cas(object,1,2);//没有线程进行操作,可以进行更改

如果在更改的时候有一个线程给 object 改成了2,然后又改成了 1 ;在基础类型(如:int)没有影响,但是 Object 对象有影响;

解决方法:做 cas 的时候加个版本号:version

解决方法:使用 AutomicStampedReference ( unsafe 什么时候调用什么时候返回这个值 )


思考


  • 什么是 volatile ? 它有什么用?
  • 什么是 synchronized ? 什么是 CAS ?两者有什么区别?分别在什么场景下使用?
  • 什么时候要对锁进行细化?什么 时候进行泛化?
  • 什么是 ABA 问题?有什么影响?怎么解决?

各式锁的实际应用

公平锁?不公平锁?乐观锁?悲观锁?自旋锁?重量级锁?读写锁?


乐观锁 cas

(要改的对象,期望的值,要给的值)无锁操作,其实是一个乐观锁......cas本身可以看成是一个锁;


  • automic : 一种使用 cas 实现的原子性操作(上篇中提过)

原子操作的简单方法:


函数 效果 备注
AtomicInteger a = new AtomicInteger(0); int a = 0; 创建对象a并且赋初值为0;
a.incrementAndGet( ); i++; 对原值+1后返回;
a.getAndIncrement( ); ++i; 对原值返回后+1;
a.addAndGet(i); a+=i; 返回a+i;
a.getAndAdd(i); a+=i; 返回原值之后给a+i;

在线程很多的情况下:LongAdder(分段锁:在线程多的时候有优势) > Atomic > synchronized。


Synchronized 的可重入性:


//可重入:
synchronized void m1(){
for(int i = 1;i<10;i++){
try{TimeUtil.SECONDS.sleep(1);// 睡一秒}catch(InterruptedException e){e.printStackTrace();}
sout(i);
}}synchronized void m2(){sout("m2...");}public static void main(String[] args){
T01_ReentrantLock1 r1 = new T01_ReentrantLock1();
new Thread(r1::m1).start();
try{
TimeUtil.SECONDS.sleep(1);// 睡一秒
}catch(InterruptedException e){
e.printStackTrace();
}
new Thread(r1::m2).start();
}

输出结果:0 1 23 4 5 6 7 8 9 m2...

代码修改:synchronized

//可重入:
synchronized void m1(){for(int i = 1;i<10;i++){try{TimeUtil.SECONDS.sleep(1);// 睡一秒}catch(InterruptedException e){e.printStackTrace();}sout(i);if(i == 2){new Thread(r1::m2).start();}}
}synchronized void m2(){sout("m2...");}public static void main(String[] args){T01_ReentrantLock1 r1 = new T01_ReentrantLock1();new Thread(r1::m1).start();try{TimeUtil.SECONDS.sleep(1);// 睡一秒}catch(InterruptedException e){e.printStackTrace();}}

输出结果:0 1 2 m2 ... 3 4 5 6 7 8 9


lock():替代 synchronized 的方法;


Lock lock = new ReentrantLock();
  • 特点:

    • 需要手动上锁 lock.lock( );
    • 需要手动解锁lock.unlock( );
    • 防止进程出错而导致死锁,需要try{ …… }catch( ){ …… }

  • 优点:

    • 可以使用tryLock()尝试上锁;

    • synchronized遇到锁之后只能等待,而tryLock()可以自定义等待时间;

    • locked = lock.tryLock(SECONDS(时间长度),TimeUtil.SECONDS(时间格式:秒));

  • 常用方法:

方法 参数 用法
.lock( ); null 锁定
.unlock( ); null 释放
.tryLock(n,TimeUtil.SECONDS); 时间长度
时间单位
等待参数时间过程中:
如果当前进程释放了,则锁定;
不释放则不锁定;
.lockInterruptibly( ); null; 可以相应被打断的锁;
.interrupt( ); Null; 打断这个锁;

公平锁

ReetrantLock lock = new ReentrantLock( true );


  • 概念:

    • 当执行队列中有线程正在排队的时候:

      • 公平锁:继续等待,排队执行;
      • 不公平锁:不等待,直接抢,有可能抢到第一个执行;
  • 创建方式:
    • 在创建锁的时候加个 true 创建出来的就是公平锁;
public class T05_ReentrantLock extends Thread(){private stratic ReentrantLock lock = new ReentrantLock(true);public void run(){for(int i = 0;i<100;i++){lock.lock();try{Sout(Thread.currentThread().getName()+"获得锁");}finally{lock.unlock();}}}
}

一个倒计时的门栓 CountDownLatch


CountDownLatch latch = CountDownLatch( threads.length ); //创建一个length长度的门栓

.await() 阻塞

原join() 当前线程结束自动往前走

.countDown() 原子性--


栅栏工具 CyclicBarrier

循环栅栏工具


// 一个参数:不到20的时候,等待,到了20个,这20个发车,再来的继续等待
CyclicBarrier barrier = new CyclicBarrier(20);
// 两个参数:
CyclicBarrier barrier = new CyclicBarrier(20,run);
run(){ Sout("满员,发车!"); }
//lambdo 表达式
CyclicBarrier barrier = new CyclicBarrier(20,()->Sout("满员,发车!"));

同步进行的 Phaser

按照不同的阶段对线程进行划分。


  • 使用场景:

    • 遗传算法
    • 现实生活一步一步执行的场景(如:婚礼)
    • 像是一个一个栅栏一样

  • 使用方法:

    • 自定义一个类,继承 Phaser类;

      static class MarrigePhaser extends Phaser

    • 重写onAdvance方法;(栅栏被推倒的时候自动调用)

      protected boolean onAdvance(int phase,int registeredParties)


  • 方法:

    phaser.arriveAndAwaitAdvance();    //执行结束,开始等待;
    phaser.arriveAndDeregister();   //执行结束,不进入下一阶段;

读写锁

程序中的读写锁(一种排他锁、共享锁)


  • 概念

    • A进程在读取ABCD的时候,B进程也来读取ABCD,同时发现A进程在读取,则读取成功;
    • A进程在读取ABCD的时候,B进程来修改ABCD,同时发现A进程在读取,若此时更改ABCD的内容,则A进程读取会出问题,所以修改失败;
    • **总结:**两个都是读取的进程可以同时进行,当有 读 进程在进行时,无法进行 写 进程,写同理;
  • 作用

    • 避免 / 减少 脏数据
static ReadWriteLoak readWriteLock = new ReentrantReadWriteLock();
//在 ReentrantReadWriteLock 中 分出一个 `readLock`一个`writeLock`
static Lock readLock = readWriteLock.readLock();
static Lock writeLock = readWriteLock.writeLock();public static void read(Lock lock){try{lock.lock();Thread.sleep(1000);Sout("read over!");// 模拟读取过程}catch(InterruptedException e){e.peintStackTrace();}finally{lock.unlock();}
}public static void write(Lock lock,int a){try{lock.lock();Thread.sleep(1000);Sout("write "+ a +"over!");// 模拟读取过程}catch(InterruptedException e){e.peintStackTrace();}finally{lock.unlock();}
}public static void main(String[] args){Runnable readR = ()->read(lock);    //Runnable readR = ()->read(readLock);Runnable write = ()->write(lock,new Random().nextInt());for (int i=0;i<18;i++)new Thread(readR ).start();for (int i=0;i<2 ;i++)new Thread(writeR).start();}
}

// 如果使用 ReentrantLock的话,以上代码在执行的时候也需要等待一秒;

// 解决方法:将Main方法中的锁换成`Runnable readR = ()-> read(readLock);


Semaphore 一个有意思的线程池

Semaphore s = new Semaphore(x);x是几则这个 < 线程池 > 就 允许几个线程 同时执行。


public static void main(String[] args){Semaphore s = new Semaphore(1);//括号中数字为x时,允许x个线程同时执行// T1 Runningnew Thread(()->{try{s.acquire();// 进来一个进程 1 变成 0 ,别的线程不能执行Sout("T1 Running");Thread.sleep(200);Sout("T1 Running");}catch(InterruptedException e){e.printStackTrace();}finally{s.release();// 离开一个进程 0 变成 1 ,别的线程可以执行}});// T2 Runningnew Thread(()->{try{s.acquire();// 进来一个进程 1 变成 0 ,别的线程不能执行Sout("T2 Running");Thread.sleep(200);Sout("T2 Running");}catch(InterruptedException e){e.printStackTrace();}finally{s.release();// 离开一个进程 0 变成 1 ,别的线程可以执行}});
}

如果x==1则运行结果是T1 T1 T2 T2,否则可能是T1 T2 T1 T2

Exchanger 用于 < ! 两个 ! > 线程交换数据的方法

使用场景:双人游戏中两人交换装备!执行一次就失效,可以循环等待下一次;


public static void main(String[] args){// T1new Thread(()->{String s = "T1";try{s = sxchanger.exchange(s);}cathc(InterruptedException e){e.printStackTrace();}Sout(Thread.currentThread().getName()+""+s);},"t1").start();// T2new Thread(()->{String s = "T2";try{s = sxchanger.exchange(s);}cathc(InterruptedException e){e.printStackTrace();}Sout(Thread.currentThread().getName()+""+s);},"t2").start();
}

线程中有两个变量,分别是 s 和 s (局部变量),两个线程同时执行,最后交换 T1 与 T2 的值;


分布式锁


只是某个类型的锁,将来补充概念。

总结 :


  • 无论何种情况,优先考虑使用synchronized
  • 什么情况下使用lock()?它与 synchronized()相比有什么优点?
  • 为什么使用读写锁?读写锁是怎么实现的?
  • 随机列举一些跟锁一起使用的方法~~
  • 把标题下的各种锁的使用场景和实现方式全都想一遍~


http://www.taodudu.cc/news/show-5383306.html

相关文章:

  • 关于数据库事务、隔离级别、锁的理解与整理
  • Redis基础入门及五大数据结构API使用
  • 多线程基础篇-08
  • 同态加密综述(一)
  • 我理解的数据库事务
  • 【工作流Activiti7】1、入门篇
  • Java高级技术第五章——高并发编程之从synchronized关键字到事务并发的若干问题
  • Redis学习笔记(from狂神说)
  • Pulsar Producer(生产者)
  • 高并发之——死锁,死锁的四个必要条件以及处理策略
  • 表锁,行锁,排他锁,共享锁,悲观锁和乐观锁
  • IDEA报错npm ERR Maximum call stack size exceeded
  • 视频流媒体平台EasyNVR前端打npm包后报Maximum call stack size exceeded错误
  • npm run build打包时提示RangeError:Maximum call stack size exceeded
  • 解决挖矿病毒占用cpu以及误删 ld-linux-x86-64.so.2 文件的问题
  • tensor.stack()
  • Android studio下使用ndk-stack定位crash
  • 5.3运维企业部分--nginx反向代理
  • Docker学习(六):Docker Compose和Docker Stack区别
  • JAVA中stacksize是什么意思_【Java Thread StackSize】如何理解Java中Thread构造器中的stackSize的默认值为0?...
  • 记录一次曲折的开发经历
  • Linux UAC2 功能的设备描述符
  • shell中awk编辑器
  • 数据结构 DAY05 栈的应用之中缀表达式转换
  • Tarjan算法:重边的影响及跑有向图和无向图的区别
  • gerrit push多个commit,其中一个关联的commit被abandon,另一个无法正常merge
  • 计算机键盘上怎么锁屏,电脑上锁屏怎么设置
  • 中途离开电脑怎么一键锁屏
  • 【行研报告】2021麦肯锡中国汽车行业CEO特刊——附下载链接
  • 2020-01-09——麦肯锡2019年中国报告

【Java】多线程与高并发相关推荐

  1. 如何掌握java多线程,高并发,大数据方面的技能?

    https://www.zhihu.com/question/27575123 如何掌握java多线程,高并发,大数据方面的技能? 因为想进入互联网公司,然后发现互联网类型的公司问的主要问题都离不开这 ...

  2. 多线程导出excel高并发_大牛带你深入java多线程与高并发:JMH与Disruptor,确定能学会?...

    前言 今天我们讲两个内容,第一个是JMH,第二个是Disruptor.这两个内容是给大家做更进一步的这种多线程和高并发的一些专业上的处理.生产环境之中我们很可能不自己定义消息队列,而是使用 Disru ...

  3. 总结-Java多线程与高并发简记

    1.什么是多线程? 一个进程可以开启多个线程,每个线程可以并发/并行执行不同任务. 2.Java多线程实现方式    2.1.继承Thread类    2.2.实现Runnable接口方式实现多线程 ...

  4. JAVA多线程下高并发的处理经验

    java中的线程:java中,每个线程都有一个调用栈存放在线程栈之中,一个java应用总是从main()函数开始运行,被称为主线程.一旦创建一个新的线程,就会产生一个线程栈.线程总体分为:用户线程和守 ...

  5. Java多线程、高并发秒杀时MySQL出现死锁原因(Deadlock found when trying to get lock)及对应解决方案

    1. 死锁背景 1.1 在做高并发秒杀中创建订单.减库存步骤时出现异常:MySQLTransactionRollbackException: Deadlock found when trying to ...

  6. java多线程实例_多线程&高并发(全网最新:面试题+导图+笔记)面试手稳心不慌...

    前言 当你开始开始去跳槽面试的时候,明明只是一份15K的工作,却问你会不会多线程,懂不懂高并发,火箭造得让你猝及不防,结果就是凉凉:现如今市场,多线程.高并发编程.分布式.负载均衡.集群等可以说是现在 ...

  7. java基础巩固-宇宙第一AiYWM:为了维持生计,多高(多线程与高并发)_Part1~整起(线程与进程篇:线程概念、线程状态、线程死锁)

    这个题目我感觉很多大哥大姐和我一样,虽然夹在众位大哥大姐中跟着一块喊着"多线程与高并发"的口号,但是这里面其实包含的东西并不像名字里面这么少.现在就开始咱们的旅程吧. 特此感谢,低 ...

  8. java 多线程 安全 源码,纯干货,从源码解析多线程与高并发,再说不会,我再也不踏足IT圈...

    没什么太多说的,多线程与高并发,面试重点,咱直接进入正题,联合底层源码,咱们从源码看一下,多线程与高并发底层的知识点,这也是阿里p8+的面试官建议的学习到的级别java CAS Compare And ...

  9. node和php处理高并发,node.js“多线程”如何处理高并发任务?,nodejs java 高并发

    node.js"多线程"如何处理高并发任务?node . js"多线程"是如何处理高度并发的任务的?,下面的文章介绍了使用nodejs"多线程&quo ...

最新文章

  1. 戴尔存储副总裁谈戴尔-EqualLogic一周年庆
  2. 识别哈希算法类型hash-identifier
  3. 【C语言天天练(二四)】内存分配
  4. STM32程序占用的内存容量计算
  5. C#开发笔记之20-如何用C#深克隆一个对象(优雅方案)?
  6. 【连载】如何掌握openGauss数据库核心技术?秘诀三:拿捏存储技术(2)
  7. DataGradView操作之,列头右键菜单隐藏和显示字段功能
  8. 「干货分享」我所在团队的竞品分析模板--附下载
  9. i7 8750h支持linux,6核神U!i7-8750H游戏本评测:碾压7代
  10. iredMail安装
  11. 视频直播本地测试服务器搭建
  12. 跨域问题的解决-gateway跨域接解决方案,使用CorsWebFilter
  13. 光E电做好个人理财规划理财如此简单
  14. 基于Opencv和STM32物理鼠标的目标跟踪器
  15. 笔记本电脑无法进入睡眠状态_笔记本电脑进入睡眠状态后无法通过鼠标或键盘来唤醒屏幕怎么解决...
  16. mysql 遗失对主机的连接,MySQL远程连接丢失问题解决方法(Lost connection to MySQL server)...
  17. 10月18号 蒟蒻的流水账
  18. php两个手机号正则表达式_php 手机号码验证正则表达式
  19. jmeter代理服务器录制脚本教程(入门篇)
  20. 【SpringBoot】70、SpringBoot实现MySQL数据库自动备份管理系统

热门文章

  1. 干货 | 新时代的 SSR 框架破局者:qwik
  2. LDAP查询数据(JAVA)
  3. Java8 ForkJoinPool(一) 源码解析
  4. WebView播放Swf文件
  5. html文件容易中毒,中毒.html
  6. 3、docker 镜像
  7. Python 多线程加速for循环
  8. COST 231 Hata 模型 自学笔记
  9. 编程狂人|大型系统存储层迁移实践
  10. 淘宝客商品列表api,Onebound全平台电商API