Java线程和并发

进程与线程:

1、进程指的是运行中的程序,是程序的一次执行过程。当我们在电脑上运行各种软件时,都会开启对应的线程。程序是我们写出的静态的代码,而进程是建立在代码运行中,是一个动态的概念(有自身的产生、存在、消亡过程)

2、线程是由进程创建的,是进程的一个实体,一个进程可以拥有多个线程(如迅雷内下载可以多个文件同时下载,此时就是多线程执行)

3、单线程:同一时刻,只允许执行一个线程;多线程:同一时刻可,可以执行多个线程

4、并发:同一时间段(间隔非常小),多个任务交替执行,其时间间隔非常非常小,造成一种貌似同时的错觉。单核CPU实现多任务就是并发

5、并行:同一时刻,多个任务同时执行。多核CPU可实现不同任务就是并行

6、并发和并行是可以一起产生的

线程使用:

1、创建线程的两种方式:继承Thread类,重写run();实现Runnable接口,重写run()

2、重写的run()内一般是写自己的业务代码,Thread实现了Runnable接口,run()就是在Runnable内的

3、当主线程main启动一个子线程Thread-0后,main线程并不会阻塞,会继续执行

4、jconsole命令可以监控线程状态

5、在多线程下,主线程main结束后,不一定意味着进程就结束了,若其他子线程还在执行,进程也会继续

6、子线程也可以开线程

7、我们要开启线程,要调用start()而不是run(),run()只是一个普通方法,用来实现业务逻辑,调用start()会在底层调用start0(),此方法是一个native本地方法,由JVM调用,底层是由C/C++实现的

8、start()调用start0()后,该线程并不一定会马上执行,只是将该线程变成了可运行状态。具体什么时候调用,取决于CPU

9、由于Java是单继承的,若一个类已经继承了一个类,我们就无法通过继承Thread类来将该类当做线程类使用,这时候就需要实现Runnable接口,来满足需求。使用该方法要注意,无法直接创建实现类对象,并调用start()方法,需要创建一个Thread对象,然后将该实现类传入Thread对象,然后使用Thread对象调用start(),本质是使用了静态代理模式

10、使用实现Runnable接口方式更加适合多个线程共享一个资源的情况

线程通知结束:

1、可以在子线程设置一个循环终止变量,这样可以由主线程主动修改循环终止变量的值,进而通知子线程结束

线程常用方法:

1、interrupt()方法,线程的中断,当线程调用该方法后,该线程run()方法会出现中断,我们可以通过try-catch捕获,进行业务处理

2、join()方法,线程的加入,当线程调用该方法时,会强制将该线程全部走完后,再执行其他线程

3、yield()方法,线程的礼让,当线程调用该方法时,CPU会先执行其他线程,但礼让时间不确定,所以礼让也不一定成功(切换到Ready状态)

4、用户线程(工作线程):当线程任务执行完或以通知方式结束

5、守护线程:一般为用户线程服务,当用户线程结束,守护线程自动结束(如GC)

6、setDaemon()方法,设置守护线程

线程生命周期:

1、对于线程的生命周期,有很多种说法,5种,6种,7种的说法都有。5种的说法比较少,更多的是6种或7种,我们按源码给的枚举类,是有6种状态,而说有7种状态的是将Runable状态细分为了Ready和Running2种


①NEW:新建状态

②RUNNABLE:可运行状态

③BLOCKED:同步阻塞

④WAITING:等待状态

⑤TIMED_WAITING:有具体时间的等待状态

⑥TERMINATED:终止状态

线程同步:

1、为了解决多窗口售卖出现数据错误等多线程问题,此时就使用同步访问技术,保证数据在任何同一时刻,最多被一个线程访问,以保证数据完整性

2、线程同步:即当有一个线程在对内存或数据进行操作时,其他线程都不可以对内存或数据进行操作,直到该线程完成操作,其他线程才能对该内存或数据操作

3、实现同步的方式:使用synchronized同步代码块或synchronized同步方法。就类似于一个线程对象开始操作时,抢到了synchronized上的锁,只有当该线程执行完后,再把锁归还,所有线程再去争夺这把对象锁

4、synchronized上的锁,我们称为互斥锁,每个对象都对应于一个可称为互斥锁的标记,来保证任一时刻,只能有一个线程访问该对象

5、同步非静态方法的锁可以是this(默认为this),也可以是其他同一对象

6、同步静态方法的锁为当前类本身(当前类.class)

7、当使用继承Thread方式创建线程对象时,其互斥锁的对象要注意是共享的,使用this很可能无法锁住(关键点就是多个线程的锁对象必须为同一个)

线程死锁:

1、多个线程占用了对方的锁资源,各线程想继续向下,需要获得对方的锁,但是各线程都处于了阻塞状态,就会导致死锁,我们需要避免

释放锁:

1、会释放锁的操作:

①当前线程的同步方法、同步代码块执行完毕

②当前线程在同步方法、同步代码块中遇到break或return

③当前线程在同步方法、同步代码块中出现了异常

④当前线程在同步方法、同步代码块中执行了wait()方法,当前线程暂停

2、不会释放锁的操作:

①当前线程在同步方法、同步代码块中执行了sleep()或yield()方法

②当前线程在同步方法、同步代码块中执行时,其他线程调用额了suspend()方法

并发工具类:

1、并发工具类三大分类:

①为了并发安全:互斥同步、非互斥同步、无同步方案

②管理线程、提高效率

③线程协作

线程池(ThreadPool):

1、软件中的池,可以理解为计划经济,放入池中的资源是可以复用的,不需要重新创建和销毁,可以复用每一个线程,还可以控制资源的总量

2、如果我们不适用线程池,每个任务都开一个新线程处理,这样开销就太大了,我们希望有固定数量的线程来执行任务,这样就避免了反复创建并销毁所带来的开销问题

3、线程池构造器的参数:

4、线程池添加线程规则:

①若线程池内线程数<核心线程数,即使其他工作线程处于空闲状态,也会创建一个新线程来运行新任务

②若线程池内线程数≥核心线程数,且<最大线程数,则将任务放入队列(3种最常见的队列:直接队列SynhronousQueue、无界队列LinkedBlockingQueue、有界队列ArrayBlockingQueue)

③若队列已满,且线程数<最大线程数,则创建一个新线程来运行任务

④若队列已满,且线程数≥最大线程数,则拒绝该任务

5、使用自动创建(参数提前被设置好),如Executors.newFixedThreadPool(最大线程数)、Executors.newSingleThreadExecutor()、Executors.newCachedThreadPool()创建线程池,都可能造成OOM,所以我们一般手动去创建线程池

6、线程池内的线程数量设定为多少比较合适?

①CPU密集型(加密,计算hash),最佳线程数为CPU核心数的1~2倍

②耗时IO型(读写数据库,文件,网络读写):最佳线程数为CPU核心数的很多倍

可参考公式:线程数量=CPU核心数×(1+平均等待时间/平均工作时间)

Java并发文章1——可见性、原子性和有序性问题:

1、还在单核CPU时代时,所有线程都是在操作同一个CPU的缓存(共享数据),其中一个线程对缓存(共享数据)的写,对其他缓存(共享数据)一定是可见的。即多个线程同时访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值,我们称之为可见性

2、步入多核CPU时代后,可见性就没那么容易保证了,当多个线程在不同的CPU上执行时,各个CPU从内存中取出的变量,对于各个线程来说就不再具备可见性了

3、在上面我们了解了并发,是多个任务交替执行,应用到Java多线程中,就是多个线程交替执行。如果以操作系统的知识进行解释,每个线程会在执行非常小的一小段时间(时间片),进行任务(线程)切换,让其他任务(线程)再去执行一个时间片,不断重复

4、Java并发程序就是基于多线程,自然也会涉及到时间片切换,而很多并发问题就是因为时间片切换和原子性的结合所导致的

5、在高级语言中,一条语句其实并不是像我们看上去那样,如count++,count += 1,我们总是下意识地认为这些操作是一个不可分割的整体,就像原子一样,但其实上面这两条语句在字节码中(或者按汇编语言说明),至少需要3条CPU指令:

①需要把变量 count 从内存加载到 CPU 的寄存器(MOV AX, COUNT)

②在寄存器中执行 +1 操作(INC AX)

③将结果写入内存(MOV COUNT, AX)

6、原子性:一个操作或多个操作要么全部执行完成且执行过程不被中断,要么就不执行(可以通过 synchronized和Lock实现)

7、有序性:程序执行的顺序按照代码的先后顺序执行

常见线程池:

1、FixedThreadPool(线程数固定,不会超出指定范围)

2、CachedThreadPool(可缓存,还可以自动回收多余线程)

3、ScheduledThreadPool(支持定时及周期性任务执行)

4、SingleThreadExecutor(单线程的线程池)

5、WorkStealingPool(JDK1.8后加入,其加入队列的基本都是子任务)

关闭线程池相关方法:

1、shutDown():调用该方法后,后续的新提交任务就会被拒绝,但已经在线程池内的方法会继续被执行

2、shutDownNow():调用后,强制中断任务的执行,并且返回一个未被执行的线程集合List\

3、isShutDown():检测是否执行了shutDown()方法

4、isTerminated():检测线程池内线程是否执行完

5、awaitTermination():检测指定时间内,线程是否执行完

任务太多,怎么拒绝:

1、当线程池已经被关闭(shutDown),提交新任务会被拒绝

2、当线程池的最大线程数和工作队列使用边界均已饱和时,提交新任务会被拒绝

线程池家族分析:

1、线程池由线程池管理器、工作线程、任务队列、任务接口组成

2、Exceutor体系:Exceutor(顶级接口,只有一个execute()方法)、ExceutorService(子接口,继承了Exceutor,多了几个接口方法,如shutDown()方法)、Exceutors(工具类,帮助我们快速创建线程池)

3、线程池实现任务复用的原理,是使用相同的线程执行不同任务,底层是runWorker()方法获取到任务后再调用run()方法执行

线程池状态:

①RUNNING:接收新任务并处理排队任务
②SHUTDOWN:不接收新任务,但处理排队任务
③STOP:不接收新任务,也不处理排队任务,并中断正在进行的任务
④TIDYING:所有任务都已执行完毕,workerCount为0
⑤TERMINATED:terminate()执行完成

ThreadLocal概述:

1、字面理解ThreadLocal就是本地线程,所想表达的就是一个对象的副本,只能被该线程本地使用,其他线程不能使用该副本,同时每个线程都有这样一个对象副本,都使用自己的,这样就不存在多线程的共享安全问题

2、使用场景:

①每个线程需要一个独享的对象(通常是工具类,如日期格式转换类和随机类)

②每个线程内需要保存全局变量,可以让不同的方法直接使用,避免参数传递的麻烦

可以发现在场景②中使用ThreadLocal,即可不影响性能,也无需层层传递参数

Java并发文章2——Java内存模型,看Java如何解决可见性和有序性问题:

1、JMM是一种规范,目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题

2、JVM内存结构,和Java虚拟机的运行时区域有关;Java内存模型,和Java的并发编程有关;Java对象模型,和Java对象在虚拟机中的表现形式有关。三者是完全不同的概念

3、通过文章1,可以发现并发问题,也是在硬件发展上遇到的棘手问题。即CPU和缓存一致性、处理器优化、指令重排。其实并发问题就是根据硬件发展上遇到的问题抽象出来的,一一对应就是:缓存一致性问题其实就是可见性问题。而处理器优化是可以导致原子性问题的。指令重排即会导致有序性问题

4、缓存一致性问题、处理器优化问题、指令重排问题是硬件的不断升级导致的。同样在多线程并发中,为了保证编程中可以满足可见性、原子性及有序性。有一个重要的概念,那就是——内存模型。Java语言在处理这件事中,同样也是使用了Java内存模型(JMM),而我们提到JMM,一般指的都是JDK5后开始使用新的JMM

5、为了保证共享内存的正确性(可见性、原子性、有序性),内存模型定义了共享内存系统中多线程读写操作行为的规范。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。它与处理器有关、与缓存有关、与并发有关、与编译器也有关。解决了CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下的可见性、原子性和有序性

6、JMM采用的策略是按需禁用缓存以及编译优化

7、Java内存模型,除了定义了一套规范,还提供了一系列原语,封装了底层实现后,供开发者直接使用。比如volatile、synchronized、final、concurrent包等

8、Happens-Before含义:前面一个操作的结果对后续操作是可见的

ThreadLocal好处:

①线程安全

②不需要加锁,效率高

③节省内存开销

④避免传参

Thread、ThreadLocal、ThreadLocalMap关系:

1、每个Thread类中都持有一个ThreadLocalMap对象(大体上还是一个Map,键是ThreadLocal对象,值就是我们想要保存的副本对象),而ThreadLocalMap中可能有多个ThreadLocal对象

ThreadLocal常用方法:

1、initialValue():默认返回null,需要重写该方法。若我们已经执行过set(),线程不会调用该方法,若第一次使用get(),则会调用该方法,返回当前线程对应的初始值(线程副本)。通常情况下每个线程只调用一次该方法即可(若调用过remove(),则可再次使用该方法)

2、set():为这个线程设置一个新值

3、get():得到这个线程的新值

4、remove():删除这个线程的保存的值

ThreadLocal的内存泄露:

1、当我们使用ThreadLocal时,其ThreadLocalMap内的Key一般为弱引用,可以由GC自动回收,但ThreadLocalMap内的Value一般为强引用,无法由GC直接回收,JDK考虑到这点,在set()、remove()、rehash()等方法中,会判断Key是否为null,若为null,也会将Value设置为null,这样就会被回收。但这一前提是调用这几个方法,所以为了防止内存泄露,要求我们在线程请求退出前调用这些remove()方法

Lock接口:

1、锁是一种工具,用于控制对共享资源的访问

2、Lock和synchronized是两个最常见的锁,都可以达到线程安全目的。Lock并不是用来替代synchronized的,而是当synchronized不合适或不满足需求时,提供更高级功能的

3、Lock接口最常见的实现类是ReentrantLock

4、synchronized效率相对会比较低,且不灵活。释放锁的情况死板,不能设定锁的超时,也不能中断试图获得锁的线程。无法知道是否成功获得到锁

5、在Lock中重要的5个方法:

lock():最普通的获取锁。和synchronized作用基本一致。但Lock不会像synchronized一样在异常时自动释放锁,所以我们要手动在finnally中释放锁。但该方法不能被中断,一旦陷入死锁和synchronized一样,会陷入永久等待

tryLock():用来尝试获取锁。该方法会立即返回,获取成功就返回true,获取失败就返回false,即便拿不到锁也不会一直等待,可以有效的避免死锁

tryLock(longe time, TimeUnit unit):在指定时间内,尝试获取锁,超时就放弃

lockInterruptibly():相当于tryLock(longe time, TimeUnit unit)把超时时间设置为无限,在等待过程中线程可以被中断

unlock():用于解锁,一般写在对应的finally代码块中

6、Lock和synchronized有同样的内存语义(Happens-Befor),保证其线程操作的可见性。即下一个线程加锁后可以看到所有前一个线程释放锁前的所有操作

锁的分类:

1、锁的分类是从不同角度出发去看的,并不是互斥的(一个锁可能在不同的分类里)

悲观锁和乐观锁:

1、悲观锁又称互斥同步锁,乐观锁又称非互斥同步锁。

2、悲观锁(互斥同步锁)会有阻塞和唤醒带来的性能劣势,且可能会陷入永久阻塞(如死锁),且可能会有优先级反转问题。悲观锁悲观的认为如果我不锁住这个资源,别人就会来争抢,所以每次都会先将资源锁住

3、乐观锁(非互斥同步锁)能避免悲观锁导致的问题。乐观锁乐观的认为即便我不锁住这个资源,也不会有别人来争抢,所以不会将资源锁住。在更新期间,去对比数据有没有被其他人修改过。如果没被修改过,就说明真的是只有自己在操作,正常修改数据;如果被修改过,就不进行刚才的更新过程,选择放弃、报错、重试等策略

4、乐观锁的实现一般都是利用CAS算法

5、常见悲观锁(synchronized和Lock)、常见乐观锁(原子类、并发容器、Git)

6、悲观锁的原始开销要高于乐观锁,但一劳永逸,后续开销不会再增加;乐观锁的原始开销要低于悲观锁,但如果自旋时间很长或不断重试,那么后续开销会越来越大

7、总的来说,悲观锁和乐观锁各有千秋。悲观锁适用于并发写入多的情况;乐观锁适用于并发写入少,读取多的场景

可重入锁和非可重入锁

1、可重入锁(递归锁):当我们再次申请同一把锁时,无需先释放锁,而是可以直接继续使用这把锁,就称为可重入锁。(同一个线程可以多次获取同一把锁);非可重入锁与之相反

2、常见可重入锁(synchronized和ReentrantLock)、常见可重入锁(ThreadPoolExecutor的内部类Worker)

公平锁和非公平锁:

1、公平锁:指完全按照线程请求的顺序来分配锁;非公平锁:指不完全按照请求的顺序来分配锁,允许在合适的时机插队

2、Java默认策略就是非公平锁,目的在于提高效率,避免唤醒带来的空档期

共享锁和排他锁:

1、共享锁(读锁):获取共享锁后,只能读,但不可以修改和删除数据,此时其他线程也可以获取共享锁,也只能读,但不可以修改和删除数据;排他锁(独享锁):获取排他锁后,可以读,也可以写,但其他线程无法再获取排它锁

2、共享锁和排他锁的典型就是ReentrantReadWriteLock(读写锁),其中有读锁方式(readLock方法),还有写锁方式(writeLock方法)

3、在没有读写锁之前,虽然保证了线程安全,但在进行读操作时浪费了资源(因为读并没有线程安全问题)

4、读写锁的规则就是:要么多读、要么一写。两者不可能同时出现

5、读锁插队的策略:策略1读可以插队,效率高,但可能造成写锁饥饿;策略2读不可插队,能避免饥饿(但也有特例)

6:、非公平的ReentrantReadWriteLock读写锁:当等待队列的头节点是写锁,读锁无法插队,但如果是读锁,其他读锁是允许插队的;公平的ReentrantReadWriteLock读写锁,是完全按照队列顺序排队的

7、一般情况下,读写锁可以降级,但不能升级

8、ReentrantLock适用于一般场景,ReentrantReadWriteLock适用于并发写少,读多的场景

自旋锁和阻塞锁:

1、自旋锁:当同步代码的逻辑非常简单,阻塞或唤醒这种状态装换耗费的时间,很可能比我们写的代码执行的时间还要长,为了这一小段时间去切换线程是得不偿失的。为了让线程“稍等一下”,我们会让当前线程自旋,如果在自旋完成前就已经释放了锁,那么当前线程就可不必阻塞而是直接获取同步资源,从而避免线程切换的开销。这就是自旋锁;阻塞锁:和自旋锁相反

2、自旋锁和乐观锁一样,算法实现也是CAS

3、自旋锁一般用于多核的服务器,在并发度不是特别高的情况下,比阻塞锁效率高。另外,自旋锁适用于临界区比较短小的情况,否则如果临界区很大(线程一旦拿到锁,很久以后才会释放),那也是不合适的

可中断锁和不可中断锁:

1、若一线程在等待过程中,想先处理其他事情,可以进行中断,这种锁称为可中断锁;反之为不可中断锁

2、常见可中断锁(Lock)、常见不可中断锁(synchronized)

JVM锁优化:

1、自旋锁和自适应:上面说过自旋锁的概念,自适应就是指当自旋到一定次数,JVM就会将自旋锁转换为阻塞锁,防止自旋过多导致的资源损耗

2、锁消除:JVM会分析出无需加锁的情况,自动将锁消除

3、锁粗化:JVM会分析出前后的synchronized代码块使用的是同一锁对象,那么就会将这些代码合为一个代码块,执行就无需反复加锁和释放锁

4、当我们在个人开发时,可以通过以下方法优化锁和提高并发性能:
①缩小同步代码块
②尽量不要锁住方法
③减小锁的次数
④避免人为制造共享变量
⑤避免锁套锁
⑥根据具体情况选择锁分类

什么是原子类:

1、在编程中的原子操作就意味着一组操作不可中断,不可分割的,即便是在多线程环境下。原子类的作用和锁类似,也是为了保证并发情况下线程安全。但原子类可以创建原子变量,把竞争范围缩小到变量级别,也就是相比于通常的锁,其粒度更细。且在非高度竞争的情况小,效率比锁的效率高

2、6类原子类:

AtomicInteger常用方法:

①get():获取当前的值
②getAndSet():获取当前的值并设置新值
③getAndIncrement():获取当前的值,并自增1
④getAndDecrement():获取当前的值,并自减1
⑤getAndAdd(int delta):获取当前的值,并且自增传入参数值
⑥compareAndSet(int expect, int update):若当前值等于预期值,则更新该值为输入值

AtomicReference:

1、AtomicReference类的作用和AtomicInteger并没有本质区别,AtomicInteger可以让一个整数保证原子性,而AtomicReference可以让对象保证原子性,其用法也类似

AtomicIntegerFielUpdater:

1、AtomicIntegerFielUpdater可对普通变量进行升级,其使用场景可能为:我们无法更改其代码、偶尔需要一个原子操作

2、使用该类,需要传入类名和变量名(使用了反射)

Adder累加器:

1、Adder是JDK1.8引入的,高并发下的Adder下类比Atomic*基本数据类型的类效率要高,不过是空间换时间,利用了多段锁

Java并发文章3——互斥锁(上):解决原子性问题

1、本篇文章和上面总结的互斥锁又很多重合的地方,但有一些地方是没有提到的:Java 编译器会在 synchronized 修饰的方法或代码块前后自动加上加锁 lock() 和解锁 unlock()、临界区指的是加锁和释放锁之间的代码部分,是锁主要保护的资源

什么是CAS:

1、CAS的全称是 Compare-and-Swap,也就是比较并交换,是并发编程中一种常用的算法。它包含了三个参数:V(当前内存值),A(预期值),B(新值)。CAS认为V的值应该是A,如果是的话就把它修改成B,如果不是A(说明被其他线修改过),那我就不修改了。可以看出和昨天学习的乐观锁去策略是一样的,所以乐观锁底层的算法实现就是CAS

2、乐观锁、并发容器、原子类底层都使用了CAS算法

3、拿原子类AtomicInteger举例,该类会在静态代码块内加载Unsafe工具,用来直接操作内存数据,也就是说通过Unsafe来实现底层操作。AtomicInteger的各种方法,其实现都是通过Unsafe对象去调用其中方法,我们能发现大部分逻辑都是一个do-while自旋内调用compareAndSwapInt()

4、Unsafe是CAS的核心类。Java无法直接访问底层操作系统,而是通过native本地方法访问。不过尽管如此,JVM还是开了一个后门,那就是Unsafe类,提供了硬件级别的原子操作

CAS缺点:

1、ABA问题。由于CAS本质是通过对比值来判断是否被修改的,但可能出现A被改为B,但又被其他线程改回A的情况,我们仍然是认为其没有被修改过的。这时我们可以沿用数据库修改数据使用的版本号

2、自旋时间可能过长

什么是不变性:

1、如果对象被创建后,状态就不能被改变(引用和属性),那么就是具有不变性的

2、具有不变性的对象一定是线程安全的,我们不需要额外的同步开销

final作用:

1、修饰类不可继承,修饰方法不可重写,修饰变量不可修改

2、final修饰的对象,其引用指向不能变,但若对象内属性未被final修饰,其属性是可以变的

3、根据final的特性,我们要想让一个对象具有不变性,我们要保证其所有属性都是final修饰的,并且对象属性用private修饰,且不提供对外公开的set方法

栈封闭:

1、在方法内新建的局部变量,实际上是每个线程私有的栈空间,是不能被其他线程访问到的,就不会有线程安全问题,这一现象称为栈封闭

并发容器:

1、比较过时的两个容器:Vector(可以当初线程安全的ArrayList)、Hashtable(可以当初线程安全的HashMap)

2、我们可以使用Collections.synchronizedXxxx()方法将ArrayList和HashMap变成线程安全的(底层也是使用synchronized代码块,其效率也不高)

3、使用HashMap,在多线程环境下,同时put产生的碰撞或扩容,可能导致数据丢失

4、HashMap在JDK1.7时,若在多线程环境下工作可能会造成链表互相指向的死循环

5、JDK1.7中的ConcurrentHashMap最外层是16个segment,每个segment的底层数据结构与JDK7.1的HashMap类似,仍然是数组和链表组成的拉链法。每个segment独立上ReentrantLock,所以互不影响,提高了并发效率

6、JDK1.8中的ConcurrentHashMap的底层代码完全进行了重写,基本和JDK1.8HashMap的数据结构类似,保证线程安全的原理是CAS+synchronized

7、红黑树是一种平衡二叉树,可以保证在极端情况下,也不影响查询效率

8、ConcurrentHashMap的replace()可以解决使用该集合组合操作(更新值的非原子组合操作),无法保证线程安全的问题

9、就像ConcurrentHashMap代替了Hashtable,CopyOnWriteArrayList代替了Vector

10、CopyOnWriteArrayList的应用场景有:读多写少,且读操作要求快一点,写操作慢一点也没关系,比如黑名单、监听器

11、CopyOnWriteArrayList的读写锁进行了升级,前面我们了解到的读写锁规则,要么多读,要么一写。而CopyOnWriteArrayList读取也完全不用加锁,写入也不会阻塞读取操作(允许多读一写)

12、CopyOnWrite含义:先拷贝一份新的出来,再新的内容里面进行修改,最后把的引用指向这个新的(即创建新副本,读写分离)

13、ArrayList不能在迭代读取的时候进行修改,而CopyOnWriteArrayList可以在读取的时候进行修改,但是在迭代时,可能出现数据过期问题,仍然读的是旧内容(不会报错)

14、CopyOnWriteArrayList的缺点:不能保证数据实时一致性(只有数据最终一致性)、且会多出一份内存占用

Java并发文章4——互斥锁(下):如何用一把锁保护多个资源?

1、可以用一把锁来保护多个资源,但是不能用多把锁来保护一个资源

2、保护没有关联关系的多个资源:可以使用不同锁管理不同类别的多个资源(使锁细粒度化)

3、保护有关联关系(指同一个类型)的多个资源:使用类对象做为锁

4、总的来说,只要我们明确锁的覆盖范围,和锁是哪种对象就可以很好的使用锁

并发队列:

1、并发队列分为阻塞队列和非阻塞队列


2、Java并发包提供的容器,大致分为3类:Concurrent*(CAS实现)、CopyOnWrite*(可以多读一写,会复制原数据)、*Blocking*(AQS实现)

控制并发流程:

1、控制并发流程的工具类,可以帮助我们更容易的让线程之间合作

2、CountDownLatch有两个用法:一等多,多等一

3、Semaphore可以保证在同一时刻只有n个人能执行任务,acquire()用来获取许可证、release()用来归还许可证(获取和归还的数量一定要一致,否则可能会因为许可证不够用导致程序卡死)

4、如果说Lock可以代替synchronized,那么Condition也可以代替Object的wait和notify方法

AQS:

1、AQS全称是AbstractQueuedSynchronizer,直译就是抽象队列同步器,是一个用于构建锁,同步器、线程协作类的工具类(排队管理器)

2、AQS内主要模块:state、控制线程抢锁和配合的FIFO队列、期望各协作工具类去实现的获取/归还等重要方法

Runnable的缺陷:

1、不能返回返回值;不能抛出异常

Callable接口:

1、用法类似于Callable,但返回值是泛型,且可以抛出异常,相当于弥补了Runnable的缺陷

Future类:

1、我们可以使用Future.get()获取Callable接口返回的执行结果,还可以通过Future类的其他方法对线程做出各种控制

2、Future的生命周期不能后退

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值