多线程与高并发(二)

【锁的底层实现】:

【 简介 】:

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

[ CAS ] :

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值