自拟!并发线程死知识!

乐云一
  • 笔记
  • note
About 1977 wordsAbout 7 min

自拟!并发线程死知识!

分布式、缓存、线程、数据库都存在涉及再并发访问,操作的问题;此文章则记录了面对各样的场景和问题的解决方法和理论知识。

线程

多个线程共享一个进程的方法区资源,但每个线程又有自己的程序计数器虚拟机栈本地方法栈

所以 堆溢出栈溢出 不能说的太绝对,但都是危害性极大的异常。

线程组成

程序计数器: 记录当前线程的运行位置,线程切换后能正常运行。

虚拟机栈: 线程存储变量、常量池、操作数等信息空间

本地方法栈: 为被Native本地方法[底层c语言的方法]服务

: 虚拟机中的大头,对象分配内存的空间,所以堆溢出排查,应该首先注意是否对象溢出。

方法区[元空间]: 存储静态信息,已被加载的类、常量、即时编译后的代码等数据

线程状态

                                              ↑[阻塞]

0 [初始化创建] 一 1就绪 = 2 运行 一 结束

                                 ↓等待[wait] 

Synchronized关键字

synchronized属于重量级的锁,效率低下。

但是从Jdk1.6之后,官方对synchronized锁进行了大量优化。比如引入自旋锁、适应性自旋锁、偏向锁等概念。

单例模式

public class SignTest{
    private volatile  SignTest signtest;
    
    private SignTest(){
        
    }
    
    public static SignTest getSign(){
        if(signtest==null){
            synchronized(this.class){
                if(signtest==null){
                    signtest = new SignTest();
                }
            }
        }
    }
    
}

volatile:被volatile 修饰的成员会被所有线程可见,静止指令重排

锁升级

无锁状态:

偏向锁:单个程序用,为偏向锁,锁头线程id,每次进入时看当前线程是否指向锁头线程

轻量级锁:通过CAS竞争锁资源,如果竞争失败则升级,标识有线程抢锁,升级为轻量级锁

重量级锁:轻量级锁自旋等待10次,升级为重量级

ReentrantLock 对象

和synchronized经常一起被抬出来,首先reentrantlock是一个对象。

但是和synchronized一样属于可重入锁,用户和synchronized差不多。

提供lock( )和unlock( )等方法控制锁操作

另外,ReentrantLock还实现了一些高级功能:

等待可中断: 提供的lock.lockInterruptibly()方法,可以让没有获得锁的,处于等待状态的线程中断,让其做其他事。

实现了公平锁: 公平锁指的是,锁会让目前处于等待中的线程先占用。非公平锁,则是不管目前线程是什么状态都会去争夺锁资源。synchronized只能是非公平锁,reentrantlock需要通过ReentrantLock(boolean fair)创建时的构造方法指定是否是公平锁默认为非公平false

可选择性通知线程: 使用newCondition()方法达到和notify()与notiflyall()相同效果。

volatile关键字

volatile是修饰变量的关键字,只能保证数据的可见性,用于解决多个线程之间,变量可见性问题

CPU缓存

CPU缓存通常指的就是CPU内存,与CPU主存的关系相当于,redis和数据库的关系。

在数据运算操作中,先是从缓存中取一个值,计算之后,在从内存 > 主存,进行数据保存。

JMM模型

线程和主存进行数据交换的模型 QQ截图20220404004146.png

为了防止出现,线程A读到主内存,并且更新;线程B读到本地内存中的旧副本值的场景出现。

则需要使用volatile修饰,标明:当前修饰的值被所有线程可见,并且禁止指令重排

并发编程的三个特性

  1. 原子性,
  2. 可见性,当一个线程对变量进行修改时,其他所有线程应该都能看到被修改后的最新值。
  3. 有序性,代码在编译后的执行顺序应该和编译时相同,

ThreadLocal

threadlocal,相当于在每个线程下创建一个单独的空间。

实现原理就是,记录每一个线程的编号。

线程池!!!

优点

  1. 复用线程资源,减少创建和销毁的消耗
  2. 提高响应速度,任务到了直接开始
  3. 统一管理线程

使用

execute()和submit()执行线程方法:

execute: 不会返回执行结果,无法判断任务成功执行与否

submit(): 返回一个Future类型对象,通过这个对象判断任务执行情况

创建

方法一:构造器

构造器参数

1:核心线程池大小。

2:最大线程数量

3:空闲时间

4:设置空闲时间的单位

5:工作队列

6:线程工厂

7:拒绝策略

方法二Executors 方法

  1. FixedThreadPool ,固定线程数量的线程池,如果没有线程空闲,则任务等待
  2. SingleThreadExecutor,创建一个固定数量为1的线程,
  3. CachedThreadPool,创建一个根据实际情况自动扩容的线程,如果没有线程空闲,则新建一个线程加入线程池。

Atomic原子类

原子类的实现逻辑:

通过volatile+native方法,采用CAS思想。

当需要改变原子类的值时,会向将原本的值和期望值进行比较。如果相同则进行更新,而拿到原来的值则是通过Unsafe类的本地方法从内存地址中取出来,并且这个值时通过volatile修饰

线程的并发问题:

当使用到线程时,就不可避免出现资源、锁抢夺问题;

以至于造成,内存泄漏、死锁、数据不一致等问题。

上下文切换导致内存消耗

上下文切换,指线程出现突然结束任务,比如被sleep、wait、阻塞或者时间片用完。CPU会保存当前的线程运行信息,在下一次线程恢复运行时,给予恢复。

当频繁的进行上下文切换时,系统需要消耗大量资源进行保存信息,CPU占用过高,并发异常。

死锁问题

如果死锁以及出现,首先要知道是没有办法依靠系统解决,只能依靠人工的重启、停止服务解决。

所以死锁只能在设计层面打破他出现的四个必要条件任何一个了:

第一,线程的互斥性,由于synchronized 是一种偏向型的排他锁,所以无法控制

第二,线程保持请求,可以一次性请求线程需要的所有锁

第三,不放资源,可以进行判断,当拿不到资源时释放手里的资源。

第四,破坏循环等待

并发容器

ConcurrentHashMap

concurrenthashmap,使用seqment分段锁的形式,让map高效的同时还安全。分段锁即锁住当前操作的结点node,但是不对其他node进行锁操作。

在add()中使用reentantock锁住了方法。

CopyOnWriteArrayList

copyonwrite操作,简单的说就是,当list被修改的时候,copy原来的数据,将被修改的值放入副本中。在将副本的值copy到原先的值中。

ConcurrentLinkedQueue

CAS非阻塞算法实现

BlockingQueue

阻塞队列:

理解为消费者-生成者模型的队列。

ArrayBlockingQueue

读和取的操作都需要拿重入锁,队列容量慢时,放入元素阻塞。为空时,取元素阻塞。

默认情况下为非非公平。

Last update:
Contributors: leyunone
Comments
  • Latest
  • Oldest
  • Hottest
Powered by Waline v2.14.7