多线程
gcc线程由 jvm 创建,是守护进程
创建线程方法1:

创建线程方法2:

静态代理:


Lamda表达式:

例如:

例2:

停止线程:

循环1000次:1000.for

yield:
仅仅是把当前线程放到就绪队列,不一定成功,下次可能还是它

join:

线程状态:

线程优先级:


例子:

守护线程:

例如:
用户线程执行完毕就结束了,不管守护线程

线程同步:

sleep能放大线程同步的问题
synchronized:能锁住一个方法,成为同步方法,当一个线程还没调用完,另一个线程要调用只能阻塞
也可以通过同步块来实现,()表示锁住具体哪个对象,锁住谁就只能一个一个操作它,当进入到{}里面时,就把它锁住,当出了{}就将对它的锁释放:
下面锁住了对象Account,具体的操作放在了{}

例2:
注意ArrayList线程不安全

copyonwritearraylist是线程安全的,不需要synchronized了:

若线程1获得被synchronize锁住的对象a,线程2获得被synchronize锁住的对象b,1想获得b,2想获得a,此时就死锁了
lock锁是显式的:
lock.lock()和lock.unlock()之间显式加锁和解锁

synchronized和lock对比:


wait,notify:

生产者消费者问题解决:

线程池:

信号灯(设置标志位)解决生产者消费者:

JUC就是java.util .concurrent工具包的简称
wait和sleep:

synchronized多线程例子:

使用lock锁解决:


lock版的生产者和消费者(2个生产者,2个消费者,缓冲区1个格子):
condition是一个监视器

改进:
通过使得进程在不同条件上等待,来按照某个顺序运行这些进程

例1,由于有sleep 1,A线程就先运行了,且锁住了phone,B必须等A运行完了才能运行被synchronized修饰的方法,所以是先发短信:

例2:hello没有被synchronized修饰,可以直接调用,先输出hello:

例3:2个对象,各锁个的,先打电话:

例4,加上static后,synchronized锁的是类,所以先发短信:

例5,2个对象的类模板只有1个,还是先发短信:

例6,锁的不是同一个东西,先打电话:

list多线程不安全:

并发修改异常:

解决方案1用Vector,因为Vector的add有synchronized,3用CopyOnWriteArrayList,底层用的是lock,效率比synchronized高

普通的Hashset也是线程不安全的:

Hashset底层:

hashMap不安全:

使用callalbe开启线程,且得到返回值:

辅助类:CountDownLatch、CyclicBarrier、Semaphore
CountDownLatch(减法计数器):
countdownLatch.awai():当计数值≠0,阻塞,利用这一点,可以让一堆小线程执行完,再执行main线程

CyclicBarrier(加法计数器):
当有7个线程...await()时,才会调用方法体,召唤神龙成功


Semaphore:信号量
new Semaphore(3):是设置信号量初始值
acquire:相当于P操作
release:相当于V操作


读写锁:ReadWriteLock
读的时候可以被多个线程同时读(读锁,共享锁),写的时候只能被一个写(写锁,独占锁)


阻塞队列BlockingQueue :
家族:

BlockingQueue四组API:


同步队列SynchronousQueue:



池化:

使用executor不安全:

三大方法:
池里面只能有单个线程,创建完了销毁:

线程池大小=5,满了就销毁:

遇强则强,如果cpu强的话,池子里面可以容纳很多线程:

7大参数:
方法的底层用的是ThreadPoolExecutor方法

类似银行:核心线程池大小是一般情况下的容纳量1,2,最大...:当阻塞队列也满了,这时候扩增容纳量3,4,5;
keepAliveTime:超过这个时间后,大小由max->core
阻塞队列长度就是定义候选区大小
如果max满了且阻塞队列(候选区)也满了,就使用拒绝策略来拒绝新进来的进程,默认拒绝策略是abortPolicy:

例如直接用底层方法ThreadPoolExecutor创建线程:

四个拒绝策略:

线程池的最大值最好设置为CPU的核数:

函数型接口:


简化:

断定型接口:



Stream流式计算:

可以把Stream当成一个高级版本的Iterator。原始版本的Iterator,用户只能一个一个的遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如“过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等,具体这些操作如何应用到每个元素上,就给Stream就好了!

异步回调1:

异步回调2:
无错误:
因为有了get(),所以才会执行前面的CompletableFuture...来新建线程,线程体是{}里面的,新线程执行完后执行whencomplete的t,u,这是必然要执行的
get()的值是 return 返回的结果,如果线程体执行成功,则get()的结果是1,不执行exceptionally,否则是exception的2

有错误:

forkjoin把大任务拆分成小任务:

特点是工作窃取(如果b线程搞完了工作,可以把a线程没执行完的工作拿过来执行)

测试1:

直接Stream快速计算:
rangeClosed:返回的是一个有序的LongStream。包含开始节点和结束节点两个参数之间所有的参数,间隔为1:(开始节点,结束节点】
reduce(0,(a,b)->a+b):能全部加起来

JMM:java 内存模型
线程A先从主存中提取变量,再放到自己的工作内存,改完后,再写入,再存储到主存


下面的问题:虽然main改变了num,但是新线程依旧用的旧的num值:

Volatile:

上面问题中,给num加上volatile修饰,新线程就能看见num了
下面问题中,加上volatile也没有,但是给add加上synchronized有用

因为add不是原子性操作:

最佳的方案是采用原子类,底层使用CAS,很高效

指令重排:会按照计算机的想法改变指令执行顺序

原理就是在volatile语句的上下面加了屏障:

volatile在单例模式用的最多
饿汉式:

懒汉式(用的时候再创建):

懒汉式在多线程情况下可能出现问题,解决方法1
DCL:

但是会带来指令重排的问题:A线程执行完13,还没有2时,此时B线程执行发现空间已经被占用,直接返回了没初始化对象,就错了
此时给lazyMan加个volatile修饰就好了

但是反射会破坏这种模式,下图生成了2个不同对象:

弥补方法:

但是如果是2个反射对象,就又会出问题
但是反射不能破坏枚举的单例:

jad的反编译比官方的反编译更准确
CAS:

下图如果=2020(expect),就更新为2021

java无法操作内存,可以通过c++来操作内存(native方法),还可以通过unsafe来操作内存
getAndIncrement底层:先从valueoffset(内存地址)获得值,然后如果当前对象内存地址的变量值(var1+var2)是var5,就让这个值更新为var5+var4,while是自旋锁

ABA问题:B线程动了下变量,但是又改了回来,A线程不知情
比如:

解决:原子引用
假定a线程是捣乱的,更新完后,版本就变了,b线程是正常的,当更新时发现版本不是原来版本,就不更新了,返回false

注意:

公平锁和非公平锁:

比如可以通过布尔值设置公平还是非公平:

可重入锁:
一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁;
相当于锁了外面以后,再继续锁里面
比如synchronized版本:

lock版本:

结果都是:

自旋锁:
不断尝试直到成功
使用CAS来自定义自旋锁并使用:

t2得等t1释放锁才能上锁:

死锁排查:
先实现死锁

解决:


公平锁和非公平锁

浙公网安备 33010602011771号