Java并发编程

24 篇文章 0 订阅

线程回顾

线程基本概念

        程序:静态的代码

        进程:进行中的程序,被加载到内存中,是操作系统分配内存的基本单位

        线程:线程是程序处理的基本最小单位,是CPU执行的单位

线程的创建方式

        继承Thread重写run()  创建类的对象

        实现Runnable 接口  重写run()任务 new Thread(任务)

        实现Callable接口   重写call()有返回值,可以抛异常

线程状态

多线程

什么是多线程

        一个程序中,支持同时运行多个线程

        优点:提高程序响应速度,提升硬件(CPU)利用率

        缺点:线程过多占内存,CPU需要处理线程,需要性能能够满足,多线程访问同一个资源

并发编程

什么是并发编程

        并行:在同一个时间节点上,同时发生(是真正意义上的同时执行)

        并发:在一段时间内,对各个事件交替执行

        并发编程:由于大量的请求访问同一个资源会出现线程安全问题,所以需要通过编程来控制解决多个线程依次访问资源,称为并发编程

并发编程的根本原因

        多核CPU

        JMM Java内存模型

                Java内存模型,是Java虚拟机规范的工作模式

                将内存分为主内存和工作内存

                变量数据存储在主内存中,线程在操作变量是,会将主内存中的数据复制一份到工作内存

                在工作内存中操作完成后,再写回到主内存中

多线程核心根本问题

基于Java内存模型的设计,多线程操作一些共享的数据是,出现以下3个问题

        不可见性:分别同时对共享数据操作,彼此之间不可见,操作完协会主内存,有可能出现问题

        无序性:为了性能对一些代码指令的执行顺序重排,以提高速度

        非原子性:       

int i =0;//主内存 预期结果为2,实际值为1
i++;//先把主内存中的数据读到工作内存中有i=0变为i=1,再写回到主内存中
i++;//同时又有一个线程进入,从主内存总获取到i的值为0,再进行++操作,i的值变为1,写回到主内存

解决办法

        让不可见变为可见

        让无序变为不乱序/不重排(有序)

        非原子执行变为原子(加锁)  由于线程切换执行导致

volatile关键字

        volatile 修饰的变量被一个线程修改后,可以在其他线程中立即执行

        volatile 修饰的变量,在执行的过程中不会被重排序执行

        volatile 不能解决原子性问题

                缓存(工作内存)带来了不可见性

                指令重排优化 带来了无序性

                线程切换带来了非原子性

      volatile 底层实现原理  

                在底层指令级别来进行控制

                volatile 修饰的变量在操作前,添加内存屏障,不让其他的指令干扰

                volatile 修饰的变量添加内存屏障之外,还要通过缓存一致性协议(MESI)将数据写回到主内存,其他工作内存嗅探后,把自己的工作内存数据获取,重新从主内存读取最新的数据。

原子性

        只有通过加锁的方式。让线程互斥执行来保证依次只有一个线程对共享资源访问

        synchronized:  关键字  修饰代码块,方法   自动获取锁,自动释放锁

        ReentrantLock:类  只能对某段代码修饰   需要手动加锁,手动释放锁

CAS(Compare-And-Swap)

        在Java中还提供了一些原子类,在低并发情况下使用,是一种无锁实现

        采用CAS机制(Compare-And-Swap(比较并交换))是一种无锁实现,在低并发情况下使用

        采用自选思想。

                第一次获取内存值到工作内存中,存储起来作为预期值

                然后对象数据进行修改

                将工作内存中值写入到主内存,在写入之前需要做一个判断。用预期值与主内存中的值进行比较

                如果预期值与主内存中值一致,说明没有其他线程修改,将更新数的值,写入到主内存

                如果预期值与主内存中值不一致,说明其他线程修改,这时就需要重复操作整个过程

        特点:不加锁,所有的线程都可以对共享数据操作

                适合低并发时使用

                由于不加锁,其他线程不需要阻塞,效率高

        缺点:大并发时,不停的自旋判断,导致CPU占有率高

Java中的锁分类

Java中锁的名词

        乐观锁:认为并发的操作,不加锁的方式是没有问题的,每次操作前判断(CAS,自旋)是否成立,不加锁实现。

        悲观锁:认为并发操作肯定不会有问题,必须加锁,是加锁的实现。

        可重入锁:当一个线程获取到外层方法的同步锁对象后,可以获取到内部其他方法的同步锁

        读写锁:ReentrantReadWriteLock  支持读,写加锁

                            如果都是读操作,那么就不加锁,一旦有写操作,就加锁

        分段锁:不是锁,是一种锁的实现思想,将锁的粒度拆分,提高效率

        自旋锁:不是锁,是以自旋的方式重试获取

        共享锁:读写锁的读锁就是共享锁,读读是不互斥,共享

        独占锁:互斥锁,synchronized   ReentrantLock  属于独占锁

        公平锁:就是可以根据线程先来后到公平的获取锁 例如ReentrantLock 就可以实现公平锁

        非公平锁:就没有先来后到,谁抢到谁获得执行权 ReentrantLock 也可以实现非公平锁 synchronized 是非公平锁

synchronized 锁

                在sunchronized 锁的底层实现中,提供锁的状态,用来区别对待

                这个锁的状态在同步锁对象头中,有一个屈原叫Mark World 中存储

        锁的状态 :    

                无锁状态      

                偏向锁状态   一直是一个线程访问  记录线程的id 快速获取锁

                轻量级锁状态   当所装他为偏向锁时,有继续有其他线程来范围跟,此时升级为轻量级锁  没有获取到锁的线程,不会阻塞,继续不断尝试获取锁

                重量级锁状态   当锁的状态为轻量级锁时,线程自旋达到一定次数,进入到阻塞状态,锁状态升级为重量级锁,等待操作系统调度

AQS(AbstractQueueSynchronized)

        抽象同步队列是juc其他锁实现的基础

       思路:在类中维护一个state变量,然后还维护一个队列,以及获取锁,是方法锁的方法

                当线程创建后,先判断state值,为0,没有线程使用,把state=1,执行完成后将state=0;

                 期间如果有其他线程访问,state=1,将其他线程放入到队列中

ReentrantLock

        非公平

NofairSync
final void lock(){
         if(compareAndSetState(0,1))//线程来到后,直接尝试获取锁,是非公平锁
            setExclusiveOwnerThread(Thread.currentThread());
         else//获取不到
            acquire(1);   
}

        公平实现

FairSync
final void lock(){
            acquire(1);
}

ConcurrentHashMap

        线程安全的HashMap

对象引用

        强引用:即对象有引用指向的,有Object obj = new Object();这种情况下new出来的对象不能被垃圾回收的

        区别与 软引用,弱引用,虚引用

        软引用,弱引用,虚引用都是用来标记对象的一种状态

        当一些对象成为垃圾后,还需要有不同的状态,可以继承

        SoftReference,WeakReference,PhantomReference或者把自己的对象添加到软,弱,虚的对象中

        软引用:如果内存充足的情况下,可以保留软引用对象,

                     如果内存不足,经过一次垃圾回收后任然不够,那么将清除软引用的对象

        弱引用:弱引用管理的对象,只能存活到下一次垃圾回收

        虚引用:和没有任何引用是一样的,只是为了系统的监测

线程池

池的概念

        频繁的创建数据连接对象,销毁,在时间上开销较大

        集合

        事先创建出一些连接对象,每次使用时,从集合中直接获取,用完不销毁,减少创建,销毁。

        在jdk5之后,提供线程池的实现

                使用ThreadPoolExecutor类实现线程池创建管理

                池的好处:减少频繁创建销毁时间,统一管理线程,提高速度

ThreadPoolExecutor

参数     

        corePoolSize:核心线程池大小

        maximumPoolSize:线程池最大数量

        keepAliveTime:非核心线程池中的线程,在多久没有任务执行时,就终止

        unit:为 keepAliveTime 设定单位

        workQueue:一个阻塞队列,用来存储等待的任务。

        ArrayBlockingQueue 有界的阻塞对列,必须给定最大容量

        threadFactory: 线程池工厂

        handler:拒绝策略  核心线程池,阻塞队列,非核心线程池已满,继续有任务,如歌执行

                AboryPolicy(); 抛出异常,拒绝执行

                DiscardOldestPolicy();丢弃等待时间最长的任务

                DiscardPolicy();直接丢弃,不执行

                CallerRunsPolicy();交友当前提交任务的线程执行

                execute();提交任务,没有返回值 和 submit()  提交任务,可以有返回值

                关闭线程池

                        shutdownNow();直接关闭

                        shutdown();不再接受任务,等待任务执行完关闭

ThreadLocal

        本地线程变量,可以为每个线程创建一个变量副本,使得多个线程主键相互隔离不影响

  ThreadLocal底层实现

               为每个当前线程创建了一个ThreadLocalMap,唯一的ThreadLocal对象作为key

  ThreadLoacl内存泄漏问题

                因为ThreadLocal与弱引用有关,key失效后,value还被强引用着,造成内存泄漏

        正确的用法,用完之后,及时调用remove()清除

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值