多线程简记

多线程实现方式:

  1、继承thread类重写run方法

  2、实现runable接口重写run方法

  3、实现callable接口重写call方法 (可以有返回值,可以捕捉异常。)-> Future:表示一个异步运算的任务,传入一个callable的具体实现类,返回执行结果,get方法会阻塞。是runable的实现类,可以放入线程池中。

lamda表达式 要求是函数式接口(只有一个方法的接口) (参数) --> {具体实现方法}

线程状态 Thread.state state = thread.getState();

 新建   就绪   阻塞   运行   死亡

new   start()   sleep   runable   terminted

     blocked 阻塞 时间到了就进入就绪

     waited 等待

     timed_waiting 超时等待 notify或者notifyAll之后,重新获取锁,进入就绪状态

设置优先级 setpriority:0~10 设置权重,增加被CPU调用的概率

设置守护线程 thread.setDaemon(true);JVM不用等守护线程执行完毕

线程同步:队列和锁机制,保证并发安全问题。

并发编程的意义:

   1、充分利用多核CPU的优势

   2、方便进行业务拆分提升应用性能 可能存在的一些问题:线程安全问题、上下文切换的资源消耗问题,死锁问题

并发编程三要素:

   原子性:synchronized、lock、java.util.concurrent.automic-原子类(CAS)

  可见性:synchronized、lock、volatile

  有序性:Happens-Before原则

并发与并行 并发:两个人用一台电脑,你用完我用。 并行: 两个人用两台电脑

进程与线程

   进程,一个执行的程序、操作系统资源分配的基本单位,有独立的代码和资源空间,一个进程至少包含一个线程。

   线程,是cpu执行和调度的基本单位,没有独立的数据空间,同一进程中的线程共享进程的资源空间,不能独立执行,需要依存在进程中。

上下文切换:CPU执行多线程的时候,通过分配时间片轮转形式。当执行完一个线程的时间片,切换执行另一个线程,就叫上下文切换。

死锁:多个线程互相持有对方需要的资源,同时都不肯释放形成的僵持状态。

死锁的四个条件:

  1、互斥条件:某资源只能有一个线程占有。

  2、占有且等待条件:线程已经持有某些资源,需要请求新的资源,当新资源被其他线程占有时,请求进程阻塞,并且不释放已占有的资源。

  3、不可抢占条件:不能强行抢占已被其他线程占有的资源。

  4、循环等待条件:多个线程形成收尾相接的循环等待资源关系。

避免死锁:

  1、避免一个线程同时获取多个锁

  2、需要多个锁的时候,尽量保证每个线程加锁顺序相同

  3、考虑使用lock.tryLock(timeout)来替代使用内部锁机制

run和start的区别 start方法用于启动线程吗,线程由新建状态转为就绪状态,等待CPU执行调用;run是一个普通方法,用于执行具体的代码。

线程调度算法: 1、分时调度模型:平均分配占有CPU的时间,并轮流执行。 2、抢占式调度模型:优先让优先级高的线程占有CPU。

线程调度器:一个操作系统服务,为就绪状态的线程分配CPU时间。

相关方法: wait sleep notify notifyALL

wait和sleep区别

  1、sleep是thread的静态方法;wait是Object类的方法;

  2、sleep不释放锁;wait会释放锁

  3、sleep一般用于短暂暂停线程执行;wait一般用于线程之间通信。

  4、wait需要被notify或nitifyAll唤醒,或者设置超时等待时间;sleep到时间之后,自动进入就绪状态。

wait需要在循环中使用,使用if的时候,有可能在不满足条件的情况下被虚假唤醒。

wait在object而不是thread的原因 java想让任何对象都可以作为锁,因此让任何类都有wait、notify方法可以实现等待与唤醒;放在Thread中,由于是一个线程线程可能持有多个锁,因此实现起来比较麻烦。

sleep和yield静态:只在当前正在运行的任务中执行。 区别:

  1、sleep让出CPU的时候不考虑优先级,低优先级的有机会执行;yield只会给相同优先级或者更高优先级的线程执行机会。

   2、sleep之后进入阻塞状态,阻塞完进入就绪状态;yield直接进入就绪状态;

  3、sleep会抛出interruptedException,yield没有声明异常。

停止正在运行的线程 1、run完成之后,线程终止 2、stop方法强行终止 3、interupted方法中断线程

阻塞式方法:程序会一直等待这个方法执行完毕而不做别的事。

线程通信 Java中线程通信协作的最常见方式:

  一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()

   二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()

保证线程安全 synchronized lock volatile+automic

JMM内存模型

8个基本操作:read   load   use   assign(赋值)store   write    lock  Unlock

多线程简记

as-if-serial规则和happens-before规则的区别

  1、as-if-serial语义保证单线程内程序的执行结果不被改变,happens-before关系保证正确同步的多 线程程序的执行结果不被改变。

  2、as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序来执行 的。happens-before关系给编写正确同步的多线程程序的程序员创造了一个幻境:正确同步的多 线程程序是按happens-before指定的顺序来执行的。

  3、as-if-serial语义和happens-before这么做的目的,都是为了在不改变程序执行结果的前提下,尽 可能地提高程序执行的并行度。

synchronized:锁的是class或者对象 修饰方法:静态方法,锁的是class;实例方法,锁的是对象实例。 修饰代码块:指定加锁对象 底层原理: 每个对象有一个监视器锁(monitor)。每个Synchronized修饰过的代码当它的monitor被占用时就 会处于锁定状态并且尝试获取monitor的所有权 ,过程:

  1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为 monitor的所有者。

  2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

  3、如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再 重新尝试获取monitor的所有权。

升级原理:锁可以升级但是不能降级,偏向锁可以转为无锁状态 synchronized 锁升级原理:

  在锁对象的对象头里面有一个 threadid 字段,在第一次访问的时候 threadid 为空,jvm 让其持有偏向锁,并将 threadid 设置为其线程 id,再次进入的时候会先判断 threadid 是否与其线程 id 一致,如果一致则可以直接使用此对象,如果不一致,则升级偏向锁为   轻量级锁,通过自旋循环一定次数来获取锁,执行一定次数之后,如果还没有正常获取到要使用的 对象,此时就会把锁从轻量级升级为重量级锁,此过程就构成了 synchronized 锁的升级。

  锁的升级的目的:锁升级是为了减低了锁带来的性能消耗。在 Java 6 之后优化 synchronized 的实现方 式,使用了偏向锁升级为轻量级锁再升级到重量级锁的方式,从而减低了锁带来的性能消耗。 偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访 问,不存在多线程争用的情况,则线程是不需要触发同步的,减少加锁/解锁的一些CAS操作(比 如等待队列的一些CAS操作),这种情况下,就会给线程加一个偏向锁。 如果在运行过程中,遇 到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标 准的轻量级锁。 轻量级锁(避免用户态和核心态切换带来的消耗)是由偏向所升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁 争用的时候,轻量级锁就会升级为重量级锁; 重量级锁是synchronized ,是 Java 虚拟机中最为基础的锁实现。在这种状态下,Java 虚拟机会阻 塞加锁失败的线程,并且在目标锁被释放的时候,唤醒这些线程。

双重检索实现单例模式

public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进入加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}

自旋: 在不自旋的情况下,线程会进入等待状态,放弃CPU的使用权。这种上下文切换比较耗资源,由于大部分情况下,锁内执行的内容很快能够完成,所以干脆就让线程就行自循环,不放弃CPU的使用权,从而快速地在锁释放之后获得锁。

synchronized 和 Lock 有什么区别?

  首先synchronized是Java内置关键字,在JVM层面,Lock是个Java类;

  synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。

  synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁; 而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。

  通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

  ReentrantLock 底层调用的是 Unsafe 的park 方法加锁, synchronized 操作的应该是对象头中 mark word

 lock

  (1)可以使锁更公平

  (2)可以使线程在等待锁的时候响应中断

  (3)可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间

  (4)可以在不同的范围,以不同的顺序获取和释放锁 整体上来说 Lock 是 synchronized 的扩展版,Lock 提供了无条件的、可轮询的(tryLock 方法)、定 时的(tryLock 带参方法)、可中断的(lockInterruptibly)、可多条件队列的(newCondition 方法)锁操 作。另外 Lock 的实现类基本都支持非公平锁(默认)和公平锁,synchronized 只支持非公平锁,当 然,在大部分情况下,非公平锁是高效的选择。

volatile与synchronized volatile 是变量修饰符;synchronized 可以修饰类、方法、变量。 volatile 仅能实现变量的修改可见性,不能保证原子性;而 synchronized 则可以保证变量的修改 可见性和原子性。 volatile 不会造成线程的阻塞;synchronized 可能会造成线程的阻塞。 volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。 volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是 volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。synchronized关键 字在JavaSE1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的偏向锁和轻 量级锁以及其它各种优化之后执行效率有了显著提升,实际开发中使用 synchronized 关键字的场 景还是更多一些。

活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失 败。 活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,这就是所谓的“活”, 而处于死锁的 实体表现为等待;活锁有可能自行解开,死锁则不能。

线程池能够带来许多好处:

   降低资源消耗。

  通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

  提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

  提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降 低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是要做到合理利用控制并发数量

ThreadPoolExecutor就是线程池 七大参数

  corePoolSize 核心线程数量

  maximumPoolSize 最大线程数量

  keepAliveTime 线程保持时间,N个时间单位 unit 时间单位(比如秒,分)

  workQueue 阻塞队列

  threadFactory 线程工厂

  handler 线程池拒绝策略

多线程简记

 

 

excutor和excutors excutor是工具类,用来创建线程池 excutot是接口,实现对象用来执行线程任务execute()

线程池都有哪些状态?

  RUNNING:这是最正常的状态,接受新的任务,处理等待队列中的任务。

  SHUTDOWN:不接受新的任务提交,但是会继续处理等待队列中的任务。

  STOP:不接受新的任务提交,不再处理等待队列中的任务,中断正在执行任务的线程。

  TIDYING:所有的任务都销毁了,workCount 为 0,线程池的状态在转换为 TIDYING 状态时,会 执行钩子方法 terminated()。

  TERMINATED:terminated()方法结束后,线程池的状态就会变成这个。

线程池中 submit() 和 execute() 方法有什么区别?

   相同点: 相同点就是都可以开启线程执行池中的任务。

  不同点: 接收参数:execute()只能执行 Runnable 类型的任务。submit()可以执行 Runnable 和 Callable 类型的任务。 返回值:submit()方法可以返回持有计算结果的 Future 对象,而execute()没有 异常处理:submit()方便Exception处理

ThreadPoolExecutor饱和策略有哪些

  ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。

   ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务。您不会任务请求。但是 这种策略会降低对于新任务提交速度,影响程序的整体性能。另外,这个策略喜欢增加队列容量。 如果您的应用程序可以承受此延迟并且你不能任务丢弃任何一个任务请求的话,你可以选择这个策 略。

  ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。

  ThreadPoolExecutor.DiscardOldestPolicy: 此策略将丢弃最早的未处理(即队列中的)的任务请求。

 

根据CPU密集和IO密集来分配线程池

  CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。 CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开 几个模拟的多线程,该任务都不可能得到加速,因为CPU总的运算能力就那样。

  IO密集型,即该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费 大量的CPU运算能力浪费在等待。所以在IO密集型任务中使用多线程可以大大的加速程序运行, 即时在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。

并发队列:多个线程有次序共享数据的组件

blockinkQueue的四组方法

  报异常的 add remove

  返回true和false的 offer poll

  可以设置超时时间 阻塞的 put take

常用的并发工具类

  CountDownLatch 相当于减法计数器 CountDownLatch 类位于java.util.concurrent包下,利用它可以实现类似计数器的功能。比如有 一个任务A,它要等待其他3个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来 实现这种功能了。

  CyclicBarrier 相当于加法计算器 (回环栅栏) CyclicBarrier它的作用就是会让所有线程都等待完成后才会继续下一步行 动。 CyclicBarrier初始化时规定一个数目,然后计算调用了CyclicBarrier.await()进入等待的线程数。 当线程数达到了这个数目时,所有进入等待状态的线程被唤醒并继续。 CyclicBarrier初始时还可带一个Runnable的参数, 此Runnable任务在CyclicBarrier的数目达到 后,所有其它线程被唤醒前被执行。

  Semaphore 有点类似于停车场的车位数,acquire和release停车和开走 (信号量) Semaphore 是 synchronized 的加强版,作用是控制线程的并发数量(允许 自定义多少线程同时访问)。就这一点而言,单纯的synchronized 关键字是实现不了的。

ThreadLocal 是什么?有什么应用场景?

  ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作 用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。用来 解决数据库连接、Session 管理等。

ReadWriteLock 有什么用?

  ReadWriteLock 是一个读写锁接口,ReentrantReadWriteLock 是 ReadWriteLock 接 口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之 间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

FutureTask 表示一个异步运算的任务,FutureTask 里面可以传入一个 Callable 的具 体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、 取消任务等操作。

 

 

  

多线程简记

上一篇:linux开发教程,十年Java编程开发生涯,


下一篇:Jenkins 持续集成中如何使用邮件进行通知