多线程与高并发(马士兵)

概况

6大块内容:
1.基本概念
2.JUC同步工具
3.同步容器
4.线程池
5.高频面试加分项
6.Disruptor

第一节 线程的基本概念

1.基本的概念

	   进程、线程
		#### 1.创建线程的方式
		1.new Thread().start()
		2.new Thread(Runnable).start()
		3.Executors.newCachedThreadPool()或者FutureTask + Callable

		#### 2.线程的3个方法
		1.**sleep**
		意思就是睡眠,当前线程暂停一段时间让给别的线程去运行 。
		sleep是怎么复活的?
		由你的睡眠时间而定,等睡眠到规定的时间自动复活。
        2.**yield**
        就是当前线程正在执行的时候停止下来进入等待队列,回到等待队列里在系统的调度算法里头还是依然有可能把你刚回去的这个线程	              
        拿回来继续执行,当然,更大的可能性是把原来等待的那些拿出来一个执行,所以yield的意思是我让出一下cpu,后面你们能不能抢到
        那我不管。
        3.**join**
        意思就是在自己当前线程加入你调用join的线程,本线程等待。等调用线程运行完了,自己再去执行。t1和t2两个线程,在t1的某个
        点上调用了t2.join,它会跑到t2去运行,t1等待t2运行完毕继续t1运行(自己join自己没有意义)
        
		  #### 3.线程的状态

在这里插入图片描述
1.新建状态:当我们new一个线程时,还没有调用start();
2. Runnable状态:(1).Ready就绪状态 (2).Running运行状态
3.Terminated结束状态:线程执行结束
4.TimedWaiting等待状态
5.Waiting等待状态
6.Blocked阻塞状态

4.锁的概念

synchronized
多个线程去访问同一个资源的时候对这个资源上锁。

5.锁的特性
//1.缺点:每次都要new一个新的对象出来加锁
private Object o = new Object();
public void m(){
	synchronized(o){
	}
}
//2.用this对象
public void m(){
	synchronized(this){
	}
}
//3.加在方法上,这个等同于synchronized(this)
public synchronized void m(){
}
//4.静态方法是没有this对象的,可以在方法上加synchronized或者synchronized(T.class)
public class T {
	public synchronized static void m(){
	}

	public static void mm(){
		synchronized(T.class){
		}
    }
}

同步方法和非同步方法是否可以同时调用? 可以。

public class T{
	public synchronized void m1(){
    }
	public void m2(){
	}
	public static void main(String[] args){
		T t = new T();
		new Thread(()->t.m1(),"t1").start();
		new Thread(()->t.m2(),"t2").start();
	}
}

同步方法和非同步方法可以同时调用会存在一个问题:
比如有个“账户”对象,写方法设置金额(加锁),读方法获取金额(不加锁),这时候同时调用,有可能读到中间状态,存在“脏读”。

6.锁的可重入属性

一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁。

	public synchronized void m1(){
	    //通俗的说:自己已经申请了这把锁,如果不可重入,那自己会阻塞住,等待自己释放锁,那就死锁了。
        m2();
   }
   public synchronized void m2(){
   }
7.异常与锁
程序在执行过程中,如果**出现异常**,默认情况**锁会被释放**。所以 ,在并发处理的过程中,有异常要多加小心,不然可能会发生不一致的情况。
8.synchronized的底层实现

锁升级:
最开始synchronized都要去找操作系统申请锁,效率很低,后来第一个去访问某把锁的线程,比如sync(Object),先在这个Object的头
上面markword记录这个线程,如果只有第一个线程访问的时候,实际上是没有给这个Object加锁的,在内部实现的时候,只是记录
这个线程的ID(偏向锁)。
如果有线程争用,发现对象头记录的线程ID不同,就升级为自旋锁,自旋锁转圈十次之后,升级为重量级锁
偏向锁------>自旋锁------->重量级锁 (随着竞争情况升级)
我就是厕所所长一
我就是厕所所长二

执行时间短(加锁代码),线程数少,用自旋锁
执行时间长,线程数多,用系统锁

synchronized(Object) 不能用String常量、Integer、Long
锁的是对象,不是代码

第二节 volatile与CAS

volatile使一个变量在多个线程间可见

1.保证线程可见性
-----MESI协议(缓存一致性协议)
堆内存是所有线程共享的内存,除了共享内存之外,每个线程都有自己的专属区域,自己的工作内存
如果说在共享内存里有一个值的话,某个线程去访问的时候,会将这个值copy一份到自己的工作空间,然后
对这个值的任何改变,首先在自己的空间里进行改变,改完之后会马上写回去,什么时候去检查有没有新值,
也不好控制。
在这个线程里面发生的改变,并没有及时的反应到另外一个线程里面,这就是线程之间的不可见。对这个变量
值加了volatile之后就能保证一个线程的改变,另外一个线程马上就能看到。
2.禁止指令重排序(CPU)
----Double Check Lock(双重检查),在饿汉式单例中有应用
cpu原来执行一条指令的时候是一步一步的顺序执行,但是为了提高效率,现在的cpu会把指令并发的执行,第一
个指令执行到一半的时候,第二个指令可能就已经开始执行了,这叫做流水线式的执行。
在这种新的架构的设计基础之上,想充分的利用这一点,那么就要求你的编译器把你的源码编译完的指令之后,可能
进行一个指令的重新排序。

volatile只能保证线程的可见性,但不能保证线程同步

      所以volatile不能替代synchronized

synchronized优化

1.锁膨胀(锁升级)
JDK1.6之前,synchronized是重量级锁,在释放和获取锁时都会从用户态转换成内核态,而转换的效率是比较低的。但有了锁膨胀机制之后,synchronized的状态就多了无锁、偏向锁以及轻量级锁了,这时在进行并发操作时,大部分的场景不需要用户态到内核态的转换了,这样就大幅提升了synchronized的性能。
在这里插入图片描述

2.锁消除
锁消除指的是在某些情况下,JVM虚拟机如果检测不到某段代码被共享和竞争的可能性,就会将这段代码所属的同步锁消除掉,从而提高
程序性能的目的。
在这里插入图片描述
以上代码经过编译之后的字节码:
在这里插入图片描述
从上述结果可以看到,之前我们写的线程安全的StringBuffer对象,在生成字节码之后就被替换成不加锁不安全的StringBuilder对象了,原因是StringBuffer的变量属于一个局部变量,并且不会从该方法中逃逸出去,所以我们可以使用锁消除(不加锁)加速程序的运行。

3.锁粗化
锁粗化是指将多个连续的加锁、解锁操作连接在一起,扩展成一个范围更大的锁。
在这里插入图片描述
这里我们不考虑编译器优化,如果在for循环中定义锁,那么锁的范围很小,但每次for循环都要进行加锁和解锁的操作,性能是很低的;但如果我们直接在for循环的外层加一把锁,那么对于同一个对象操作这段代码的性能会提高很多。
在这里插入图片描述

4.自适应自旋锁
自旋锁是指通过自身循环,尝试获取锁的一种方式,伪代码如下:
在这里插入图片描述
自旋锁的优点在于它可以避免一些线程的挂起和恢复操作,因为挂起线程和恢复线程都需要从用户态转换成内核态,这个过程是比较慢的。所以通过自旋的方式可以一定程度上避免线程挂起和恢复所造成的性能开销。

CAS

   1. CAS号称是无锁优化,或者叫自旋。
   2.Atomic类
   3.CAS是cpu的原语支持, 也就是说CAS操作是CPU指令级别上的支持,中间不能被打断。
   4. ABA问题:
  				(1).如果是基础类型,无所谓,不影响结果值;
 				   (2).如果是引用类型,就像是你的女朋友和你分手之后又复合,中间经历了别的男人
    5.无锁机制是怎么实现的?因为使用了Unsafe类。
	//当且仅当内存值V等于旧的预期值Expected,将内存值V修改成NewValue
    cas(V,Expected,NewValue)
   	    if V==Expected
   	       V = NewValue
		   ohterwise try again or fail

Atomic/Synchronized/LongAdder三者效率的比较

经过测试,启用1000个线程 ,每个线程1000000次递增,最后花费的时间如下:
Atomic: 1781ms
Sync: 9998ms
LongAdder: 256ms

为什么Atomic要比Sync快?
因为Atomic利用的是CAS无锁机制,synchronized需要操作系统申请重量级锁,所以synchronized效率会偏低。

LongAdder为什么要比Atomic效率高?
是因为LongAdder的内部做了一个分段锁

ReentrantLock

1.可重入锁,synchronized也是可重入锁
2.ReentrantLock必须手动解锁,一定要写在try…finally里面。
3.使用tryLock方法进行尝试锁定
4.ReentrantLock还可以用lockInterruptibly()方法对interrupt方法作出响应
5.在new ReentrantLock(true)传入true,表示
公平锁

CountDownLatch

1.countDown()是在原基础上减1,await()直到数字减为0,门栓才会打开。

CyclicBarrier

1.循环栅栏,比如车子只要坐满20人就发车,周而复始。

public static void main(String[] args) throws InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(20, new Runnable() {
            @Override
            public void run() {
                System.out.println("满20人,发车");
            }
        });

        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

Phaser

1.按照不同的阶段来对线程进行执行,每个阶段有些线程可以继续往前走,有些线程到某个阶段就停止了
在这里插入图片描述
2.arriveAndAwaitAdvance()
3. protected boolean onAdvance(int phase, int registeredParties){…}

ReadWriteLock

1.读写锁,读锁就是共享锁,写锁就是排他锁。
2.适合读多写少的场景:
读线程进来的时候我们大家一块读,因为你不改原来的内容,写线程上来把整个线程全锁定,你先不要读,等我写完你再读。

    static ReadWriteLock lock = new ReentrantReadWriteLock();
    //读锁
    static Lock readLock = lock.readLock();
    //写锁
    static Lock writeLock = lock.writeLock();

Semaphore

信号灯,permits是许可的数量。
类似限流,一堆请求,只有拿到信号灯的才允许通过,不然只能等着获取释放后的信号灯。

Semaphore s = new Semaphore(2,true);
...
 try {
       s.acquire();
       .....
 }finally {
       s.release();
 }

Exchanger

交换器,两人之间互相交换数据,比如游戏中两个人装备互换。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值