Java并发1

Java基础 并发编程

什么是进程和线程

进程

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。

线程

线程是比进程更小的执行单位。一个进程在执行过程中可以产生多个线程。同类的线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈、本地方法栈。所以系统在产生一个线程,或者在线程之间切换工作时,负担要比进程小得多。因此,线程也被称为“轻量级进程”。

程序计数器为什么是私有的

程序计数器有两个作用:

  1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制。
  2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程切换回来的时候能够知道该线程上次运行到哪了。

所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置

虚拟机栈和本地方法栈为什么是私有的

为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。

堆和方法区

堆和方法区都是所有线程共享的内存空间。堆事进程中最大的一块内存,用于存放新的对象,方法区用于存放已被加载的类的信息、常量、静态变量、即时编译器编译后的代码等数据。方法区是一个概念,堆中的永久代或者元空间是方法区的具体实现。

并发与并行

同步和异步

为什么使用多线程

先从总体上来说:

再深入到计算机底层来探讨:

多线程可能的问题

内存泄漏、死锁、线程不安全等等

线程的生命周期和状态

Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:

线程创建之后它将处于 NEW(新建) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(可运行) 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 RUNNING(运行) 状态。

在操作系统层面,线程有 READY 和 RUNNING 状态;而在 JVM 层面,只能看到 RUNNABLE 状态,所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。

为什么 JVM 没有区分这两种状态呢?

现在的时分(time-sharing)多任务(multi-task)操作系统架构通常都是用所谓的“时间分片(time quantum or time slice)”方式进行抢占式(preemptive)轮转调度(round-robin 式)。这个时间分片通常是很小的,一个线程一次最多只能在 CPU 上运行比如 10-20ms 的时间(此时处于 running 状态),也即大概只有 0.01 秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度。(也即回到 ready 状态)。线程切换的如此之快,区分这两种状态就没什么意义了。

什么是上下文切换?

线程在执行过程中会有自己的运行条件和状态(也称上下文),比如程序计数器、栈信息等。当出现如下情况,线程会从占用CPU状态中退出:

前三种都会发生线程切换,线程切换意味着要保存当前线程的上下文,留待线程下次占用CPU的时候恢复现场,并加载下一个将要占用CPU的线程上下文。这就是所谓的 上下文切换

上下文切换是现代操作系统的基本功能,因其每次需要保存信息恢复信息,这将会占用 CPU,内存等系统资源进行处理,也就意味着效率会有一定损耗,如果频繁切换就会造成整体效率低下。

什么是线程死锁?

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

产生死锁的四个必要条件:

  1. 互斥条件:该资源任意一个时刻只由一个线程占用。
  2. 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  4. 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系

如何预防和避免死锁?

预防死锁

破坏死锁的产生的必要条件即可:

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

避免死锁

在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。

安全状态 指的是系统能够按照某种线程推进顺序(P1、P2、P3.....Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,使每个线程都可顺利完成。称 <P1、P2、P3.....Pn> 序列为安全序列。

sleep()方法和wait()方法对比

共同点 :两者都可以暂停线程的执行。

区别

为什么wait()方法不定义在Thread中?

wait()是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。每个对象都拥有对象锁,既然是要释放对象锁,自然是对对象进行操作而非线程。

反之,sleep()方法是让当前线程暂停执行,不涉及到对象锁,也不需要获得对象锁。

可以直接调用Thread类的run方法吗?

new一个Thread,线程就进入了新建状态。调用start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。start()会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程。但是,直接执行 run()  方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。 总结:调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。