首页 > 编程语言 >5. Java并发

5. Java并发

时间:2023-02-16 10:11:30浏览次数:55  
标签:状态 Java synchronized 关键字 并发 线程 volatile 方法

  • 什么是线程和进程?
    何为进程?
    进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。
    在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。
    在 Windows 中可以通过查看任务管理器的方式,看到 Windows 当前运行的进程(.exe 文件的运行)
    何为线程?
    线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
    Java 程序天生就是多线程程序
  • 线程生命周期有哪些,状态切换的过程。
    Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:
    NEW: 初始状态,线程被创建出来但没有被调用 start() 。
    RUNNABLE: 运行状态,线程被调用了 start()等待运行的状态。
    BLOCKED :阻塞状态,需要等待锁释放。
    WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
    TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
    TERMINATED:终止状态,表示该线程已经运行完毕。
    线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换
    线程创建之后它将处于 NEW(新建) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 RUNNING(运行) 状态
    当线程执行 wait()方法之后,线程进入 WAITING(等待) 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态。
    TIMED_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将线程置于 TIMED_WAITING 状态。当超时时间结束后,线程将会返回到 RUNNABLE 状态。
    当线程进入 synchronized 方法/块或者调用 wait 后(被 notify)重新进入 synchronized 方法/块,但是锁被其它线程占有,这个时候线程就会进入 BLOCKED(阻塞) 状态。
    线程在执行完了 run()方法之后将会进入到 TERMINATED(终止) 状态
  • 死锁产生的条件,如何避免死锁。
    产生死锁的四个必要条件:
    互斥条件:该资源任意一个时刻只由一个线程占用。
    请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
    不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
    循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

如何预防死锁? 破坏死锁的产生的必要条件即可:
破坏请求与保持条件 :一次性申请所有的资源。
破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

如何避免死锁?
避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态

  • synchronized 锁的是对象还是方法, 加在静态方法和实例方法的区别
    synchronized 是 Java 中的一个关键字,翻译成中文是同步的意思,主要解决的是多个线程之间访问资源的同步性,可以保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。

在 Java 早期版本中,synchronized 属于 重量级锁,效率低下。这是因为监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原生线程之上的。如果要挂起或者唤醒一个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。

不过,在 Java 6 之后, synchronized 引入了大量的优化如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销,这些优化让 synchronized 锁的效率提升了很多。因此, synchronized 还是可以在实际项目中使用的,像 JDK 源码、很多开源框架都大量使用了 synchronized
synchronized 关键字加到 static 静态方法和 synchronized(class) 代码块上都是是给 Class 类上锁;
synchronized 关键字加到实例方法上是给对象实例上锁;
尽量不要使用 synchronized(String a) 因为 JVM 中,字符串常量池具有缓存功能

  • synchronized 的底层实现,锁升级过程。
    synchronized 关键字底层原理属于 JVM 层面的东西
    synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。

synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。

不过两者的本质都是对对象监视器 monitor 的获取

锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁可以升级不可降级,这种策略是为了提高获得锁和释放锁的效率

  • volatile关键字的作用,原理
    volatile 关键字可以保证变量的可见性,如果我们将变量声明为 volatile ,这就指示 JVM,这个变量是共享且不稳定的,每次使用它都到主存中进行读取
    在 Java 中,volatile 关键字除了可以保证变量的可见性,还有一个重要的作用就是防止 JVM 的指令重排序。 如果我们将变量声明为 volatile ,在对这个变量进行读写操作的时候,会通过插入特定的 内存屏障 的方式来禁止指令重排序
    volatile 关键字能保证变量的可见性,但不能保证对变量的操作是原子性的
  • volatile和synchronized区别。
    synchronized 关键字和 volatile 关键字是两个互补的存在,而不是对立的存在!

volatile 关键字是线程同步的轻量级实现,所以 volatile性能肯定比synchronized关键字要好 。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 。
volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性

  • ReentrantLock和synchronized区别。
    两者都是可重入锁
    “可重入锁” 指的是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果是不可重入锁的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增 1,所以要等到锁的计数器下降为 0 时才能释放锁。

JDK 提供的所有现成的 Lock 实现类,包括 synchronized 关键字锁都是可重入的。

synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API
synchronized 是依赖于 JVM 实现的,前面我们也讲到了 虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的,并没有直接暴露给我们。

ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成),所以我们可以通过查看它的源代码,来看它是如何实现的。

ReentrantLock 比 synchronized 增加了一些高级功能
相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:

等待可中断 : ReentrantLock提供了一种能够中断等待锁的线程的机制,通过 lock.lockInterruptibly() 来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
可实现公平锁 : ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。
可实现选择性通知(锁可以绑定多个条件): synchronized关键字与wait()和notify()/notifyAll()方法相结合可以实现等待/通知机制。ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition()方法。

  • 为什么要用线程池?
    线程池提供了一种限制和管理资源(包括执行一个任务)的方式。 每个线程池还维护一些基本统计信息,例如已完成任务的数量。

这里借用《Java 并发编程的艺术》提到的来说一下使用线程池的好处:

降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

  • 如何创建线程池?
    方式一:通过构造方法实现
    方式二:通过 Executor 框架的工具类 Executors 来实现
  • 如何设定线程池的大小
    类比于现实世界中的人类通过合作做某件事情,我们可以肯定的一点是线程池大小设置过大或者过小都会有问题,合适的才是最好。

如果我们设置的线程池数量太小的话,如果同一时间有大量任务/请求需要处理,可能会导致大量的请求/任务在任务队列中排队等待执行,甚至会出现任务队列满了之后任务/请求无法处理的情况,或者大量任务堆积在任务队列导致 OOM。这样很明显是有问题的! CPU 根本没有得到充分利用。

但是,如果我们设置线程数量太大,大量线程可能会同时在争取 CPU 资源,这样会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率

  • ThreadLocal是什么?它的内存泄漏了解吗?
    ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据
    最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值
    每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。
这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry。假如我们不做任何措施的话,value 永远无法被 GC 回收,这个时候就可能会产生内存泄露。ThreadLocalMap 实现中已经考虑了这种情况,在调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal方法后 最好手动调用remove()方法

  • CAS相关。CAS的过程,CAS存在的问题,如何解决?
    CAS 的全称是 Compare And Swap(比较与交换) ,用于实现乐观锁,被广泛应用于各大框架中。CAS 的思想很简单,就是用一个预期值和要更新的变量值进行比较,两值相等才会进行更新。
    CAS 是一个原子操作,底层依赖于一条 CPU 的原子指令。
    原子操作 即最小不可拆分的操作,也就是说操作一旦开始,就不能被打断,直到操作完成。
    CAS 涉及到三个操作数:
    V :要更新的变量值(Var)
    E :预期值(Expected)
    N :拟写入的新值(New)
    当且仅当 V 的值等于 E 时,CAS 通过原子方式用新值 N 来更新 V 的值。如果不等,说明已经有其它线程更新了V,则当前线程放弃更新。
  • sleep() 方法和 wait() 方法区别和共同点?
    共同点 :两者都可以暂停线程的执行。

区别 :
sleep() 方法没有释放锁,而 wait() 方法释放了锁 。
wait() 通常被用于线程间交互/通信,sleep()通常被用于暂停执行。
wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法。sleep()方法执行完成后,线程会自动苏醒,或者也可以使用 wait(long timeout) 超时后线程会自动苏醒。
sleep() 是 Thread 类的静态本地方法,wait() 则是 Object 类的本地方法。

标签:状态,Java,synchronized,关键字,并发,线程,volatile,方法
From: https://www.cnblogs.com/song-hua/p/17125744.html

相关文章

  • Java最全八股文(2023最新整理)
    本文已经收录到Github仓库,该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校......
  • 读Java实战(第二版)笔记11_语言特性和类库更新
    1. 注解1.1. 一种使用附加信息装饰程序元素的机制1.2. Java8之前,只有声明可以被注解1.3. 一种语法元数据(syntacticmetadata)1.4. 可以用于文档编制1.4.1. @De......
  • AcWing 785.快速排序(Java)
    题目描述给定你一个长度为n的整数数列。请你使用快速排序对这个数列按照从小到大进行排序。并将排好序的数列按顺序输出。输入格式输入共两行,第一行包含整数n。第......
  • 【多线程与高并发】- 线程基础与状态
    线程基础与状态......
  • 如何处理 Java 中的 OutOfMemoryError 异常
    java.lang.OutOfMemoryError是Java中的一个运行时错误,它通常发生在Java虚拟机(JVM)由于Java堆内存不足而无法分配对象时。Java垃圾回收器(GC)无法释放新对象所需的空间,从......
  • Java类加载的执行过程
    类加载分为以下5个步骤:1.加载:根据查找路径找到相应的class文件然后导入;2.检查:检查加载的class文件的正确性;3.准备:给类中的静态变量分配内存空间;4.解析:虚拟......
  • java数组
    java数组相同类型数据的有序集合数组的建立定义数组//数据类型[]变量名=赋值//数据类型变量名[]=赋值声明建立//定义数组int[]num;//声明建立num=ne......
  • Java 8新特性之 Optional 类
    前言java.util.Optional是java8中引进的一个新的类,我们通过Optional类的源码可以看到,该方法的作用可以对可能缺失的值进行建模,而不是直接将null赋值给变量。Optional类......
  • JavaWeb文件上传(感谢狂神)
    1、准备工作采用Apache的开源工具common-fileupload这个文件上传组件。common-fileupload是依赖于common-io这个包的,所以还需要下载这个包。(这两个jar包需要下载引入,Tomc......
  • Java基础语法
    Java基础语法注释注释是不会执行的,而是给写代码的人看的。分为单行注释、多行注释、文档注释。单行注释://注释内容多行注释:/*注释内容*/文档注释(JavaDoc)......