多线程笔记

1、synchronized

synchronized是一种互斥锁,一次只允许一个线程进入被锁住的代码块,synchronized可以修饰实例方法(锁对象实例),静态方法锁(当前类的class对象),代码块(传入synchronized的对象),在内存中,对象由对象头(markword信息)、对象实际数据、对齐填充组成,JDK早期用的都是重量级锁依赖的是操作系统的mutex相关指令来实现,会有用户态和内核态的转换会有性能损耗,1.6后面改进了有了锁的升级的概念,在jvm层面实现加锁的逻辑。

锁升级

只有一个线程进入临界区,cas对比markword的ID,偏向锁

多个线程交替进入临界区,轻量级锁(自旋锁)10次取锁,没有的话进入等待队列

多个线程同时进入临界区,重量级锁

2、volatile关键字

  • 保证线程可见性

           AB线程都用到一个变量,java默认是一个A线程开始运行时,会把变量值从内存中读到线程             的工作区,在运行过程中都用这个copy的值,并不会每一次都读取堆内存,所以如果B线程             修改了变量的值的话,A线程并不会及时的感知到,使用了volatile关键字的话每一次对变量             的读写都是直接走堆内存

  • 禁止指令重排序

volatile使一个变量在多个线程间可见,volatile关键字并不能保证原子性

3、CAS(compare and swap)

cas(value,expected,newValue)
{
if value=ecpected
   value=newValue
otherwise try again or fail
}

由CPU原语支持

CAS的缺点:会带来ABA问题

A线程读到当前值是10,但是B线程已经把10改成了100,C线程又把100改成了10,虽然对A线程来说当前值是未被修改过的,但我们从上帝视角来看是被BC改过的,当然也可以加一个版本号来解决这个问题

4、JUC同步锁

  • ReentrantLock
  • CountDownLatch
  • CyclicBarrier
  • ReadWriteLock
  • Semaphore
  • LockSupport
  • Exchanger

ReentrantLock(可重入的互斥锁)

可用于替代synchronized,但是比synchronized更加灵活,需要注意的是,必须要必须要必须要手动释放锁,使用syn锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此经常在finally中进行锁的释放;提供了trylock()方法尝试锁定,无法锁定或者在规定时间内无法锁定都不影响方法的继续执行,可以根据trylock的返回值来判定是否锁定

在new ReentrantLock(true),可以设置是公平锁还是不公平锁,true为公平锁,但默认是非公平锁

还可以调用interrupt()打断线程的等待,lockInterruptibly()可以对interrupt()方法做出响应

CountDownLatch

直译的话是倒数开门栓,是一个线程工具类,有一个门栓await()插在哪里使线程等待,等其他的一些线程完成之后调用countdown方法使计数器减1,当减为0的时候才继续执行。

final CountDownLatch latch = new CountDownLatch(3);

latch.countDown();

 latch.await();

CyclicBarrier

循环栅栏,作用是让指定数量的线程完成之后才开始下一步行动

调用await方法就加一次,加到数量开始下一步之后又变成0开始下一步

public class T07_TestCyclicBarrier {
    public static void main(String[] args) {
        CyclicBarrier barrier = new CyclicBarrier(20, new Runnable() {
            @Override
            public void run() {
                System.out.println("开始下一步");
            }
        });

        for(int i=0; i<100; i++) {

                new Thread(()->{
                    try {
                        barrier.await();

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        e.printStackTrace();
                    }
                }).start();
            
        }
    }
}

ReadWriteLock

是一个接口,管理一组锁,读锁和写锁

ReadLock,是这个共享锁,如果t1线程获取了读锁,t2线程来申请读锁的话可以直接访问,t1、t2共享资源,要是t2访问写锁的话就一直等待直到t1释放读锁。

WriteLock。是一个排它锁,只要有线程获取到了写锁,无论其他线程来申请读锁还是写锁都要等待t1释放写锁。

Semaphore

信号量,使用它可以限制同时访问资源的线程个数

举例:

  • 可以把它简单的理解成我们停车场入口立着的那个显示屏
  • 每有一辆车进入停车场显示屏就会显示剩余车位 减1
  • 每有一辆车从停车场出去,显示屏上显示的剩余车辆就会 加1
  • 当显示屏上的剩余车位为 0 时,停车场入口的栏杆就不会再打开,车辆就无法进入停车场了,直到有一辆车从停车场出去为止

Semaphore的几个方法:

acquire():从信号量获得一个许可,在获得之前线程阻塞,否则线程被中断,拿到就许可数1

release():释放一个许可,许可数+1

availablePermits():获得信号量中的可用的许可数

LockSupport

阻塞线程和叫醒线程,可以让线程在任何位置阻塞(park方法),也可以在任何位置唤醒(unpark方法),可以在park方法之前调用unpark方法放行线程。

locksupport与wait、notify的区别

wait和notify都是object中的方法,调用这两方法时必须获取到锁对象,park不需要对象就可以锁住线程(locksupport.lock)

notify只能随机唤醒一个线程,而unlock可以唤醒指定线程

Exchanger

exchanger,用于线程之间数据的交换。它提供了一个同步点,在这个同步点两线程之间可以交换数据,一个线程先调用exchange方法时,他会一直等待另一个线程也执行exchange方法

5、AQS

JUC包中的ReentrantLock,,Semaphore,ReentrantReadWriteLock,CountDownLatch 等等几乎所有的类都是基于AQS实现的。

lock时的方法调用

AQS的结构

AQS 中有两个重要的东西,一个以Node为节点实现的链表的队列(CHL队列),还有一个STATE标志,并且通过CAS来改变它的值

 ReentrantLock的非公平锁的lock过程

  • 首先调用tryAcquire()方法获取到当前线程,getState()为0的话,则拿到锁,使用CAS操作将State变为1并将将线程设置为当前锁的拥有者,如果判定getState不为0的话再判定当前线程是否为锁的拥有者,是则将state+1(可重入)。
  • 若上面的都不满足,则调用acquireQueued(addWaiter(Node.Exclusive,ags))

addWaiter(Node.Exclusive,ags):有一个死循环把节点加到队列的尾巴那里(CAS(oldtail,node))这样避免了整个链表都加锁,Exclusive表示加锁的类型,排他锁。返回节点node

acquireQueued:判定node,如果node的前置节点是head则tryAcquire()尝试拿锁(与head抢锁)抢不到就阻塞,等待前一个节点唤醒,如果前置节点不是head则将前驱节点是waitState设置为SIGNAL,当前线程park挂起

ReentrantLock的非公平锁的unlock过程 

  1. 调用unlock方法实际就是调用AQS的release方法,而release方法会调用子类的tryRelease方法tryRelease会一直把State减为0,说明当前线程已经把锁释放了
  2. 然后从队尾开始往前找State<0并且离head节点最近的节点进行唤醒
  3. 唤醒之后,被唤醒的节点会去CAS获取锁,假如获取到之后会把head节点干掉自己设置成head节点

6、ThreadLocal(线程局部变量)

set(value)方法:拿到当前线程,通过getMap方法获取当前线程的ThreadLocalmap容器,如果有就把ThreadLocal和对应的值value存进去,没有就在当前线程创建一个

get()方法:用了ThreadLocal保证了同一个线程获取一个Connection对象,从而保证一次事务的所有操作需要在同一个数据库连接上

ThreadLocalMap里面是有一个弱引用指向ThreadLocal的,只要调用垃圾回收gc就会被回收

7、java里面的四种引用类型(强、弱、软、虚)

强引用:M m=new M();这个是强引用,不会被垃圾回收

软引用:一个对象只有软引用指向他时,内存不够的时候才会回收(做缓存用,但一般用不上)

弱引用:WeakReference<M> m=new WeakReference<>(new M())

m.get()拿到M的值,弱引用只要调用到垃圾回收gc就会被回收

ThreadLocal里面的弱引用

ThreadLocal<M> t1=new ThreadLocal<>();

t1.set(new M());

t1.remove();

如果使用强引用,即使t1=null,但key的引用还是会指向ThreadLocal对象,所以会有内存泄漏,而使用弱引用则不会,t1=null之后ThreadLocal会被垃圾回收时回收,但这样还是会有内存泄漏问题,因为ThreadLocal被回收之后,key的值会变为null,则整个value再也不会被访问到,依然造成内存泄漏,所以不用了一定要调用remove方法。

虚引用

 虚引用里面的这个M的值是取不到的,但当M被回收的时候queue会有值,虚引用一般用于管理堆外内存的,但M被回收的时候通过queue可以检测到,然后可以清理堆外内存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值