【技术笔记-线程1】基本概念、volatile 和 CAS
一、线程相关基本概念
区分程序、进程、线程
1.程序是保存在电脑里的一堆代码,静态的。比如,qq安装后,保存在c盘某个文件夹下一些文件。
2.进程是程序运行起来加载在内存中的代码,是程序的载体。比如,双击QQ运行起来后,分配内存以供运行。
3.什么是线程? 是资源的最小单位,一个进程可能一个或多条线程。
三种启动线程的方式:1.Thread 2.Runnable 3.通过线程池来创建:Executors.newCachedThreadPool
不加synchronized 会脏读,业务上允许脏读就不要加,效率会低百千倍。
synchronized 重入锁,必须是, super时,不可重入就死锁了。
程序出现异常,锁释放了。
synchronized 在1.5实现是重量级的。
锁升级概念:
我就是厕所所长(1 2)
markword 记录这个线程ID(偏向锁) 效率高。
如果有线程征用,升级为自旋锁。 用马桶举例,循环转圈玩。默认旋10次。
还没有得到这把锁,升级为重量级锁,去操作系统申请。等待状态,不占cpu了。
锁只能升级,不能降级。
什么情况下,用自旋锁好?
自旋锁,占用cpu但是不占用操作系统,所以是用户态的,不经过内核态。os锁内核态。
执行时间长,线程数多 用系统锁更好些。
执行时间短,线程数少 用自旋锁synchronized 。
synchronized锁的是对象
不加任何,是锁当前this
锁静态是this.xxx.class
锁定方法和非锁定方法可同时执行
锁升级
偏向锁,自旋锁,重量级锁。
synchronized(Object)
锁的Object,不能用String常量,interger,Long
String常量 会导致不同的线程访问同一个对象。
要是实际工程中,直接规定,不能加头,用锁。
二. volatile 和 CAS
保证线程可见性
MESI
缓存一致性协议(cpu)
禁止指令重排序(cpu)
DCL单例
new Thread(t::m, "t1").start(); //
java 的 匿名函数lambda 2种写法:
(Apple a) -> a.getWeight () Apple: :getWeight
() -> Thread. currentThread()。dumpStack() Thread. currentThread() : :dumpStack
(str, i) ->str. subatring(i) String: :subetring
(String s) -> System.out .println(s) System.out :: println
2.1
加了volatile,内存每次都能读到。
单例模式
1. 饿汉式 private + get方法
初始化后,jvm就帮你初始化
2.调用方法时初始化,懒汉式,带来线程不安全性。
3.线程安全的,在get方法上加synchronized
4.锁细化,先判断线程是否为空再new
5.也是不对的,1 , 2 个线程都判断了,都会初始化。
6.用双重检查,保证线程安全。
超高并发的情况下,如,京东、淘宝的秒杀。会有指令重排序的问题。
加volatile 不允许对这个对象的指令重排序。深追究,jvm的指令重排序,代码级别的,本质是使用cpu的读屏障、写屏障
new 对象过程,分三步,1初始值 2成员变量赋真正初始值 3赋值给栈里变量INSTENCE
volatile不能保证原子性,不能替代synchronized
2.2 锁优化
锁粒度变粗,变细
争论不剧烈的情况下,变细为好。可以使线程争用时间变短,从而提高效率。
当征用锁过多的情况下,锁为粗好。加大锁。
对对象加锁,对象最好加final防止对象变化导致锁无效,异常。
2.2.1 CAS无锁优化,自旋锁
Atomic开头的都是CAS,常见AtomicInteger
例:代码2.2.1
原理:Compare And Set
都是用Unsafe的CompareAndSetxxx,CompareAndExchangexxx等实现的。
cas(v,Expected,NewValue)
先判断是否是我期望的值,不是,说明在我访问中,有人改变了,要重新执行,要不就退出。
思考:假如我判断符合期望,在准备读取的时候有人改变了呢?
cas是cpu原语支持的,指令级别的,不能打断的。
问: 期望值怎么来?比如,列表,往后插,判断索引是否是3. 一般根据业务逻辑判断。
ABA问题,int 、long类型,无所谓,对象就有问题。
解决:加版本号,
A 1.0
B 2.0
A 3.0
AtomicStampedReference 解决(https://www.cnblogs.com/zyrblog/p/9864932.html)
了解Unsafe:等同于c c++的指针
典型的单例
8jdk还是 CompareAndSet
11jdk用了弱指针weakCompareAndSetInt,他的好处是垃圾回收的时候效率高。
还有allocateMemory操作指针。。。
分配 释放 分配 释放
c -> molloc free c++-> new delete
图2.2.1 Unsafe
代码2.2.1
/**
* 解决同样的问题的更高效的方法,使用AtomXXX类
* AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性的
* @author mashibing
*/
package com.mashibing.juc.c_018_00_AtomicXXX;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class T01_AtomicInteger {
/*volatile*/ //int count1 = 0;
AtomicInteger count = new AtomicInteger(0);
/*synchronized*/ void m() {
for (int i = 0; i < 10000; i++)
//if count1.get() < 1000
count.incrementAndGet(); //count1++
}
public static void main(String[] args) {
T01_AtomicInteger t = new T01_AtomicInteger();
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(t::m, "thread-" + i));
}
threads.forEach((o) -> o.start());
threads.forEach((o) -> {
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}