【锁的底层实现】:
【 简介 】:
JDK早期的时候是重量级的 , 会去找OS系统申请锁 ,效率非常低。
后来的改进——《锁升级过程》。
【锁升级过程】:
sync( Object )
【偏向锁】:
markword记录这个线程ID( 偏向锁 );如果有线程征用偏向锁的话,这个时候就升级成自旋锁;
【自旋锁】:
自旋锁默认情况下是自旋10次。10次以后升级为重量级锁——去OS中申请资源。
【重量级锁】:
去操作系统申请资源。
【闲聊】:
sync并不比Atmoic原子类的操作慢;
【讨论( 什么情况下使用自旋锁比较合适 )】:
Atomical_Lock用的全都是自旋锁;自旋锁有一个特点,它是占CPU,但是它不访问操作系统,所以它是在用户态去解决问题,它不经过内核态,用户态加锁解锁的效率要比内核态要高。
【 用户态锁 和 内核态锁 比较 】:
内核态不占CPU——在旁边竞争的线程是进入等待队列里,你在那里等着,不占用CPU,什么时候CPU运行了把你叫起来你才运行。
//执行时间长(加锁代码),且线程比较多的用内核态锁( 其它竞争线程可以在等待队列里等待 ),内核态锁即系统锁。
//执行时间短(加锁代码),且线程不是太多。(1个线程执行 ,2W+个线程在自旋 ,效率也是不高的 )。
【day1课后总结】:
-
线程的概念 、 启动方式 、常用方法。
-
synchronized( Object )
//不能用String常量 、Integer 、Long -
【线程同步】:
synchronized
锁的是对象不是代码 ;
锁this ;
锁XXX.class ;
锁定方法和非锁定方法可以同时执行 ;
锁升级过程:
偏向锁;
自旋锁;
系统锁;
自旋锁和系统锁各自的使用场景。
【day2课前复习】:
【锁升级 与 对象头】:
对象头上记录着锁升级的全部信息。
【自旋】:
自旋可以看成是一种积极的排队(在那里转圈),是需要占用CPU时间的。
【 Volatile保证线程可见性 】:
【实验】:
package Ten_Class.t02.no122;
import java.util.concurrent.TimeUnit;
public class T {
/*volatile */ boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别。
void m(){
System.out.println( " m start " );
while (running){ //这里模拟的是服务器的操作~ 服务器如果你不点停止的话,它会一直 7*24小时 一直运行。
//什么时候running被设置为false , 就立即停止。
// try{
// TimeUnit.MILLISECONDS.sleep(10);
// }catch (InterruptedException e){
// e.printStackTrace();
// }
}
System.out.println(" m end ! ! ! ");
}
public static void main(String[] args) {
T t = new T();
new Thread( t::m , "t1" ).start();
//【 lambda表达式 】:
// new Thread( new Runnable( run(){ m(); } ) );
try{
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
t.running = false;
}
}
【最终输出】:
//程序一直没有停止!!!
【修改】:
- 打开volatile
volatile boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别。
【Volatile的两个作用】:
- 保证线程可见性;
MESI
缓存一致性协议
【 Volatile禁止指令重排序 】:
- 禁止指令重排序;
DCL单例( Double Check Lock )
【DCL代码】:
package T04_YouXuXing.T05_singleton;
// DCL Double Check Lock
public class Mgr06 {
private static volatile Mgr06 INSTANCE; //JIT
private Mgr06(){}
public static Mgr06 getInstance(){
//业务逻辑代码省略
if (INSTANCE == null){ //Double Check Lock
synchronized(Mgr06.class) {
if (INSTANCE==null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr06();
}
}
}
return INSTANCE;
}
public void m(){
System.out.println(" m ");
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(
()-> System.out.println( Mgr06.getInstance().hashCode() )
).start();
}
}
}
【本质】:
本质是汇编语言;
【汇编创建对象过程】:
1 )申请空间地址(半初始化)——new
2 )赋值(初始化)——invokespecial
3 )建立关联——astore
//这里面有指令重排序的话,有可能刚申请了空间,还没有赋值,然后就被赋给了变量。这个时候变量里面是没有值的。
//——超高并发的状态,阿里京东是有可能产生的。
【 Volatile不能保证原子性 】:
package Ten_Class.t02.no124;
import java.util.ArrayList;
import java.util.List;
public class T_加了volatile {
volatile int count = 0;
void m(){
for (int i = 0; i < 10000; i++) {
count++;
}
}
public static void main(String[] args){
T_加了volatile t = new T_加了volatile();
List<Thread> threads = new ArrayList<>();
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 );
}
}
//多个线程同时读到这个数,加一之后再写回去,所以浪费了很多机会。
//count这个值是保证了可见性,但是count++这个操作并不是原子性的操作。
【 synchronized优化 】:
//把锁的粒度变细。征用不是特别剧烈的情况下,你的锁的粒度最好要小一些。
【例子】:
m1是错误的 , m2才是正确的。
/**
* synchronized优化
* 同步代码块中的语句越少越好
* 比较m1和m2
*
*/
package Ten_Class.t02.no125;
import java.util.concurrent.TimeUnit;
public class T_FineCoarseLock {
int count = 0;
synchronized void m1() {
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
count++;
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void m2() {
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁。
//采用细粒度的锁,可以使线程争用时间变短,从而提高效率。
synchronized (this) {
count++;
}
//do sth need not sync。
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
【 锁的粗化 】:
//当征用特别频繁的时候——就应该让锁变大变粗。
某段业务逻辑中很多很多细碎的小锁,这个时候还不如直接就给弄成一个大锁,它的征用反而就没那么频繁了。
【 final防止对象改变 】:
/**
* 锁定某对象o,如果o的属性发生改变,不影响锁的使用
* 但是如果o变成另外一个对象,则锁定的对象发生改变
* 应该避免将锁定对象的引用变成另外的对象
*
* @author mashibing
*/
package Ten_Class.t02.no125;
import java.util.concurrent.TimeUnit;
public class T_SyncSameObject {
/*final*/ Object o = new Object();
void m() {
synchronized (o) {
while (true) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
T_SyncSameObject t = new T_SyncSameObject();
new Thread(t::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread t2 = new Thread(t::m, "t2");
t.o = new Object(); //锁对象发生改变,所以t2线程得以执行,如果注释掉这句话,线程2将永远得不到执行机会
t2.start();
}
}
【day1课程简单回顾】:
【读写屏障】:
- LoadFence
- StoreFence
【 CAS(1) 】:
[ 特殊的类 ]:
由于某些特别常见的操作,需要老是来回加锁;——干脆提供常见操作的类,这些类的内部自动实现了锁,这些锁并不是重量级锁,而是CAS号称无锁,
//凡是Atomic开头的,都是用CAS来保证线程安全的类。
【例】:
/**
* 解决同样的问题的更高效的方法,使用AtomXXX类
* AtomXXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性的
*
* @author mashibing
*/
package Ten_Class.t02.no127;
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 < 100; 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);
}
}
【最终输出】:
100000.
【 cas( V , Expected , NewValue ) 】:
cas操作你可以将其想象成一个方法——这个方法有三个参数。
( 1参 ):
要改的那个值。
( 2参 ):
期望当前的值会是多少?
( 3参 ):
要设定的新值。
【 CAS(2) 】:
【 ABA问题 】:
//处理这个问题需要加版本号,加version。
//——用AtomicStampedReference解决ABA问题。
【 ABA问题与数据类型 】:
如果是基础类型,无所谓;
引用类型。
【不需要加锁是怎么做到的呢?】:
- 内部使用的是Unsafe类。
这个类除了反射能用外,其它的不能直接使用。这个类不能直接使用的原因还和ClassLoader有关系。
【Atomic操作】:
所有的Atomic操作内部下面都是CompareAndSet操作。
【 weakCompareAndSetObject Int Long 】:
//通过名字我们就知道应该是用的指针——软弱虚指针。通过指针的好处就是垃圾回收的时候效率比较高。
【Unsafe】:
直接操纵Java虚拟机里面的那些内存。让我们具备了C++写程序的能力。
【分配/释放 内存】:
【 C 】:
malloc free
【 C++ 】:
new delete