java多线程(一)

851 阅读8分钟

何日请缨提锐旅,一鞭直渡清河洛。

概述

进程 = 服务器、PC或Mac上同时运行的多个应用程序 线程 = 在一个进程中运行多个任务

进程

进程——当一个软件应用程序开始运行时,它会使用系统资源,如I/O设备、CPURAMHDD,可能还有网络资源。类似地,其他软件应用程序也希望同时使用相同的系统资源。为了让多个软件应用程序共享系统资源,它们之间必须有明确的边界。否则,它们运行时会相互影响。通过进程来实现这种隔离。由于多个进程可以同时运行,一个操作系统通过进程执行多个任务!

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e2490631128f46~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e2490631128f46~tplv-t2oaga2asx-image.image

使用CPU,内存,磁盘和网络在OS中运行的进程

线程

线程—软件应用程序(例如Word文档)可能不得不在处理用户事件和保存当前工作之间进行多任务处理。为了实现这一目标,每个任务都需要访问该进程可用的相同系统资源,但要在其自己的空间内。与OS相似,每个进程都可以通过线程为运行于其中的多个任务提供隔离。由于多个线程可以在进程中同时运行,因此一个进程通过线程执行多任务!

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e248fb8d6b8481~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e248fb8d6b8481~tplv-t2oaga2asx-image.image

Java进程(Java虚拟机)中运行的线程

单线程与多线程应用程序

单线程应用

打个比方:一家有8名成员的厨房部门的餐厅在任何给定时间都只能提供一张桌子,因为他们只有一名服务员。如果有更多客人,他们只能在大厅等候。

缺点

  • 在大厅等候的客人肯定很沮丧。
  • 八个人在厨房,浪费资源。

优点

  • 复杂性降低,一次指接待一批客人。

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e24a0548109269~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e24a0548109269~tplv-t2oaga2asx-image.image

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e24906312808b5~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e24906312808b5~tplv-t2oaga2asx-image.image

单线程应用

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e249063147f3eb~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e249063147f3eb~tplv-t2oaga2asx-image.image

执行时间

多线程应用

一家有8名成员的厨房部门的餐厅在任何给定时间都只能提供16张桌子,因为他们有4个服务员。 优点

  • 客人的等待时间打打减少
  • 充分合理的利用资源

缺点

  • 资源共享到时业务复杂

由于餐厅现在引入了更多的服务员(线程),因此它正在有效地利用厨房(CPU)人员(核心)。结果,它在任何给定时间服务于更多的来宾(用户)

这同样适用于软件应用程序。由于并发执行,本质上导致了响应能力的显着改善。

同样,可以拆分为多个独立子任务的长时间运行的任务可以在多个线程中并行运行,从而显着提高了应用程序的性能。

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e248fd704716ca~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e248fd704716ca~tplv-t2oaga2asx-image.image

多线程应用

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e248fd568fe1f6~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e248fd568fe1f6~tplv-t2oaga2asx-image.image

执行时间

并发带来的响应和并行引起的性能是多线程应用程序的动机。

堆和栈

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e248fd03eacba5~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e248fd03eacba5~tplv-t2oaga2asx-image.image

上面我们编写了一个由线程执行的代码块。Java应用程序启动时,Java进程会生成主线程(main方法),这是应用程序的入口点。从现在开始,整个应用程序逻辑要么在主线程中执行,要么在我们从应用程序中派生的线程中执行,以实现并发执行

线程执行的应用程序逻辑在CPU中执行计算,计算结果则存储在RAM中。

每个线程将等待轮流使用一个CPU内核执行计算,并等待一个本地内存区域(每个方法调用的栈和栈中的多个栈帧)临时存储计算结果。线程完成代码执行后,通常会将结果刷新回RAM(堆)。

堆是所有对象所在的线程之间的共享内存区域。 堆栈是分配给每个正在运行的线程的专用内存区域。 堆内存是垃圾收集器可直接回收的,通过删除应用程序中不再使用(引用)的对象来释放空间,而栈持有的内存空间在执行线程完成后被释放

Java应用程序中创建的所有对象都在堆的内存中分配了空间。只要从应用程序中的某处引用它,这些对象就存在。

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e2491639ab61c8~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e2491639ab61c8~tplv-t2oaga2asx-image.image

执行某个方法或调用一系列方法的线程将需要内存中的空间来存储局部变量和方法参数——这个内存区域称为栈。线程调用的每个方法都堆叠在前面的方法之上——调用称为栈帧

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e248fd7059d0a2~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e248fd7059d0a2~tplv-t2oaga2asx-image.image

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e2490631a065e9~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e2490631a065e9~tplv-t2oaga2asx-image.image

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e2491639c28b23~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e2491639c28b23~tplv-t2oaga2asx-image.image

monitor和锁

当一个对象及其状态被多个线程共享时,对该状态所做的任何修改(例如。网页计数器),必须以原子操作执行。否则,对象的状态将被并发修改破坏。原子性是通过使用锁来保护关键代码块来实现的,从而在相互竞争的线程之间强制互斥。

每个对象都有一个称为monitor的固有锁。由于这种语言规定,通过向方法签名中添加关键字synchronized,可以很容易地锁定关键代码块。在下面的程序中,安全对象的open()方法可以被线程访问,只有在获取了内部锁之后——注意方法签名中的synchronized关键字。

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e249e062cae4f0~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e249e062cae4f0~tplv-t2oaga2asx-image.image

https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e249d081291f78~tplv-t2oaga2asx-image.image
https://p1-jj.byteimg.com/tos-cn-i-t2oaga2asx/gold-user-assets/2019/11/1/16e249d081291f78~tplv-t2oaga2asx-image.image

通过获取内部锁依次访问安全对象的线程

执行同步方法的线程被认为获得了对象的锁。在此线程完成方法的执行之前,没有其他线程可以执行此对象的这个或任何其他同步方法。

但是已经可以访问该对象锁的线程可以调用其他同步方法,而无需重新获取该锁。此机制称为重入锁定或重入同步。

锁或同步的另一个重要方面是建立happens-before关系。也就是说,在synchronized块中对对象的状态所做的任何修改都保证对其他线程可见,这些线程随后将通过获取相同的锁来访问相同的状态。

获取对象锁的方法有两种

  • 通过在方法上添加synchronized关键字
  • 使用 synchronized同步代码块

使用同步代码块是首选方法,因为它不会阻塞所有实例方法,而只会阻塞要防止并发访问的阻塞,从而提高了性能。

实例方法的同步对象是this当前对象 静态方法的同步对象是当前对象的class

实际上,同步(或加锁)的作用是防止多线程应用程序中出现以下错误情况

  • 线程干扰——多个线程同时修改一个实例的状态,并在进程中破坏它,因为相互竞争的线程计划运行的时间不同,从而导致它们无法按顺序执行同一组操作。
  • 内存不一致错误——即使写线程在读取之前完成了执行,读线程也会看到过时的数据。这是因为写线程会将更改后的值存储在CPU缓存中,而不是将其刷新到主内存中,其他所有线程都可以在主内存中看到更新后的状态。

当竞争线程修改/修改或修改/读取共享的可变状态时,如果没有适当的同步(涉及非原子操作),则由于交织而导致“竞争条件”和“内存不一致错误”

总结

  • 操作系统将CPURAMHDD、网络设备等硬件资源提供给软件应用程序进行计算。
  • 这些资源在操作系统中作为进程同时运行的多个应用程序之间共享。操作系统的强大之处在于其通过进程执行多任务的能力。
  • 同样,作为进程运行的单个软件应用程序必须执行多任务,以便有效利用硬件资源。进程的能力在于它可以通过并发运行的线程来执行多任务。
  • 通过并发性(例如。:一个servlet同时服务多个用户),通过并行的性能(例如。:并行调用多个HTTP端点来服务一个用户请求)是多线程应用程序的动机。
  • 线程在CPU内核中运行。任何应用程序代码都在线程中运行。启动Java应用程序时,JVM从主线程内调用main()方法。
  • 堆是分配给所有线程以存储对象的公共内存区域。任何引用堆中对象的线程都可以读取/修改该对象。
  • 栈是分配给每个线程的私有内存区域(例如:两个线程同时调用一个通用实用程序方法将在其自己的栈中执行该方法,其中一个线程的局部变量和方法参数对另一个线程不可见)
  • 获取公共锁后,访问堆中同一对象的多个线程必须同步执行此操作,以防止破坏对象的状态。

原文链接:Java: Multithreading — Part 1