目录
CyclicBarrier 和 CountDownLatch
创建线程有几种方式
创建线程的方式可以分为四种:
-
继承Thread类并重写run方法创建线程,这种方式实现简单但线程类不可以再继承其他类
-
实现Runnable接口并重写run方法,这种方式避免了单继承局限性,编程更加灵活,实现解耦
-
实现Callable 接口并重写call方法,这种方式可以获取线程执行结果的返回值,并且可以抛出异常
-
使用线程池创建
runnable和callable的区别
这两个接口都是线程任务类的接口,区别点在于
-
Runnable接口run方法无返回值;Callable接口call方法有返回值,也就是说如果需要获取线程类的执行结果,必须要使用Callable
-
Runnable接口run方法只能抛出运行时异常,且无法捕获处理;Callable接口call方法允许抛出异常,可以获取异常信息
start和run方法的区别
-
start() 方法:
start()
方法用于启动一个新线程,并使线程进入就绪(Runnable)状态,然后由Java虚拟机调用该线程的run()
方法。- 在调用
start()
方法后,线程会在新的调用栈(call stack)中执行run()
方法,这是多线程并发执行的一种标准方式。
Thread thread = new Thread(() -> { // 线程执行的任务 System.out.println("Thread running"); }); thread.start(); // 启动线程
-
run() 方法:
run()
方法定义了线程的主要工作逻辑,是线程的入口点。- 如果直接调用
run()
方法,它会在当前线程的调用栈中顺序执行,而不会启动新的线程。
Thread thread = new Thread(() -> { // 线程执行的任务 System.out.println("Thread running"); }); thread.run(); // 在当前线程中执行,不会启动新的线程
start()
方法用于启动一个新线程并执行 run()
方法的内容,而直接调用 run()
方法则会在当前线程中按顺序执行 run()
方法的内容,没有实现多线程的效果。在使用多线程时,通常应该调用 start()
方法来启动新线程,而不是直接调用 run()
方法。
notify和notifyAll的区别
这两个方法都是用户唤醒被wait方法休眠的线程的,区别点在于:
-
notifyAll:唤醒所有wait的线程
-
notify:随机唤醒一个 wait 线程
sleep和wait的区别
sleep和wait都是Java中用来让线程暂时放弃CPU使用权,进入阻塞状态的方法。他们的主要区别点有下面几个:
-
方法归属不同:sleep是Thread 的静态方法,而wait是Object的成员方法
-
醒来时机不同: sleep会在指定的时间后自动苏醒,而wait需要其他线程的唤醒
-
锁特性不同:sleep不会释放锁,而wait会释放锁
-
使用限制不同:wait必须用在synchronized代码块中,而sleep无此限制
说一下线程的状态及转换
线程共分为7种状态,分别是:新建、就绪、运行、终止以及阻塞、等待、计时等待
它们之间的转换关系是这样的:
-
当线程new出来之后,没有start之前就会处于新建状态
-
当线程执行start方法之后,就进入就绪状态
-
当就绪的线程一旦获取到了cpu的执行权,就可以进入运行状态
-
当线程执行完了run方法之后,就进入了死亡状态
这是一条正常的流程,但是代码在运行状态下可以因为一些原因进入到其它状态,比如说:
-
当进行抢锁操作时,抢锁失败就会进入到阻塞状态
-
当代码调用了wait方法时,就会进入等待状态
-
当代码调用了sleep方法时,就会进入计时等待状态
synchronized的实现原理
在Java中,每个对象都隐式包含一个 monitor(监视器)对象,加锁的过程其实就是竞争 monitor 的过程,
当线程进入字节码monitorEnter指令之后,线程将持有monitor对象,执行monitorExit时释放 monitor 对象
如果在这个过程中,其它线程就会阻塞等待获取该monitor对象
CAS与Synchronized
CAS和Synchronized都可以保证多线程环境下共享数据的安全性。那么他们两者有什么区别?
Synchronized是从悲观的角度出发:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线
程阻塞,用完后再把资源转让给其它线程)。因此Synchronized我们也将其称之为悲观锁。jdk中的ReentrantLock也是一种悲观锁。
CAS是从乐观的角度出发:
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。CAS这种机制我们也可以将其称之为乐观锁。
Java中有哪些锁
Java中的锁有很多,根据不同的维度,我给您说一下我了解的一些:
-
悲观锁和乐观锁
悲观锁:就是认为自己在操作一个共享数据的时候,一定还有其它人来操作,所以直接锁定资源,比如:synchronized、Lock
乐观锁:就是认为自己在操作一个共享数据的时候,一定不会有其它人来操作,所有不锁定资源,而是在更新前使用类似于CAS的机制去判断
-
可重入锁&非可重入锁
可重入锁是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁,不会因为之前已经获取过还没释放而阻塞
ReentrantLock 和 synchronized 都是可重入锁
不可重入锁:与可重入相反,获取锁后不能重复获取,否则会死锁(自己锁自己)
-
自旋锁
自选锁即是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待(不放弃 CPU 资源)
然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环,此时获取锁的线程一直处于活跃状态(而非阻塞)
它的好处是不会使线程状态发生切换,不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快
缺点是如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU
synchronized 和 lock 的区别?
Synchronized 是 Java 并发编程中很重要的关键字,另外一个很重要的是 volatile。 Syncronized 的目的是一次只允许一个线程进入由他修饰的代码段,从而允许他们进行自我保护。Synchronized 很像生活中的锁例子,进入由 Synchronized 保护的代码区首先需要获取 Synchronized 这把锁,其他线程想要执行必须进行等待。 Synchronized 锁住的代码区域执行完成后需要把锁归还,也就是释放锁,这样才能够让其他线程使用。
Lock 是 Java 并发编程中很重要的一个接口,它要比 Synchronized 关键字更能直译“锁”的概念,Lock 需要手动加锁和手动解锁,一般通过 lock.lock()方法来进行加锁,通过 lock.unlock()方法进行解锁。与 Lock 关联密切的锁有 ReetrantLock 和 ReadWriteLock。
Lock 实现和 synchronized 不一样,后者是一种悲观锁,它胆子很小,它很怕有人和它抢吃的,所以它每次吃东西前都把自己关起来。而! Lock 呢底层其实是 CAS 乐观锁的体现,它无所谓,别人抢了它吃的,它重新去拿吃的就好啦,所以它很乐观。
Synchronzied 和 Lock 的主要区别如下:
-
存在层面:Syncronized 是 Java 中的一个关键字,存在于 JVM 层面,Lock 是 Java 中的一个接口
-
锁的释放条件:1.获取锁的线程执行完同步代码后,自动释放;2.线程发生异常时,JVM 会让线程释放锁;Lock 必须在 finally 关键字中释放锁,不然容易造成线程死锁
-
锁的获取: 在 Syncronized 中,假设线程 A 获得锁,B 线程等待。如果 A 发生阻塞,那么 B 会一直等待。在 Lock 中,会分情况而定,Lock 中有尝试获取锁的方法,如果尝试获取到锁,则不用一直等待
-
锁的状态:Synchronized 无法判断锁的状态,Lock 则可以判断
-
锁的类型:Synchronized 是可重入,不可中断,非公平锁;Lock 锁则是可重入,可判断,可公平锁
-
锁的性能:Synchronized 适用于少量同步的情况下,性能开销比较大。Lock 锁适用于大量同步阶段:
–Lock 锁可以提高多个线程进行读的效率(使用 readWriteLock)
-
在竞争不是很激烈的情况下,Synchronized 的性能要优于 ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized 的性能会下降几十倍,但是 ReetrantLock 的性能能维持常态;
•ReetrantLock 提供了多样化的同步,比如有时间限制的同步,可以
被 Interrupt 的同步(synchronized 的同步是不能 Interrupt 的)等。
synchronized 和 volatile 的区别?
volatile 关键字是线程同步的轻量级实现,所以 volatile 性能比 synchronized 关键字要好。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块。!
多线程访问 volatile 关键字不会发生阻塞,而 synchronized 关键字可能会发生阻塞。 volatile 关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。
volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
CyclicBarrier 和 CountDownLatch
两个看上去有点像的类,都在 java.util.concurrent 下,都可以用来表示代码运行到 某个点上,二者的区别在于:
-
CyclicBarrier 的某个线程运行到某个点上之后,该线程即停止运行,直到所有的 线程都到达了这个点,所有线程才重新运行;CountDownLatch 则不是,某线程运行到某个点上之后,只是给某个数值-1 而已,该线程继续运行
-
CyclicBarrier 只能唤起一个任务,CountDownLatch 可以唤起多个任务
-
CyclicBarrier 可 重 用 , CountDownLatch 不 可 重 用 , 计 数 值 为 0 该 CountDownLatch就不可再用了