多线程基础知识

多线程

1、创建线程的几种方式
继承Thread类
  • 第一步定义Thread类的子类,并重写该类的run()方法,该run()方法作为线程的执行体。
  • 第二步创建Thread子类的实例,即创建该线程的对象
  • 第三步调用线程对象的start()方法来启动该线程
通过实现Runnable接口来创建线程
  • 第一步定义Runnable接口的实现类,同时实现该接口的run方法,将该run方法作为线程执行体。
  • 第二步创建Runnable实现类的实例,并将这个实例作为thread的target来创建Thread对象,Thread对象为线程对象。
  • 调用线程对象的start方法来启动该线程。
通过实现Callable接口来创建并启动线程
  • 第一步创建Callable接口的实现类,并实现call方法,该call方法作为线程的执行体,且该call方法是有返回值的,然后再创建Callable实现类的对象。
  • 第二步使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable的实例对象的call()方法的返回值。
  • 第三步使用FutureTask对象作为Thread对象的target创建新的线程,调用线程的start方法,启动新的线程。
  • 第四步调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
这三种方法的一些总结
  • 实现Runnable接口和实现Callable接口来创建线程的方式实际上差不多,只不过就是再Callable接口定义的call方法有返回值。
  • 采用Runnable、Callable接口的方式创建多线程的好处是他们创建的线程类只是实现了Runnable接口或者Callable接口,还可以继承其他的类,在这种方式下多个线程可以共享一个target对象,适合多个相同的线程来处理同一资源的情况。
  • 通过继承Thread类创建线程的好处是编写代码较为简单
  • 推荐使用实现Runnable和Callable接口的方式来创建多线程。
2、Thread类的常用方法
构造方法
  • Thread()

  • Thread(String name)

  • Thread(String name)

  • Thread(Runnable target)

静态方法
  • cunrrentThread():返回当前正在执行的线程
  • interrupted():返回当前执行的线程是否已经被打断
  • sleep(long millis):使当前执行的线程睡眠多少毫秒
  • yield():使当前执行的线程自愿暂时放弃对处理器的使用权并允许其他线程执行
实例方法
  • getId():返回该线程的id
  • getName():返回该线程的名字
  • getPriority():返回该线程的优先级
  • interrupt():使该线程中断;
  • isDaemon():返回该线程是否是守护线程;
  • setName(String name):设置该线程的名字;
3、run()和start()的区别
  • run()被称为是线程的执行体,他的方法代表了线程需要完成的任务,而start()方法是用来启动线程的
  • 调用start方法的时候,会在mian方法开辟的主栈外边新开辟一个栈空间,在这个栈空间开辟之后start方法立即失效,然后JVM会默认自行调用run方法,将这个run方法压在栈底,完成后去执行线程的任务。
  • 如果不调用start方法直接run方法,其本质还是一个单线程,因为这个run方法实际上还是在mian方法的栈中执行的,没有额外开辟栈空间。
4、线程是否可以重复启动
  • 只能对于新创建状态的线程启动start方法,否则引发一个IllegalThreadStateException异常
5、线程的生命周期
  • 新建状态:刚new出来的线程的对象
  • 就绪状态:新建状态调用了start方法就会进入到就绪状态,就绪状态的线程又叫做可运行状态,表示当前线程具有抢夺cpu时间片的权利(cpu时间片就是执行力),当一个线程抢到时间片之后,就可以开始执行run方法,run方法执行成功之后标志着线程进入到了运行状态。
  • 运行状态:run方法开始执行就标志着线程进入到了运行状态,当之前占有的cpu时间片执行完之后,会重新回到就绪状态。继续抢夺cpu时间片,当再次抢夺到cpu时间片之后,就会再次进入到运行状态,接着再执行run方法,继续往下执行。
  • 阻塞状态:当线程遇到阻塞事件之后,例如接收用户从键盘输入,或者sleep方法等,此时线程就如进入到阻塞状态,阻塞状态的线程会放弃之前占用的cpu时间片,当阻塞解除的时候,需要这个线程继续回到就绪状态继续抢夺cpu时间片。
  • 死亡状态:运行状态时当run方法执行结束之后就会进入死亡状态,整个线程也就到此结束了。
6、如何实现线程同步
  • 同步方法

    • 即有synchronized关键字修饰的方法,由于java的每一个对象都有一个内置锁,当用此关键字修饰方法的时候,内置锁会保护整个方法。在调用该方法前,需要获取内置锁,否则就处于阻塞状态。需要注意的是,synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。
  • 同步代码块

    • 既有synchronized关键字修饰的语句块,被该关键字修饰的语句块会自动加上内置锁,从而实现同步。需要值得注意的是,同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
  • ReentrantLock

    • java5新添加了一个java.util.concurrent包来支持同步,其中ReentranLock类是可重入、互斥、实现了Lock接口的锁,它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。需要注意的是,RenntranLock还有一个可以创建公平锁的构造方法,但由于能大幅降低程序运行效率,因此不推荐使用。
  • volatile

    • volatile关键字为域变量的访问提供了一种免锁机制,使用volatile修饰域相当于告诉虚拟机该域可能被其他线程更新,因此每次使用该域就要重新计算,而不是使用寄存器中的值。需要注意的是,volatile不会提供任何原子操作,他不能用来修饰final类型的变量。
  • 原子变量

    • 在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步,例如Atomiclnteger表可以使用原子方式更新int的值,可用在应用程序当中(如以原子方式增加计算器),但不能用于替换Integer。可以扩展Number,允许哪些基于数字类的工具和实用工具统一访问。
7、线程、进程之间的关系
进程
  • 进程是程序的一次执行过程,是系统运行程序的基本单位,同时他也是资源分配的最小单位,因此进程是动态的。系统运行一个程序即是一个进程的创建,运行到消亡的过程。简单的来说一个进程就是执行中的程序,他在计算机当中是一个指令接着一个指令进行执行的,同时每个进程还占有某些系统资源如cpu时间,内存空间,文件,输入输出的设备,并且各个进程之间是相互独立的。
线程
  • 线程是比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程,线程是cpu调度的最小单位,线程与进程不同的是同一类的多个线程共享同一块空间和同一组资源,所以系统在产生一个线程或者是在各个线程之间切换工作时,负担要比进程小得多。
8、进程之间的通信方式
  • 信号、管道、消息队列、信号量、共享内存
信号
  • 信号又称为软终端,通知程序发生异步事件。程序执行中随时可能被各种信号中断,进程可以忽略该信号,也可以中断当前程序转而去处理信号,引起信号原因:
    • 程序中执行错误码
    • 其他进程发送来的原因
    • 用户通过控制终端发送来的
    • 子进程结束时向父进程发送SLGCLD
    • 定时器生产的SLGALRM
管道
  • 管道的优点是不需要加锁,缺点是默认的缓冲区太小,只有4k,同时只适合父子间进程通信,而且一个管道只适合单向通信,如果要双向通信需要建立两个。而且不是和多个子进程,因为消息会乱,他的发送机制是用read/write这是适用流,缺点是数据本省没有边界,需要应用程序自己解释,而一般消息大多是一个固定长度的消息头。和一个变长的消息体,一个子进程从管道read到消息后,消息体可能被别的子进程接收到
  • 单向、一端输入,另一端输出,先进先出的fifo。管道也是文件,管道大小为4096个字节
  • 特点是:管道满的时候,写阻塞;空时读阻塞
  • 分类:普通管道位于内存当中,命名管道位于文件系统,没有亲缘关系管道只要知道管道名也可以通信。
消息队列
  • 消息队列也不用加锁,默认缓冲区和但消息都要大一些,它并不局限于父子间进程通信,不过稍微加个标识,可以通过消息中的type进行区分,比如一个任务分派进程,创建了若干个执行子进程,不管是父进程发送分派任务,还是子进程发送任务消息,都将type设置为目标进程的pid,因为msgrcv可以指定只接受消息类型为type的消息,这样就实现了子进程只接收自己的任务,父进程只接收任务结果。
  • 消息队列是先进先出的fifo原则
共享内存
  • 共享内存几乎认为他是没有上限的,他也是不局限于父子进程,采用跟消息队列类似的定位方式,因为内存是共享的,不存在任何单向的限制,最大的问题就是需要应用程序自己做互斥。
信号量
  • 信号量是一种用于提供不同进程或同一个进程间的不同线程同步手段的原语,systemv信号量在内核中维护
  • 二值信号量:其值只有0、1两种选择,0表示资源被锁,1表示资源可用;
  • 计数信号量:其值在0和某个限定值之间,不限定资源数只在0、1之间
  • 计数信号量集;多少个信号量的集合组成信号量集。
总结
  • 管道是最弱的,只适合有限的场景
  • 消息队列能使和大部分场景,缺点是默认缓冲也比较小,不过这个可以调整,前提是你有管理员的权限
  • 共享内存是最强大的,只是要做互斥
9、线程之间的通信方式
  • 在java中线程通信主要有以下三种方式
wait()、notify()、notifyAll()
  • 如果线程之间采用synchronized来保证线程安全,则可以利用wait()、notify()、notifyAll()来实现 线程通信。这三个方法都不是Thread类中所声明的方法,而是Object类中声明的方法。原因是每 个对象都拥有锁,所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作。并且因为当 前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。另外,这三个方法都是本 地方法,并且被final修饰,无法被重写。
    wait()方法可以让当前线程释放对象锁并进入阻塞状态。notify()方法用于唤醒一个正在等待相应对 象锁的线程,使其进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。 notifyAll()用于唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放 锁后竞争锁,进而得到CPU的执行。
    每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列。就绪队列存储了已就绪(将要竞争 锁)的线程,阻塞队列存储了被阻塞的线程。当一个阻塞线程被唤醒后,才会进入就绪队列,进而 等待CPU的调度。反之,当一个线程被wait后,就会进入阻塞队列,等待被唤醒。

  • await()、signal()、signalAll()
    如果线程之间采用Lock来保证线程安全,则可以利用await()、signal()、signalAll()来实现线程通 信。这三个方法都是Condition接口中的方法,该接口是在Java 1.5中出现的,它用来替代传统的 wait+notify实现线程间的协作,它的使用依赖于 Lock。相比使用wait+notify,使用Condition的 await+signal这种方式能够更加安全和高效地实现线程间协作。
    Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition() 。 必须要注意 的是,Condition 的 await()/signal()/signalAll() 使用都必须在lock保护之内,也就是说,必须在 lock.lock()和lock.unlock之间才可以使用。事实上,await()/signal()/signalAll() 与
    wait()/notify()/notifyAll()有着天然的对应关系。即:Conditon中的await()对应Object的wait(), Condition中的signal()对应Object的notify(),Condition中的signalAll()对应Object的notifyAll()。

  • BlockingQueue
    Java 5提供了一个BlockingQueue接口,虽然BlockingQueue也是Queue的子接口,但它的主要用 途并不是作为容器,而是作为线程通信的工具。BlockingQueue具有一个特征:当生产者线程试 图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从 BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。
    程序的两个线程通过交替向BlockingQueue中放入元素、取出元素,即可很好地控制线程的通 信。线程之间需要通信,最经典的场景就是生产者与消费者模型,而BlockingQueue就是针对该 模型提供的解决方案。

10、Java同步机制中的wait、notify
  • wait、notify、notfyAll用来实现线程之间的通信,这三个方法都不是Thread类中所申明的方法,而是Object类中申明的方法,原因是每个对象都拥有锁,所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作。并且因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常的复杂了,另外,这三个方法都是本地方法,并且被final修饰,无法进行重写,斌且只有采用synchronized实现线程同步时才能使用这三个方法。
  • wait方法可以让当前线程释放对象锁并进入阻塞状态。nitify方法用于唤醒一个正在等待相应对象锁的线程,使其进入就绪队列,以便于在当前线程释放锁以后竞争锁,进而得到cpu执行。
  • 每个锁的对象都有两个队列,一个是就绪队列,一个是主塞队列,就绪队列存储了已就绪(将要竞争锁)的线程,阻塞队列存储了被阻塞的线程。当一个阻塞线程唤醒后,才会进入就绪队列,进而等待cpu的调度。反之,当一个线程被wait后,就会进入阻塞队列,被等待唤醒
11、slepp和wait的区别
  • sleep是Thread类中的静态方法,wait是Object类中的成员方法
  • sleep可以在任何地方使用,而wait只能在同步方法使用
  • sleep不会释放锁,而wait方法会释放锁,并需要通过notify、notifyAll来重新获取锁
12、如何让子线程先执行、主线程在执行
  • 启动线程之后,立即调用该项成的join方法,则主线程必须等待子线程执行完毕之后才能执行。
13、阻塞线程的方式
  • 线程调用sleep方法主动放弃所占有的处理器资源
  • 线程嗲用了一个阻塞式的IO方法,在该方法返回之前,该线程被阻塞
  • 线程试图获得一个同步监视器,但是该监视器正在被其他线程所持有;
  • 线程等待调用了线程的supseng方法方法将该线程挂起,但是这个方法容易导致死锁,所以应该避免使用该方法。
14、synchronized与lock的区别(***)
  • synchronized是java的一个关键字,synchronized是在jvm层面实现了加锁和解锁,这个他是java中的接口,他是在代码的层面实现了枷锁和解锁
  • synchronized可以使用在代码块上以及方法上,但是Lock只能写在代码中
  • synchronized在执行完毕或者遇到异常的时候能够自动的释放锁,但是Lock不能自动释放,需要在finally中显示的释放锁。
  • synchronized有可能会导致线程拿不到锁而一直等待,但是Lock可以设置获取锁失败的时间。
  • synchronized无法得知是否获得锁成功,Lock可以通过设置tryLock来得知加锁是否成功
  • synchronized锁可重入,不可中断,非公平;Lock可重入、可中断、可以公平也可以不公平。
15、synchronized底层原理实现
1、代码块同步底层原理
  • synchronized作用在代码的时候,他的底层是通过monitorenter和monitorexit指令来实现的。

  • monitorenter

    每个对象都是一个监视锁(monitor),当monitor被占用的时候就会处于锁定的状态,线程执行monitorenter指令尝试获取monitor的所有权,过程如下:如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。如果线程已经占有该monitor,只是重新进入,则进入数+1,如果其他线程已经占用了monitor,则该线程进入阻塞状态,知道monitor的进入数为0,再重新尝试获取monitor色所有权。

  • monitorexit

    执行monitorexit的线程必须是object所对应的monitor持有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。

    monitorexit指令出现了两次,第一次为同步正常推出释放锁,第二次为异步退出释放锁。

2、方法同步的底层原理
  • 方法的同步并没有通过monitorenter和monitorexit指令来完成,不过相对于普通的方法,其常量池多了ACC_SYNCHRONIZED标识符。JVM就是根据该表示符来实现方法的同步
  • 当方法调用的时候,调用指令会检查方法的ACC_SYNCHRONIZED访问标志是否被设置了,如果被设置了,执行线程先获取monitor,获取成功之后才能执行方法体,方法执行完毕之后再释放monitor。在方法执行的期间,其他线程都无法再或得同一个monitor对象。
3、synchronized可以修饰静态方法吗
  • synchronized可以修饰静态方法,但是不能修饰静态代码块。
  • 当修饰静态方法的时候,监视器锁(monitor)便是对象Class实例、因为Class数据存在于永久代,因此静态方法锁相当于该类的一个全局锁
4、总结
  • 方法同步和代码块同步在本质上是没有去别的,只不过就是方法同步是一种隐式的方式来实现,无需通过字节码来完成。两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现的,被阻塞的线程会被挂起、等待重新调用度,会导致"用户态和内核态"两个态之间来回切换,对性能有较大的影响。
16、不使用synchronized和Lock如何保证线程安全
  • 1、volatile
    • volatile关键字为域变量的访问提供了一种免锁机制,使用volatile修饰域相当于告诉虚拟机该域变量可能会被其他的线程进行更新,因此每次使用该域的时候就要进行重新的计算,而不是使用寄存器中的值,需要注意的是,volatile不会提供任何原子操作,他也不能用来修饰final类型的变量
  • 2、原子变量
    • 在java的util.concurrent.atomic包中提供了创建原子类型的变量的工具类,使用该类可以简化线程同步,例如AtomicInteger表示可以使用原子的方式更新int的值,可用在应用程序当中,但是不能够用于替换Integer,可以扩展Number,允许那些处理基于数字类的工具和统一工具进行访问。
  • 3、本地存储
    • 可以使用ThreadLocal这个类来实现线程本地存储的功能,每一个线程的Thread对象中都会维护一个ThreadLocalMap对象,ThreadLocal就是这个ThreadLocalMap的入口,使用这个ThreadLocalMap可以找到对应本地的线程变量。
  • 4、不可变的
    • 只要一个不可变的对象被正确的创建出来,拿起外部可见状态就永远不会被改变,永远都不会看到他在多线程中处于不一致的状态,“不可变性”带来的安全性是最直接的、最纯粹的。
17、java中的乐观锁和悲观锁
  • 悲观锁:顾名思义总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会进行上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。Java中的悲观锁是使用synchronized关键字或Lock接口进行实现的。
  • 乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会被修改,所以不会上锁,但是在更新的时候会判断一下在此期间有没有别人去更新这个数据。乐观锁适用于多读的应用类型,这样可以提高吞吐量。在jdk1.5新增了juc包,就是建立在cas之上的。相对于synchronized这种阻塞算法,cas是非阻塞算法的一种常见实现。所以juc在性能上有了很大的提升。
公平锁和非公平锁
  • synchronized锁和它对应的类或者方法以及代码块进行加锁,这种是非公平的锁,还有一种ReentranLock(瑞嗯吹lock),他虽然默认是非公平的锁,但是它可以实现公平的。。
Java锁升级
  • 在jdk1.6之前,synchronized是一个重量级锁,是一个效率比较低下的锁,但是在jdk1.6之后,jvm为勒提高锁的释放以及获取的效率对synchronized进行了优化,引入了偏向锁和轻量级锁,从此以后锁的状态就有了四种:无锁、偏向锁,轻量级锁、重量级锁。这四种状态会随着竞争的情况逐渐升级,而且是不可以逆的过程,即不可以降级,这四种锁的级别由低到高依次是无锁、偏向锁、轻量级锁、重量级锁。
  • 无锁指的是没有对资源进行锁定,所有的线程都能够访问并且修改同一个线程,但是在同时只能够有一个线程修改成功,无锁状态的特点是他是在循环内部进行的,当多个线程同时修改一个变量的值,必定会有一个线程修改成功,没有修改成功的线程会继续循环直到成功为止。
  • 偏向锁偏向锁就是当代码块初次执行到这个synchronized锁是,锁就会升级成为偏向锁,他是通过CAS修改了锁对象头中的锁标志位,将标志位修改为01意思就是偏向于第一个获得获得他的线程的锁,当这个同步代码块执行完毕比的时候线程不会主动释放偏向锁。
    • 偏向锁是指当一段同步代码一直被同一个线程所访问的时候,不存在线程的竞争,那么后续再访问的时候会自动释放锁,从而降低获取锁带来的消耗,即提高性能。
    • 偏向锁只有遇到其他线程尝试竞争锁的时候,才会释放这个偏向锁,线程是不会主动释放偏向锁的。
  • 轻量级锁轻量级锁是指当锁是偏向锁的时候,却被另外的线程所访问,此时偏向锁就会升级为轻量级锁,其他线程通过自旋的形式来获取这个锁,线程不会阻塞从而提高性能。轻量级锁的标志位是00
    • 轻量级锁的获取主要有两种情况:
      • 当关闭偏向锁的时候
      • 当多个线程竞争偏向锁的时候就会导致偏向锁升级为轻量级锁。
  • 重量级锁重量级锁指的是当有一个线程获取锁之后,其余所有等待该锁的线程就会处于阻塞的状态。其实就是所有的控制权都交给了操作系统,由操作系统来决定线程间的状态和线程之间的调度,当出现频繁的对线程运行状态进行切换就会导致大量的系统资源被消耗。
18、如何实现互斥锁
  • 最基本的就是synchronized关键字
  • 在jdk1.5后新增的juc包中的Lock接口便成为了Java的另外一种全新的互斥手段。
19、分段锁
  • 我知道的一种分段锁ConcurrentHashMap他的实现就是基于一种分段锁,他就是将独立对象上的锁进行了分解,就是如果在容器中有多把锁,每一把锁用于锁容器中一部分数据,当多线程访问容器中不同数据段的时候,线程间是不会存在锁竞争的,从而有效提高并发的效率,将数据分成一段段进行存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一段数据的时候,其他线程能够访问其他段的数据。
20、读写锁
  • 读写锁与传统锁不同的是它可以共享给线程来读,但是写不是共享的,总结就是读读不互斥,读写互斥,写写互斥,而传统的独占锁是读读互斥、读写互斥、谢谢互斥,在实际开发环境中读操作远大于写操作,所以为了优化这种场景创建出来了读写锁。
21、volatile关键字有什么用?
  • 当一个变量被定义成为volatile之后,他将具备两个特性
    • 保证可见性
      • 当写一个volatile变量的时候,jmm会把线程本地中的变量强制刷新到主内存中去,这个写回操作会导致其他线程中的volatile变量缓存无效。
    • 禁止指令重排
      • 当程序执行到volatile变量的读写操作时,在它前面的操作的更改肯定全部已经进行,且结果已经对后面的操作可见,在其后面的操作肯定还没有进行
      • 在进行指令优化的时候,不能将volatlie变量访问的语句放在其后面执行,也不能将v欧拉里了变量后面的语句放在前面进行执行。
    • 注意,虽然volatile能够保证可见性,但它不能保证原子性。volatile变量在各个线程的工作内存是不存在一致性问题的,但是java里面的运算操作符并非原子操作,这导致volatile变量运算操作并非原子操作,这导致volatile变量的运算在并发下一样是不安全的。
22、 volatile的实现原理
  • volatile可以保证线程可见性提供了一定的有序性,但是无法保证原子性。在jvm的底层volatile是采用内存屏障来进行实现的,这个内存屏障在底层实际上就是一个lock指令
  • 内存屏障的作用
    • 他确保了指令在重排的时候不会吧它后面的指令排到内存屏障之前的位置,也不会把前面的指令拍到内存屏障的后面;即在执行到内存屏障这句指令的时候,在他前面的操作就已经全部完成了。
    • 他会强制将对缓存的修改写入到主存中去
    • 如果是写操作,他会导致其他cpu中对应的缓存无效
23、对juc的了解
  • juc是java.util.concrrent包的缩写,它可以提供并发的包,其中包含并发编程用到的基础组件
  • java.util.concrrent.locks包下面的这个Lock接口基于AQS建立的,然后在juc下面大多出工具都是基于Lock来实现的。
  • 线程池:Executor、Exectors、ThreadPoolExector、等
  • 并发的容器:ConcurrentHashMap、ConcurrentArrayList等支持并发的容器
24、AQS的理解
  • 是用来构建锁和其他同步组件的骨架类,减少了各个功能组件实现的代码量,也解决了在实现同步器时设计的大量的细节问题。
  • AQS采用模板方法模式,在内部维护了n多模板方法的基础之上,子类只需要实现特定的几个方法,不是抽象方法,就可以实现子类自己的需求。
  • 基于AQS实现的组件
    • RenntranLock:可重入锁,支持公平和非公平的方式来获取锁
    • RenntranReadWriteLock 读写锁。
25、TreadLocal
  • ThreadLocal实际上就是来解决线程不安全的问题的,ThreadLocal实际上给每一个线程都维护了一个本线程的ThreadLocalMap,这个map记录了变量的初始值,当他每次访问共享变量的时候实际上时访问了这个ThreadLocalMap中的变量,赋值也是如此通过这个Map将各个线程之间隔离做到各个线程之间不会相互影响。
ThreahLocal的应用场景
  • 我知道他的一个经典使用场景是位每一个线程分配jdbc连接connection,这样就可以保证每个线程在各自的connetion上面进行数据库的操作,不会出现a线程关闭了b线程的连接。此外ThreadLocal还经常用于session会话,将sesson保存在ThreadLocal中使线程处理多次处理会话始终得到的是同一个session
26、线程池
  • 系统启动一个新线程的成本是比较高的,因为他涉及与操作系统进行交互,在这中情况下,使用线程池可以很好的提高性能,尤其是当程序中需要创建大量生存期很短的线程的时候。
  • 线程池在启动的时候就会创建大量的空闲线程,程序将一个runnable对象或者callable对象传给线程池的时候,线程就会启动一个空闲线程来执行他们的run方法或者call放啊,当这两个方法执行完毕之后,该线程不会死亡而是回到了空闲状态,等待执行下一个run方法或者call方法
  • java5开始支持内建线程池
  • 线程池的工作流程:
    • 1、一个任务在提交之后,先判断核心线程是否已经满了,如果没有满的话会创建一个线程来执行这个任务
    • 2、判断任务队列是否已经满了,没有满则将新提交的任务添加到任务队列当中
    • 3、判断整个线程池是否已经满了,如果没有满通过创建新的线程来执行任务,如果已经满了采用拒绝策略。
  • 线程池的状态
    • running:表示线程池可以接受新的任务,并且能够处理工作队列当中的任务
    • shutdown:关闭状态,不能够再接收新的任务,但是它可以将工作队列当中任务继续执行完毕
    • stop:不能在接收新的线程,并且也无法再处理工作队列中的任务,处在running状态或者shutdown状态的时候,调用shutdownnow()方法会立即进入stop状态
    • 当所有的任务都处理完毕了,有效的线程数为0,此时就会进入TIDYING状态,这个状态调用terminated方法进入termenated状态。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值