进程是操作系统分配资源的最小单位,每个进程都是一个在运行中的程序,在windows中一个运行的xx.exe就是一个进程,他们都拥有自己独立的一块内存空间,一个进程可以有多个线程
线程是操作系统调度的最小单元,负责当前进程中程序的执行,一个进程可以运行多个线程,多个线程之间可以共享数据
管道是内核管理的一个缓冲区,相当于内存中一个的小纸条,管道的一端连接着一个进程的输入,一端连接着另一个进程的输出,当管道中没有信息的话,从管道中读的进程会阻塞,直到另一端的进程放入信息,当管道中信息放满时,尝试放入信息的进程会阻塞,直到另一个端进程取出信息,当两个进程都结束时,管道也就结束了
特点:单向的,一端输入一端输出,采用先进先出FIFO模式,大小4K,满时写阻塞,空时读阻塞
分类:普通管道(仅父子进程间通讯)位于内存,命名管道位于文件系统,没有情缘关系的管道只要知道管道名也可以通讯
消息队列可以看做是一个消息链表,只要线程有足够的权限,就可以往消息队列里面存消息和取消息,他独立于发送进程和接收进程,提供了一种从一个进程向另一个进程发送数据块的方法,每一个数据块都有一个消息类型(频道)和消息内容(节目),每个类型相互不受影响,类似于一个独立的管道
特点:全双工可读可写,生命周期跟随内核,每个数据块都有一个类型,接收者可以有不同的类型值,每个消息的最大长度是有上限的(MSGMAX),字节数也是有上限的(MSGMNB),系统上的消息队列总数也是有上限的(MSGMNI),
信号量本质上是一个计数器,它不以传送数据为目的,主要是用来保护共享资源,使得资源在一个时刻只有一个进程独享
原理:信号量只有等待和发送两种操作,即P(sv)和V(sv)两个操作都属于原子操作
P(sv):如果sv的值大于0,就给它减1;如果它的值为0,就挂起该进程的执行
S(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因sv而挂起,就给他加1
共享内存就是允许两个进程或多个进程共享一定的存储区,当一个进程改变了这个内存区域中的内容时,其他进程都会觉察到这个更改
特点:数据不需要在客户端和服务端之间来回复制,数据直接写到内存,减少了数次数据拷贝,是很快的一种IPC
缺点:共享内存没有任何的同步和互斥机制,需要使用信号量来实现对共享内存的存取和同步
套接字是一种允许两个不同进程进行通信的编程接口,通过套接字接口,可以使一台机器之间的进程可以相互通信,也可以使不同机器上的进程进行网络通信,套接字明确把客户端和服务端分开,实现了多个客户端连接到一个服务端
原理:服务器端应用程序调用socket创建一个套接字,它是系统分配给服务器进程的类似文件描述符的资源,不能与其他进程共享,服务器进程给这个套接字起一个名字,本地套接字的名字是Linux文件系统中的文件名,一般在/tmp或/usr/tmp中,网络套接字的名字与客户端连接的特定网络有关的服务标识符(端口号),系统调用bind给套接字命名后,服务器进程就开始等待客户端连接到命名套接字,系统调用一个listen创建一个队列,用于存放来自客户端的进入连接,服务器用accept来接收客户端的连接,当有客户端连接时,服务器进程会创建一个与原有命名不同的新的套接字,这个套接字只用于与这个特定的客户端进行通信,原有套接字继续处理来自其他客户端的连接。
套接字的域:
AF_INET域中的类型
线程之间共享程序的公共状态,通过读写这个公共状态来进行隐式通信,这会经历两个过程
线程之间通过发送消息来显示的进行通信,比如wait()和notify()方法
线程被创建并启动后,他既不是一启动就进入执行状态,也不是一直处于执行状态,在线程的生命周期中,他要经过新建(new),就绪(Runnable),运行(Running),阻塞(Blocked)和死亡(Dead)5种状态,在启动过后,他不可能会一直占用,所以状态会在运行和阻塞之间切换
线程的上下文是指某一个时间点寄存器和程序计数器的内容,上下文切换可以认为是内核(操作系统的核心)在CPU上对于进程(包括线程)进行切换,上下文切换过程中的信息是保存在进程控制块(PCB,process control block)的,PCB也被称作为切换桢
上下文的切换过程
上下文切换的原因
寄存器是CPU内部数量较少但是速度很快的内存,
程序计数器是一个专用的寄存器,用于表明指令序列中CPU正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置,具体依赖于特定的系统
线程调度器是一个操作系统服务,它负责为Runnable状态的线程分配CPU时间片,当一个线程被创建和启动后,它的执行便依赖于线程调度器,分配cpu时间可与基于线程的优先级或者线程等待的时间
抢占式调度指的是每条线程执行的时间,线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是
协同式调度指某一个线程执行完成之后主动通知系统切换到一个线程上执行,这种模式就像接力赛一样,一个接一个,线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步的问题,缺点是如果一个线程编写有问题,一直在运行中,那么可能导致整个系统崩溃
按照任务进入队列的顺序,依次调用,执行完一个任务再执行下一个任务,只有当任务结束后才切换到下一个任务
优点:最小的任务切换开销(没有在任务执行中发生切换),最大的吞吐量(因为没任务切换开销),最朴实的公平性(先来先做)
缺点:平均响应时间高
适用场景:队列中任务耗时差不多的场景
按照任务的耗时长短进行调度,优先调度耗时最短的任务,这个算法的前提是知道每个任务的耗时时间,需要注意的是耗时最短指的是剩余执行时间,解决了先进先出算法中短耗时任务等待长耗时任务的窘境
优点:平均响应时间低
缺点:耗时长的任务迟迟得不到调度,不公平,容易形成饥饿,频繁的任务切换,调度的额外开销大
给队列中的每个任务分配一个时间片,当时间片到了之后将此任务放到队列的尾部,切换到下一个任务执行,解决了SJF中长耗时任务饥饿的问题
优点:每个任务都能够得到公平的调度,耗时短的任务即使落在耗时长的任务后面,也能够较快的得到调度执行
缺点:任务切换开销大,需要多次切换任务上下文,时间片不好设置
适用场景:队列中耗时差不多的任务
java使用的线程调度是抢占式调度,java中线程会按优先级分配cpu时间片,优先级越高越先执行,但是优先级高的线程并不能独自占用cpu时间片,只能是得到更多的cpu时间片,反之,优先级低的线程分到的执行时间少,但不会分配不到执行时间
任何线程都可以设置为守护线程(Daemon)和用户线程(User),默认情况下新建的线程是用户线程,通过setDaemon(true)可以将线程设置为守护线程,这个函数必须在线程启动前进行调用,否则会报错,守护线程依赖于用户线程,当用户线程都退出了,守护线程也就退出了,典型的守护线程就是垃圾回收线程
线程安全是某个函数,函数库在多线程的环境中被调用,能够正确的处理多个线程之间的共享变量,不会对共享资源产生冲突,不会影响程序的运行结果
优势:线程类只是实现了Runnable接口或者Callable接口,还可以继承其他类,在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU,代码和数据分开,形成清晰的模型,较好的体现了面向对象的思想
劣势:编程稍微复杂,如果要访问当前线程,则必须Thread.currentThread()方法
优势:编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程
劣势:继承了Thread类,不能再继承其他类
使线程进入waiting状态,只有等待另外线程的通知或者被中断才会返回,调用会释放对象的锁,因此wait方法一般用在同步方法或者同步代码块中
使当前线程休眠,与wait方法不同的是sleep不会释放当前锁,会使线程进入timed-wating状态
使当前线程从执行状态变为就绪状态,也就是当前线程让出本次cpu时间片,与其他线程一起重新竞争CPU时间片
中断一个线程,本质是给这个线程发行一个终止通知信号,影响这个线程内部的一个中断标识位,这个线程本身并不会因此而改变状态,注意在线程处于timed-wating状态时调用interrupt会抛出InterruptedException,使线程提前结束timed-wating状态
中断状态是线程固有的一个标识位,通过isInterrupted来获取标识位的值,根据值然后调用thread.interruptd方法来安全的终止线程
等待其他线程终止,在当前线程中调用一个线程的join()方法,则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待cpu分配,适用于主线程启动了子线程,需要用到子线程返回的结果,也就是主线程需要在子线程结束后再结束,这个时候就可以使用join方法
线程唤醒,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中的一个线程,选择是任意的
类不同:sleep方法属于Thread类中,wait方法属于Object类
释放锁:sleep不会释放锁,wait会释放锁,sleep不释放锁到指定时间就会自动唤醒,wait不会自动唤醒
应用场景不同:wait被用于通信,sleep被用于暂停执行
start方法被用来启动新创建的线程,内部调用了run方法,这和直接调用run方法效果不一样,直接调用run方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法会创建新的线程
notify只会唤醒一个线程,notiyfAll会唤醒所有线程
notify可能会导致死锁,而notifyAll则不会,notify是对notifyAll的一个优化
interrupted会将中断状态清除,而isinterrupted不会,java多线程中的中断机制使用这个内部标识符来实现,当一个中断线程调用Thread.interrupt()来获取中断状态时,中断状态会被清除,并设置为true,而非静态方法调用isinterrupted用来查询其他线程的中断状态不会改变中断状态的标识,任何抛出InterruptedException异常的方法都会将中断状态清零
线程同步指线程之间的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒,线程的同步方法大体可以分为两类,用户模式和内核模式,内核模式指的是利用系统内核对象的单一性来进行同步,使用时需要切换内核态和用户态,例如事件,信号量,互斥量,用户模式不需要切换到内核态,例如原子操作(单一的一个全局变量),临界区
synchronized可以把任意一个非NULL的对象当做锁,属于独占式的悲观锁,同时也是可重入锁
synchronized关键字是用来控制线程同步的,在多线程的环境下,控制synchronized代码段不被多个线程同时执行
volatile相比synchronized更加轻量
volatile修饰的变量具有synchronized的可见性,但是不具备原子性,也就是线程能够自动发现volatile变量的最新值
volatile禁止了指令重排,在执行程序时,为了提升性能,处理器和编译器常常会对指令进行重排,指令重排虽然不会影响单线程的执行结果,但是会破坏多线程的执行语义,指令重排有两个条件,1在单线程环境下不能改变程序的运行结果,2存在数据依赖关系的情况不允许指令重排
适用场景:一个变量被多个线程共享,线程直接给这个变量赋值,例如状态标记量和单例模式的双检锁
ThreadLocal是一个本地线程副本变量工具类,主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发的场景下,可以实现无状态的调用,特别适用于各个线程依赖不同变量值完成操作的场景,是一种空间换时间的做法,在每个Thread里面维护了一个以开地址实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然也就没有线程安全的问题了
基本方法
应用场景:
特点
volatile应用在多个线程对实例变量更改的场合,刷新主内存共享变量的值从而使得各个线程可以获得最新的值,线程读取变量的值需要从主内存中读取;synchronized是锁定当前变量,只有当前线程可以该变量,其他线程被阻塞住,synchronize会创建一个内存屏障,内存屏障保证了所有CPU操作结果都会直接刷到主内存中,从而保证了操作的内存可见性
volatile仅能使用在变量级别,synchronized则可以使用在变量,方法,和类级别
volatile不会造成线程的阻塞,synchronized会造成线程的阻塞
volatile只能保证变量的可见性不能保证原子性,synchronized保证了变量的可见性和原子性
volatile标记的变量不会编译器优化,可以禁止指令重排,synchronized标记的变量可以被编译器优化
悲观锁是一种悲观思想,总是假设最坏的情况,即认为每次都是线程不安全的,每次读写都会进行加锁,synchronized就是悲观锁的实现
乐观锁是一种乐观思想,每次去拿数据的时候都认为别人不会修改,所以不会上锁,只是在更新的时候判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制实现,适合于多读的应用场景,在数据库中write_condition机制就是乐观锁的实现,java中java.util.concurrent.atomic包下面的原子变量类也是使用了乐观锁的一种CAS实现方法
公平锁加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得
非公平锁加锁时不考虑等待问题,直接尝试获取锁,获取不到则自动到队尾等待
在相同条件下,非公平锁的性能比公平锁高5-10倍,因为公平锁在多核的情况下需要维护一个队列,增加了开销
synchronized是非公平锁,ReentrantLock默认的lock()方法采用的也是非公平锁
java并发包提供的加锁模式分为独占锁和共享锁
独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock就是以独占方式实现的互斥锁,独占锁采用悲观锁的加锁策略,避免了读/读写冲突,如果某个只读线程获取了锁,则其他线程只能等待,这种情况其实不需要加锁,因为读操作并不会影响数据的一致性
共享锁允许多个线程同时获取锁,并发访问共享资源,加锁策略是乐观锁
可重入锁指的是同一线程外层函数获得锁之后,内层递归函数仍然有获取该锁的代码,但不受影响,ReentrantLock和Synchronized都是可重入锁
自旋锁是指当一个线程在获取锁的时候,如果锁已经被其他线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环,获取锁的线程一直处于活跃状态
缺点:
优点:
锁的状态有四种:无锁状态,偏向锁,轻量级锁,重量级锁
轻量级是相对于使用操作系统互斥量来实现的传统锁而言,轻量锁不是用来代替重量级锁的,他的本意是在没有多线程竞争的前提下,减少传统重量级锁使用产生的性能消耗,适用于线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁升级为重量级锁
锁升级
随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级到重量级锁,锁的升级是单向的,只能从低到高,不会出现锁降级
Synchronized是通过对象内部的一个叫做监视器锁(monitor)来实现,但是监视器锁本质是依赖于底层的操作系统的Mutex Lock来实现的,而操作系统实现线程之间的切换这就需要从用户态转换到内核态,这个成本非常高,状态之间的转换需要相对比较长的时间 ,这也就Synchronized效率慢的原因,这依赖于操作系统Mutex Lock所实现的锁称之为重量级锁,JDK1,6之后为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了轻量级锁和偏向锁
偏向锁的引入是为了在某个线程获得锁之后,消除这个线程的锁重入(CAS)的开销,看起来让这个线程得到了偏向,减少不必要的轻量级锁的执行路径,因为轻量级锁的获取和释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能
读写锁分为读锁和写锁,读锁是无阻塞的,在没有写的情况下使用可以提高程序的效率,读锁和读锁不互斥,读锁和写锁互斥,写锁和写锁互斥
读锁适用于多个线程同时读,但不能同时写
写锁适用于只能一个线程写数据,且其他线程不能读取
分段锁不是一种实际的锁,而是一种思想,ConcurrenHashMap就是使用的分段锁
Semaphore是一种基于计数的信号量,可以设定一个阈值,根据这个阈值,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,申请许可信号量的线程将会阻塞,Semaphore可以用来构建一些对象池,资源池之类的,也可以创建一个计数为1的Semaphore二元信号量,类似互斥锁的机制
CAS(Compare And Swap/Set)比较并交换,CAS是一种基于锁的操作,CAS操作包含三个参数(V,A,B),内存位置(V),预期原值(A)和新值(B),如果内存地址里面的值和A的值一样,那么就将内存里面的值更新成B
CAS操作采用的是乐观锁,,通过无限循环来获取锁,如果在第一轮循环中,A线程的值被B线程修改了,那么A线程需要自旋,到下次循环才有机会执行
缺点:
AQS的全称是AbstractQueuedSynchronizer,这个类在java.util.concurrent.locks包下面,是一个用来构建锁和同步器的框架
核心思想:如果被请求的共享资源空闲,则将当前请求资源的线程设置成有效的工作线程,并且将共享资源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列来实现的,即将暂时获取不到的锁加入到队列中
CLH队列是一个虚拟的双向队列,虚拟的双向队列即是一个不存在的队列实例
基于AQS的实现:ReentrantLock,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureTask
AQS使用一个int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作,AQS使用CAS对该同步状态进行原子操作实现对其值的修改
private volatile int state;//共享变量,使用volatile修饰保证线程的可见性
//获取同步状态的当前值
protected final int getState(){
return state;
}
//设置同步状态的值
protected final void setState(int newState){
state = newState
}
//原子操作(CAS操作),将同步状态值设置为给定值
protected final boolean compareAndSetState(int expect,int update){
return unsafe.compareAndSwapInt(this,stateOffset,expect,update);
}
AQS定义了两种对资源的共享方式
不同的自定义同步器争用共享资源的方式也不同,自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败时入队和唤醒出队等),AQS在顶层已经实现了
AQS使用了模板方法模式,自定义同步器时需要重写下面几个AQS提供的模板方法
isHeldExclusively()//该线程是否正在独占资源,只有用到condition才需要去实现它
tryAcquire(int) //独占方式,尝试获取资源,成功返回true,失败返回false
tryRelease(int) //独占方式,尝试释放资源,成功返回true,失败返回false
tryAcquireShared(int) //共享方式,尝试获取资源,负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源;
tryReleaseShared(int) //共享方式,尝试释放资源,成功则返回true,失败返回false
默认情况下每个方法都抛出UnsupportedIOperationException.这些方法的实现必须都是内部线程安全的。并且通常应该简短而不是阻塞,AQS类中的其他方法都是final,所以无法被其他类使用,只有这几个方法可以被其他类使用
一般来说,自定义同步器要么是独占方式,要么是共享方式,只需要实现tryAcquire-tryRelease或者tryAcquireShared-tryReleaseShared,但是AQS也支持同时实现两种方式,比如ReentrantReadWriteLock
总结,synchronized关键字加到static静态方法和代码块上都是给Class类上锁。加到实例方法就是给对象实例上加锁,尽量不要使用到String类型上,因为JVM中字符串常量池具有缓存功能
public class SynchronizedDemo {
public void method(){
synchronized (this){
System.out.println("synchronized---code");
}
}
}
使用javap反编译后 javap -c -v SynchronizedDemo
在执行方法之前之后都有一个monitorenter和monitorexit字符,前面的monitorenter就是获取锁,执行完代码后释放锁,执行monitorexit,第二个monitorexit是为了防止在同步代码块中因异常退出而没有释放锁的情况下,第二遍释放,避免死锁的情况
重入的原理是一个线程在获取到该锁之后,该线程可以继续获得锁,底层原理维护了一个计数器,当线程获得该锁时,计数器加1,再次获得该锁时继续加1,释放锁时,计数器减1,当计数器值为0时,表明该锁未被任何线程所持有,其他线程可以竞争获取锁
在锁对象的对象头里面有一个threadId字段,在第一次访问的时候threadId为空,jvm让其持有偏向锁,并将threadid设置为线程id,再次进入的时候会先判断threadid是否与其线程id一致,如果一致则可以继续使用该对象,如果不一致则升级为了轻量级锁,通过自旋循环一定次数之后,如果还没有正常获取到要使用的对象,此时就会把锁再升级为重量级锁,从而降低锁带来的性能消耗
Lock可以说是Synchronized的扩展版,Lock提供了无条件的,可轮训的(tryLock方法),定时的(tryLock(long timeout,TimeUnit unit)),可中断的(lockInterruptitibly),可多条件的(newCondition方法)锁操作,另外Lock的实现类基本都支持非公平锁和公平锁,synchronized只支持非公平锁
优势:
主要方法
ReentrantLock继承接口Lock并实现了定义的方法,是一种可重入锁,除了能完成Synchronized所能完成的所有功能外,还提供了诸如可响应的中断锁,可轮询锁请求,定时锁等避免死锁的方法
使用ReentrantLock必须在finally中进行解锁操作,避免程序出现异常而无法正常解锁的情况
synchronized是悲观锁,属于抢占式,会引起其他线程阻塞
两个都是可重入锁
Synchronized是和if,else,for一样的关键字,ReentrantLock是类
ReentrantLock相比synchronized的优势是可中断,公平锁,多个锁
减少锁持有时间
只用在有线程安全要求的程序上加锁
减小锁粒度
将大对象(这个对象可能会被很多线程访问),拆成小对象,增加并行度,ConcurrentHashMap就是其中的一个实现
降低锁竞争
使用偏向锁,轻量级锁,降低锁竞争
锁分离
根据功能将锁分离开,一般分为读锁和写锁,做到读读不互斥,读写互斥,写写互斥,保证了线程安全,又提高了性能
锁粗化
正常来说是为了保证线程间有效并发,会要求锁的持有时间尽量短,但是如果时间太短,多个线程对一个锁不停的请求,同步,释放,这其中对锁的开销也会浪费系统资源
锁消除
对不需要共享的资源中取消加锁
容器类存放于Java.util包中,主要有3种:Set(集),list(列表,包含Queue)和Map(映射)
Set
List是有序的Collection,实现有ArrayList,Vector,LinkedList
底层通过数组实现,允许对元素进行快速随机访问,缺点是每个元素之间不能有间隔,当容量不够需要增加容量时,需要将原来的数据复制到新的存储中间中,当进行插入或者删除时,需要对数组进行复制,移动,代价比较高
适合随机查寻和遍历,不适合插入和删除,打印时使用Arrays.toString()输出每个元素
底层是通过数组实现,是线程安全的,避免了多线程同时写而引起的不一致性,性能比ArrayList慢
采用双向链表结构存储数据,很适合数据的动态插入和删除,随机访问和遍历速度比较慢,提供了专门操作表头和表尾的元素
set有独一无二的性质,用于存储无序(存入和取出)元素,值不能重复,数据是否重复的本质是比较对象的HashCode值,所以如果想要让两个对象相同,就必须覆盖Object的hashCode和equals方法,实现有HashSet,TreeSet,LinkHashSet
内部采用HashMap实现,不允许重复的Key,只允许一存储一个null对象,判断key是否重复通过HashCode值来确定
使用二叉树的结构存储数据,二叉树是有序的,排序时需要实现Comparable接口,重写comoare函数,排序时该函数返回负整数,零,正整数分别对应小于,等于,大于
继承于HashSet,实现了LinkedHashSet,底层采用LinkedHashMap来保存元素
不是一个线程安全的容器,默认容量是16,根据键的hashCode存储数据,大多数情况可以根据hash函数一次性获取到数据,因此具有很快的查询速度,键只允许一个null,值可以有多个null
JDK1.7采用数组+链表的方式存储,每次扩容都是2^n,扩容后是原来的两倍,负载因子是0.75,扩容的阈值是当前数组容量*负载因子
JDK1.8采用数组+链表+红黑树方式存储,相比JDK1.7多了一个红黑树,当链表的元素超过了8个以后会将链表转换成红黑树
ConcurrentHashMap是一个线程安全的容器,底层是一个Segment数组,默认长度是16,所以并发数是16,通过继承ReentrantLock进行加锁,每次加锁的时候只锁住数组中的一个Segment,也就是分段锁的思想,只要保证了操作的Segment线程安全,也就实现了全局的线程安全
HashTable功能和HashMap相识,不同的是他属于线程安全的,继承自Dictionary,在性能上不如concurrentHashMap,使用场景较少,因为线程安全的时候使用concurrentHashMap,不需要保证线程安全的时候使用HashMap
TreeMap实现了SortedMap接口,底层数据结构是红黑树,保存的时候根据键值升序排序,适用于在遍历的时候需要得到的记录是排序后的,注意在使用的时候,key必须实现Comparable接口,否则就会抛出运行时异常java.lang.ClassCastException
LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在用iterator遍历的时候,得到的记录肯定是先插入的,可以在构造时带参数,控制访问次序排序
使用场景
利用池化思想,将线程管理起来,使用的时候不需要再创建和销毁,即用即拿提高了效率,减少了线程的开销,方便了管理
总结:执行任务时检查线程池中的线程数量,小于核心数则新建一个线程执行任务,大于等于核心数则放入任务队列,大于核心数且小于最大线程数则创建新线程,当线程数大于核心线程数,且空闲时间超过了keepalive时则会销毁线程
ThreadPoolExecutor.AbortPolicy
线程池默认的拒绝策略,当线程池中数量达到最大线程数时抛出java.util.concurrent.RejectedExcutionException异常,任务不会被执行
ThreadPoolExecutor.DiscardPolicy
默默丢弃不能执行的新加任务,不会抛出异常
ThreadPoolExcutor.CallerRunsPolicy
重试添加当前的任务,会自动重复调用execute()
ThreadPoolExecutor.DiscardOldestPolicy
抛弃线程池中工作队列头部的任务,也就是等待得最久的任务,并执行新传入的任务
底层是通过new ThreadPoolExecutor(10,10,0L,TimeUnit.MILLSECONDS,new LinkedBlockingQueue())创建,初始化一个指定线程数的线程池,其中核心线程数和最大线程数相同,使用LinkedBlockingQueue作为阻塞队列,当线程没有可执行任务时不会释放线程,由于使用LinkedBlockingQueue的特性,这个队列是无界,若消费不过来,会导致内存被任务队列占满,最终OOM
缓存线程池,底层通过new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue())创建缓冲线程池,因为线程池的最大值是Integer.MAX_VALUE,所以当并发数很大,线程池来不及回收时,会导致严重的性能问题
队列使用的LinkedBlockingQueue无界队列,可以无限添加任务,直到内存溢出
submit可以返回持有计算结果的Future对象,execute返回类型是void
线程池大小要看执行什么类型的任务,一般可分为CPU密集型,IO密集型
CPU密集型应该使用较小的线程池,一般为CPU核心数+1
IO密集型两种方式 1:使用较大的线程池,一般为CPU核心数2,2:(线程等待时间与线程CPU时间之比+1)CPU核心数
CountDownLatch是基于AQS共享模式的实现,适用于一个或多个线程等待某个条件,期间阻塞,直到所有线程都符合,然后继续执行的场景
构造时传入一个int参数作为计数器,主要方法是countDown和await,每调用一次countDown()方法计数器减1,await方法会阻塞,直到计数器为0
CountDownLatch不能够重用,计数器只能做减法,如果需要重用考虑使用CyclicBarrier或者重新创建CountDownLatch
CyclicBarrier中文叫栅栏,也可以叫同步屏障,让一组线程都到达栅栏之前阻塞,当最后一个线程到达栅栏后放行,好比一扇门,默认是关闭状态,阻塞线程的运行,直到所有线程都就位时,门才打开,让所有线程一起通过,最后还可以关闭,继续下一轮
构造时传入一个int参数作为需要拦截的线程数,每当一个线程调用await方法就会告诉CyclicBarrier已经有一个线程到达栅栏
许可证集合,用于限制可以访问某些资源的线程数目,这里的限制指的是资源的互斥而不是同步,只能保证在同一时刻资源是互斥的,但不是同步的,这点和锁不一样
常用方法
页面更新:2024-05-12
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号