多线程笔记

1.volatile

可见性

指令重排

不能保证原子性

2.ThreadLocal原理

3.HashMap原理-concurrentHashMap

jdk1.8虽然加入红黑树但是能转树的概率 百万分之一

public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
 调用真正的put方法之前 会先算出key的hashcode  
 
 
  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
     	
        Node<K,V>[] tab; Node<K,V> p; int n, i;
     //hashMap默认会创建一个长度为16的node数组
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
     	//n=16  根据当前key的hash值算出在这个数组的下标
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);//没有值直接放入 
        else {//hash冲突
            Node<K,V> e; K k;
            if (p.hash == hash &&//当前放入的key跟原来存在的key相同
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;//替换
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        //判断是否树华,数组长度>=64 ,节点长度>=8
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();//判断是否扩容
        afterNodeInsertion(evict);
        return null;
    }


concurrentHashmap--- 
    //两个线程同时插入hashmap 算出hash值都等于3 3这个地方正好没有值 可能会出现覆盖 
    if (casTabAt(tab, i, null,
                             new Node<K,V>(hash, key, value, null)))
                    break; 
    //两个线程同时插入hashmap  算出hash值等于 3这个位置有了,
synchronized

4.synchronized

查看字节码文件,

同步方法会加上acc_synchronized 来隐式实现。运行时常量池中方法的 ACC_SYNCHRONIZED 标志[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GnwYgM3q-1639824804688)(线程.assets/ACC_Synchronized.png)]

方法级的同步是隐式,即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志区分一个方法是否同步方法。当方法调用时,调用指令将会 检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor, 然后再执行方法

同步代码块会出现**moniterenter moniterexit 指令,**在正常执行完代码或出现一个异常时,锁释放。

堆空间中,一个对象的内存布局包含:

对象头:synchronized 的锁对象就在对象头,主要由Mark Word 和 Class MetaData Address组成的

Mark Word存储对象的hashCode、锁信息(锁标记位、锁ID)、分代年龄或GC标志等信息。

image-20210827115215818

Class MetaData Address:指针指向对象的类元数据

实例变量:存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐

对齐填充:由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐

实例对象。image-20210827095042800

释放锁:1.同步代码块运行完了,2.同步代码块抛异常

javap -verbose xxx.class
同步方法会acc_synchronized
同步代码块会出现 moniterenter moniterexit,完成一个异常----moniterexit

synchronized(对象)
对象在内存中得布局:

​	对象头

​	实例数据

​	对齐填充
java对象头:
new Student()----student.class
class metadata address:指向class对象的指针,虚拟机可以确定该对象时哪个class的实例
mark word:对象的信息,标志位等,实现锁的关键  id属性
		对象的信息:	hashcode,分代年龄:gc 垃圾回收
		锁类型:
		锁标志位: 01(无) 00(轻量级) 10(重量级)  轻量级 重量级不同的 
		还会记录,当前持有该锁的线程是谁
只靠标记是不行:mointer监视器---来完成加锁放锁 也叫管程
ObjectMointer父类 c++实现的,每一个对象都一定会有一个moniter,出现的时间:伴随着对象的出生而出生,第一次去拿对象锁的时候出生,moniterenter  moniterexit
重要属性:

​	waitset:保存抢到该锁以后调用wait方法的线程

​	entrylist:记录没有抢到锁的线程。多个线程  5个线程来抢锁,某个线程抢到锁,对象mark word id=当前线程的id

​	owner:抢到锁的线程     如果释放锁,owner置空

​	count:+1     -1
A线程来了 把owner---改为A count+1 ----B线程来了,A线程还在执行 B进入entrylist等待----A线程执行wait方法,进入waitset里面

jdk6以前,lock比synchronized效率高很多,mutex lock效率低
    synchronized属于重量级锁,效率低下,因为监视器锁(monitor)是依赖于底层的操作系统的Mutex Lock来实现的,而操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高
    
jdk6以后做了优化,
B线程去拿锁 拿不到---转等待状态----B想运行又要转回来--浪费了效率
    
    自旋:多数时候,拿锁就一哈哈儿,如果去切换得不偿失,就可以让其它线程while(true)不释放cpu还更快

​	但是锁的时间长,自旋就长,就更消耗了用户可以更改,参数可以不用管
​	就有了**自适应自旋锁**:由前一次,在同一个锁上自旋时间及锁的持有时间,可以自动调整自旋时间,甚至可以不自旋

**锁消除**

​	stringbuffer 自动消除

​```java
//方法属于线程栈  天生线程安全 内部的东西都是安全的 没必要加锁
    //jvm 会把锁自动消除:锁消除
    public void test(){
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("abc");
    }
​```

**锁粗化:**

​		循环多次stringbuffer 每次调用方法都会加锁
StringBuffer stringBuffer = new StringBuffer();
	public void test(){
	synchronized(){//锁粗化
       while(1000次)
        stringBuffer.append("abc");
        }
    }
会在栈里面复制一份moniter的信息,对象头
对象头会有一个指针

无锁、偏向锁、轻量级锁、重量级锁
偏向锁:只有一个线程 抢锁,本来抢锁需要很多认证,改一些标记,先把这些标记标记为这个线程,减少加锁的流程
(就偏向于他了 mark word(堆中)的id=当前线程的thread id)
轻量级锁:AB交替执行,偏向锁----轻量级锁
轻量级锁:很多线程大家没有规律抢 ----重量级锁

锁的释放依赖moniter:jmm内存模型,1.把数据同步,高速缓冲(栈的内存) 比如读取student对象 修改student对象 写入student对象到堆,另外把其它栈里面有这个对象的全部要求重新读取(可见性) 2.修改对象头信息,锁标志,id,指针

https://www.cnblogs.com/wuzhenzhao/p/10250801.html

https://blog.csdn.net/javazejian/article/details/72828483

https://blog.csdn.net/scdn_cp/article/details/86491792

5.安全集合

vector:所有的方法都加锁,效率较低,不建议使用

读读的问题,

CopyOnWriteArrayList/CopyOnWriteArraySet:写时复制

读写分离的思想

CopyOnWriteArrayList 仅适用于写操作非常少的场景,而且能够容忍读写的短暂不一致

内存占用问题 数据一致性问题

写时复制:复制的那一份去修改,修改完了 替换原来

1.空间浪费

2.数据不一致

适用场景 读多写少

6.死锁

package cn.cdqf.dead;

public class MyDeadLock implements Runnable{
    private MyLock myLock1 = new MyLock();
    private MyLock myLock2 = new MyLock();

    @Override
    public void run() {
        synchronized (myLock1){
            //Thread-1获得了myLock1
            System.out.println(Thread.currentThread().getName()+"获得了myLock1");
            synchronized (myLock2){
                System.out.println(Thread.currentThread().getName()+"获得了myLock2");
            }
        }

        synchronized (myLock2){
            //Thread-0获得了myLock2
            System.out.println(Thread.currentThread().getName()+"获得了myLock2");
            synchronized (myLock1){
                System.out.println(Thread.currentThread().getName()+"获得了myLock1");
            }
        }
    }

    public static void main(String[] args) {
        MyDeadLock myDeadLock = new MyDeadLock();
        new Thread(myDeadLock).start();
        new Thread(myDeadLock).start();
    }
}

class MyLock{}

线程之间互相等待对方释放锁,就是死锁

编号解死锁:对多把锁 按从小到大排序,每个要用多锁的地方,都先锁小的,再锁大的。

package cn.cdqf.deadlock;
//一般syn里面锁芯不用String 因为String会放在字符串常量池,所以容易出现多个地方锁相同
//也少用Integer -128---127被缓存了的 是同一个对象
public class DeadLockTest {
    private MyLock myLock1 = new MyLock(1);
    private MyLock myLock2 = new MyLock(2);

    public void test1(){
        //一个地方 A在前B在后 另外一个地方B在前 A在后
        //现在给锁编号 那么永远小的在前
        if(myLock1.getI()<myLock2.getI()){
            synchronized (myLock1){
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (myLock2){

                }
            }
        }else {
            synchronized (myLock2){
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (myLock1){

                }
            }
        }

    }
    public void test2(){
        if(myLock1.getI()<myLock2.getI()){
            synchronized (myLock1){
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (myLock2){

                }
            }
        }else {
            synchronized (myLock2){
                try {
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (myLock1){

                }
            }
        }
    }
}

7.wait/notify/notifyall

wait():等待,释放锁 来源于 object的 notify------waitset

调用wait方法 这个线程会进入 moniter waitset()---------依赖对象 syn(A)

wait notify notifyall 都需要在同步代码块里面运行-------是同步那个锁芯调用的

Sys…(A){}:A对象锁代码块里面的notify只能叫醒A对象代码里面wait

1.同步代码块里面运行:java.lang.IllegalMonitorStateException:

2.Object方法:wait/notify/notifyall

3.notify随机叫醒一个wait

4.notifyAll当前对象所有的wait()

A B{A.join} B线程需要等到A线程执行结束才继续执行

wait/notify必须要同步代码块中使用

当前所对象的包含的代码中没有wait的线程,没法叫

当前锁对象中的notify/notifyAll只能叫醒,当前锁对象包含中的wait()

wait与notify与notifyAll锁对象是谁就必须用锁对象来调用方法,不然会出现IllegalMonitorStateException

这三个方法都是在Object类,所以每一个对象都有这三个方法

一般都用notifyAll:叫醒当前锁对象中所有的wait方法

notify:随机叫一个

被wait以后会释放锁

8.线程提高效率

多核----多线程肯定能提高效率

即使在一个核里面2个也可以提高效率:

CPU密集型:项目是大量的运算,

IO密集型:大部分io操作

混合型:io/cpu混合的

计算机两大块,某一个块是运行cpu的,另外一个ram运行磁盘–内核

9.线程池

     ThreadPoolExecutor threadPoolExecutor =
                new ThreadPoolExecutor(
                        //核心线程数:线程创建的时候就初始化这么多个线程:
                        //默认核心线程不回收 但是 threadPoolExecutor.allowCoreThreadTimeOut(true);回收时间跟非核心相同
                        // 最大线程数:20-10忙不过来的时候 还可以创建10个线程
                        10,20,
                        //非核心线程数 回收时间:非核心线程 空闲超过这个时间那么就被回收
                        10000,
                        //单位
                        TimeUnit.MILLISECONDS,
                        //任务队列
                        new ArrayBlockingQueue<>(50)
                        //拒绝策略:线程池都忙不过来的时候,可以适当选择拒绝
                        /**
                         *  ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常
                         * ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常:菲律宾真人荷官
                         * ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列
                         * ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务
                         */
                , new ThreadPoolExecutor.AbortPolicy());
                //来了任务 核心线程先上  放入任务队列 忙不过来了  创建非核心线程  还有任务  任务队列满了就拒绝策略

offer(E e): 将给定的元素设置到队列中,如果设置成功返回true, 否则返回false. e的值不能为空,否则抛出空指针异常。

offer(E e, long timeout, TimeUnit unit): 将给定元素在给定的时间内设置到队列中,如果设置成功返回true, 否则返回false.

add(E e): 将给定元素设置到队列中,如果设置成功返回true, 否则抛出异常。如果是往限定了长度的队列中设置值,推荐使用offer()方法。

put(E e): 将元素设置到队列中,如果队列中没有多余的空间,该方法会一直阻塞,直到队列中有多余的空间。

take(): 从队列中获取值,如果队列中没有值,线程会一直阻塞,直到队列中有值,并且该方法取得了该值。

poll(long timeout, TimeUnit unit): 在给定的时间里,从队列中获取值,如果没有取到会抛出异常。

remainingCapacity():获取队列中剩余的空间。

remove(Object o): 从队列中移除指定的值。

contains(Object o): 判断队列中是否拥有该值。

drainTo(Collection c): 将队列中值,全部移除,并发设置到给定的集合中。



https://blog.csdn.net/weixin_37598682/article/details/79895769
n核的个数

n+1:cpu密集型,尽量一个cpu去抛一个线程,减少上下文切换

2n:io密集型 有线程去做io的时候 另外的线程让cpu利用起来

混合型:
n*u*(1+WT/ST)
u:目标cpu利用率
wt:线程等待时间
st:线程运行时间

终端:jvisualvm --profiler--项目---
自用st
总-st=wt

调优
javacodegeeks.com/2012/03/threading-stories-about-robust-thread.html

10.AQS

AQS的源码我简单看了一下,没怎么看懂

我自己的理解就是,aqs是一套jdk帮我们实现了类似于加锁解锁,线程协作,获得许可,释放许可等基本功能的工具体系类,正是基于这些基础的工具,实现大量的比如信号量,ReentrantLock,CyclicBarrier方便开发人员并发类

11.syn与lock区别

获得锁的条件,释放锁,是否公平,原理。。。。。

12.线程在哪儿用的

项目里面哪个场景

13.cas

Compare and Swap:比较并交换 cpu保证的

V A B

V是i的值 1

A现在读取i的值 1

B 改变后的值 2

concurrentHashMap:

AtomicInteger----unsafe compareAndSwapInt

2个操作

比较并交换—比较是否是原来的值 才交换(原子性) 在java里面没有原子,汇编语言 一个指令

ABA问题:AtomicStampedReference—jdk提供的就可以

14.CompletableFuture

15.springboot的线程池

普通配置线程池 @async

定时任务 单独的线程池

16.jvm+redis

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值