并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、死锁、线程不安全等等。
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:

由上图可以看出:线程创建之后它将处于 NEW(初始) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(就绪/可运行) 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 RUNNING(运行) 状态。
在操作系统层面,线程有 READY 和 RUNNING 状态;而在 JVM 层面,只能看到 RUNNABLE 状态,所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。
为什么 JVM 没有区分这两种状态呢?
java 现在的时分(time-sharing)多任务(multi-task)操作系统架构通常都是用所谓的“时间分片(time quantum or time slice)”方式进行抢占式(preemptive)轮转调度(round-robin 式)。这个时间分片通常是很小的,一个线程一次最多只能在 CPU 上运行比如 10-20ms 的时间(此时处于 running 状态),也即大概只有 0.01 秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度。(也即回到 ready 状态)。线程切换的如此之快,区分这两种状态就没什么意义了。
当线程执行 wait()方法之后,线程进入 WAITING(等待) 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态。
TIMED_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将线程置于 TIMED_WAITING 状态。当超时时间结束后,线程将会返回到 RUNNABLE 状态。
当线程进入 synchronized 方法/块或者调用 wait 后,(被 notify)想要重新进入 synchronized 方法/块时,但是锁被其它线程占有,这个时候线程就会进入 BLOCKED(阻塞) 状态。
线程在执行完了 run()方法之后将会进入到 TERMINATED(终止) 状态。
线程在执行过程中会有自己的运行条件和状态(也称上下文),比如说每个线程都有自己的程序计数器,栈信息等。当出现如下情况的时候,线程会从占用 CPU 状态中退出。
这其中前三种都会发生线程切换,线程切换意味着需要保存当前线程的上下文,留待线程下次占用 CPU 的时候恢复现场。并加载下一个将要占用 CPU 的线程上下文。这就是所谓的 上下文切换。
上下文切换是现代操作系统的基本功能,因其每次需要保存“信息恢复”信息,这将会占用 CPU,内存等系统资源进行处理,也就意味着效率会有一定损耗,如果频繁切换就会造成整体效率低下。
死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法继续下去,称为死锁。
如下图所示:线程A持有资源1,线程B拥有资源2,他们都想拥有对方的资源,所以这两个线程就会相互等待而进入死锁状态。

破坏死锁的产生的必要条件即可:
破坏互斥条件 : 这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
破坏请求与保持条件 : 一次性申请所有的资源。
破坏不剥夺条件 : 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件 : 靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放,破坏循环等待条件。
锁排序法: 指定获取锁的顺序,比如某个线程只有获得A锁和B锁,才能对某资源进行操作,在多线程条件下,如何避免死锁? 通过指定锁的获取顺序,比如规定,只有获得A锁的线程才有资格获取B锁,按顺序获取锁就可以避免死锁。这通常被认为是解决死锁很好的一种方法。
使用显式锁中的ReentrantLock.try(long,TimeUnit)来申请锁。
避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。
安全状态 指的是系统能够按照某种线程推进顺序(P1、P2、P3.....Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,
使每个线程都可顺利完成。称 <P1、P2、P3.....Pn> 序列为安全序列。
共同点:
不同点:
wait()和notify()方法是用来让不同的线程协作的,比如等待其他线程完成一些操作后再执行下一步操作。但是,如果我们在不同的线程之间共享一些数据,那么就需要确保这些数据在不同线程之间的访问是安全的,避免出现数据不一致等问题。为了确保数据的安全,我们需要使用同步方法或同步代码块来保护共享数据。而wait()和notify()方法必须在同步方法或同步代码块中使用,才能确保线程的安全性。这是因为wait()方法会释放锁,如果不在同步方法或同步代码块中使用,就无法保证线程在等待过程中不会被其他线程修改共享变量的值,从而可能导致数据不一致的问题。 而notify()方法也需要在同步方法或同步代码块中使用,否则可能会因为线程间的竞争而导致notify()方法没有正确地唤醒线程。同时,将wait()和notify()方法放在同步方法或同步代码块中,也可以避免死锁和永久等待的问题。
wait()方法是让获得对象锁的线程实现等待,并自动释放当前线程占有的对象锁。每个对象(Object)都拥有对象锁,既然要释放当前线程占有的对象锁并让其进入WAITING状态,自然要操作对应的对象(Object)而非当前的线程(Thread)。
因为 sleep() 是让当前线程暂停执行,不涉及到对象类,也不需要获得对象锁。
new 一个 Thread,这个线程进入初始状态。让这个线程调用start()方法,会启动这个线程并使这个线程进入就绪/可运行(Ready)状态,当这个线程分配到时间片后就可以开始运行了(RUNNING状态)。start()方法会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作。直接执行run()方法,会把run()方法当成main线程下的一个普通方法去执行,并不会在某个线程中去执行,所以这并不是多线程工作。
总结:调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。