JUC简介

JUC是java.util.concurrent工具包的简称,这是一个处理线程的工具包,JDK1.5开始出现的。

线程与进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位,是操作系统结构的基础。在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。
线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
总得来说。进程,指在系统中正在运行的一个应用程序;程序一旦运行就是进程;进程是资源分配的最小单位。线程,系统分配处理器时间资源的基本单元,或者说进程之内独立执行的个单元执行流。线程程序执行的最小单位。

线程的状态

线程的状态包括NEW(新建)RUNNABLE(准备就绪)BLOCKED(阻塞)WAITING(不见不散)TIMED_WATING(过时不候)TERMINATED(终结)

wait/sleep

sleepThread 的静态方法,waitObject的方法,任意对象实例都能调用。
sleep不会释放锁,它也不需要占用锁。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在 synchronized中)。
它们都可以被 interrupted方法中断。

并发和并行

并发:同一时刻多个线程在访问同一个资源,多个线程对一个点。例子:春运抢票 电商秒杀…..
并行:多项工作一起执行,之后再汇总。例子:泡方便面,电水壶烧水,一边撕调料倒入桶中。

管程

是一种同步机制,保证同一个时间,只有一个线程访问被保护数据或者代码。

用户线程和守护线程

用户线程:自定义线程。主线程结束了,用户线程还在运行,JVM存活。
守护线程:比如垃圾回收。没有用户线程了,都是守护线程,JVM结束。

Lock接口

synchronized关键字

synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:

  • 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  • 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
    • 虽然可以使用 synchronized 来定义方法,但 synchronized 并不属于方法定义的一部分,因此synchronized 关键字不能被继承。如果在父类中的某个方法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized 关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同方法,因此,子类的方法也就相当于同步了。
  • 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  • 修饰一个类,其作用的范围是 synchronized 后面括号括起来的部分,作用主的对象是这个类的所有对象。

下面用三个线程模拟卖票的例子

  1. public class Ticket {
  2. private int number = 30;
  3. public synchronized void sale(){
  4. if (number > 0){
  5. System.out.println(Thread.currentThread().getName()+"卖出票,"+"剩下:"+(--number));
  6. }
  7. }
  8. }
  9. public class SaleTicket {
  10. public static void main(String[] args) {
  11. Ticket ticket = new Ticket();
  12. new Thread(new Runnable() {
  13. @Override
  14. public void run() {
  15. for (int i=0;i<30;i++){
  16. ticket.sale();
  17. }
  18. }
  19. }, "A").start();
  20. new Thread(new Runnable() {
  21. @Override
  22. public void run() {
  23. for (int i=0;i<30;i++){
  24. ticket.sale();
  25. }
  26. }
  27. }, "B").start();
  28. new Thread(new Runnable() {
  29. @Override
  30. public void run() {
  31. for (int i=0;i<30;i++){
  32. ticket.sale();
  33. }
  34. }
  35. }, "C").start();
  36. }
  37. }

创建线程的多种方式

  • 继承Thread
  • 实现Runnable 接口
  • 使用Callable 接口
  • 使用线程池

Lock接口

Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作。它允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。
Lock 接口有实现类ReentrantLockReentrantReadWriteLock.ReadLockReentrantReadWriteLock.WriteLock

LockSynchronized 区别

  • Lock不是Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内置特性。Lock 是一个类,通过这个类可以实现同步访问;
  • synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而 Lock 在发生异常时,如果没有主动通过 unLock()去释放锁,则很可能造成死锁现象,因此使用 Lock 时需要在 finally 块中释放锁;
  • Lock 可以让等待锁的线程响应中断,而 synchronized 却不行,使用synchronized 时,等待的线程会一直等待下去,不能够响应中断;
  • 通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。
  • Lock 可以提高多个线程进行读操作的效率。

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时 Lock 的性能要远远优于synchronized

下面用Lock接口实现刚才卖票的例子(可重入锁)

  1. public class TicketLock {
  2. private int number = 30;
  3. private final ReentrantLock lock = new ReentrantLock();
  4. public void sale(){
  5. //上锁
  6. lock.lock();
  7. try {
  8. if (number > 0){
  9. System.out.println(Thread.currentThread().getName()+"卖出票,"+"剩下:"+(--number));
  10. }
  11. }finally {
  12. //解锁
  13. lock.unlock();
  14. }
  15. }
  16. }
  17. public class SaleTicket {
  18. public static void main(String[] args) {
  19. TicketLock ticket = new TicketLock();
  20. new Thread(() -> {
  21. for (int i=0;i<30;i++){
  22. ticket.sale();
  23. }
  24. }, "A").start();
  25. new Thread(() -> {
  26. for (int i=0;i<30;i++){
  27. ticket.sale();
  28. }
  29. }, "B").start();
  30. new Thread(() -> {
  31. for (int i=0;i<30;i++){
  32. ticket.sale();
  33. }
  34. }, "C").start();
  35. }
  36. }

线程间通信

以一个加减1为例,先用synchronized 来实现

  1. public class Share {
  2. private int number = 0;
  3. public synchronized void incr() throws InterruptedException {
  4. while (number != 0){
  5. wait();
  6. }
  7. System.out.println("值增加:"+(number++)+" ==> "+number);
  8. notifyAll();
  9. }
  10. public synchronized void decr() throws InterruptedException {
  11. while (number <= 0){
  12. wait();
  13. }
  14. System.out.println("值减少:"+(number--)+" ==> "+number);
  15. notifyAll();
  16. }
  17. }
  18. public class ShareMain {
  19. public static void main(String[] args) {
  20. Share share = new Share();
  21. new Thread(()->{
  22. for (int i=0;i<10;i++){
  23. try {
  24. share.incr();
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. }, "A").start();
  30. new Thread(()->{
  31. for (int i=0;i<10;i++){
  32. try {
  33. share.decr();
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. }, "B").start();
  39. }
  40. }

结果是这种交替出现的样子
image.png

多线程编程步骤

  • 创建资源类,在资源类创建属性和操作方法
  • 在资源类操作方法
    • 判断
    • 干活
    • 通知
  • 创建多个线程,调用资源类的操作方法
  • 防止虚假唤醒问题

线程间定制化通信

实现下面的场景,A打印5次,B打印10次,C打印15次,循环往复进行10轮。
image.png

  1. public class OutLock {
  2. /**
  3. * 定义标志位
  4. */
  5. private int flag = 1;
  6. private Lock lock = new ReentrantLock();
  7. private Condition c1 = lock.newCondition();
  8. private Condition c2 = lock.newCondition();
  9. private Condition c3 = lock.newCondition();
  10. /**
  11. * 打印A
  12. */
  13. public void outA() throws InterruptedException {
  14. lock.lock();
  15. try {
  16. while (flag != 1){
  17. //等待
  18. c1.await();
  19. }
  20. for (int i=0; i<5;i++){
  21. System.out.println(Thread.currentThread().getName());
  22. }
  23. flag = 2;
  24. //通知
  25. c2.signal();
  26. }finally {
  27. lock.unlock();
  28. }
  29. }
  30. /**
  31. * 打印B
  32. */
  33. public void outB() throws InterruptedException {
  34. lock.lock();
  35. try {
  36. while (flag != 2){
  37. //等待
  38. c2.await();
  39. }
  40. for (int i=0; i<10;i++){
  41. System.out.println(Thread.currentThread().getName());
  42. }
  43. flag = 3;
  44. //通知
  45. c3.signal();
  46. }finally {
  47. lock.unlock();
  48. }
  49. }
  50. /**
  51. * 打印C
  52. */
  53. public void outC() throws InterruptedException {
  54. lock.lock();
  55. try {
  56. while (flag != 3){
  57. //等待
  58. c3.await();
  59. }
  60. for (int i=0; i<15;i++){
  61. System.out.println(Thread.currentThread().getName());
  62. }
  63. flag = 1;
  64. //通知
  65. c1.signal();
  66. }finally {
  67. lock.unlock();
  68. }
  69. }
  70. }
  71. public class OutLockMain {
  72. public static void main(String[] args) {
  73. OutLock outLock = new OutLock();
  74. new Thread(() -> {
  75. for (int i=0; i<10;i++){
  76. try {
  77. outLock.outA();
  78. } catch (InterruptedException e) {
  79. e.printStackTrace();
  80. }
  81. }
  82. }, "A").start();
  83. new Thread(() -> {
  84. for (int i=0; i<10;i++){
  85. try {
  86. outLock.outB();
  87. } catch (InterruptedException e) {
  88. e.printStackTrace();
  89. }
  90. }
  91. }, "B").start();
  92. new Thread(() -> {
  93. for (int i=0; i<10;i++){
  94. try {
  95. outLock.outC();
  96. } catch (InterruptedException e) {
  97. e.printStackTrace();
  98. }
  99. }
  100. }, "C").start();
  101. }
  102. }

集合的线程安全

先演示一个集合的线程不安全例子

  1. ArrayList<String> list = new ArrayList<>();
  2. for (int i=0;i<30;i++){
  3. new Thread(() -> {
  4. list.add(UUID.randomUUID().toString().substring(0,5));
  5. System.out.println(list);
  6. },String.valueOf(i)).start();
  7. }
  1. 会报出如下异常<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2961643/1646730775401-25e24b1f-7e74-4433-bb27-d04b1377d20c.png#clientId=u8f5376a5-8292-4&from=paste&height=249&id=u458a53de&margin=%5Bobject%20Object%5D&name=image.png&originHeight=249&originWidth=913&originalType=binary&ratio=1&size=32615&status=done&style=none&taskId=uf9f2991e-55d3-48ac-ae76-d43c6f651cd&width=913)<br />即`java.util.ConcurrentModificationException`,并发修改异常。<br />出现上述问题是因为`ArrayList`的`add()`方法没有同步,它是线程不安全的

Vector

Vector是线程安全的

  1. List<String> list = new Vector<>();
  2. for (int i=0;i<30;i++){
  3. new Thread(() -> {
  4. list.add(UUID.randomUUID().toString().substring(0,5));
  5. System.out.println(list);
  6. },String.valueOf(i)).start();
  7. }

但是使用Vector效率较低

Collections

Collections工具类中有_synchronizedList()_方法,可以返回一个同步的List

  1. List<String> list = Collections.synchronizedList(new ArrayList<>());
  2. for (int i=0;i<30;i++){
  3. new Thread(() -> {
  4. list.add(UUID.randomUUID().toString().substring(0,5));
  5. System.out.println(list);
  6. },String.valueOf(i)).start();
  7. }

CopyOnWriteArrayList

使用JUC提供的类CopyOnWriteArrayList,写时复制技术

  1. List<String> list = new CopyOnWriteArrayList<>();
  2. for (int i=0;i<30;i++){
  3. new Thread(() -> {
  4. list.add(UUID.randomUUID().toString().substring(0,5));
  5. System.out.println(list);
  6. },String.valueOf(i)).start();
  7. }
  1. `CopyOnWriteArrayList`采用并发读、独立写,最后再合并<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/2961643/1646731669308-9f0dc7ea-8356-4f4c-b0f6-51b8d31dca08.png#clientId=u8f5376a5-8292-4&from=paste&height=275&id=ub8c4cca3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=275&originWidth=700&originalType=binary&ratio=1&size=69339&status=done&style=none&taskId=u62b08600-cad1-4002-8f76-2f6bb17db00&width=700)