1.进程是包含线程的,每个进程内部的线程之间是贡献堆和方法资源区的,但是每个线程的程序计数器,虚拟机栈和本地方法栈是不同的.所以在各个线程之间切换的代价是比较小的.
2.java程序天生就是多线程程序.一个 Java 程序的运行是 main 线程和多个其他线程同时运行
从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的堆和方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈 和 本地方法栈。
总结: 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反。
程序计数器主要有下面两个作用:
所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。
为了保证局部变量不被其他线程访问,所以虚拟机栈和本地方法是线程私有的
堆:是最大的一块内存,主要存放的是所有线程的共享资源和新创建的对象
方法区: 主要用于存放已被加载的类信息,常量,静态变量.
再深入到计算机底层来探讨:
1.初始状态,线程被创建出来但是没有被start()
2.运行状态: 线程被调用了start()等待运行的状态
3.阻塞状态: 当线程进入 synchronized
方法/块或者调用 wait
后(被 notify
)重新进入 synchronized
方法/块,但是锁被其它线程占有,这个时候线程就会进入 BLOCKED(阻塞) 状态。
4.等待状态:表示该线程需要等待其他线程作出一些特定动作.(主动的,在同步代码之内)进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态。
5.超时等待状态: 可以在指定的时间后自己返回而不是一直傻傻等待,像第4步,当超时时间结束后,线程将会返回到 RUNNABLE 状态。
6.终止状态; 表示该线程已经运行完毕
在线程执行的过程中,会有自己的状态和运行条件,比如程序计数器,栈等信息,当出现如下的情况的时候,线程会从CPU占用的状态退出
前三种会发生线程切换,所以会保存当前线程的上下文,留待线程下次占用CPU得时候恢复现场.
线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
上面的例子符合产生死锁的四个必要条件:
2.避免死锁
1.破坏请求与保持条件,一次性申请所有资源
2.破坏不不剥夺条件: 占用部分资源的线程进一步申请其他资源时,则释放自己的资源
3.破坏循环等待条件: 依靠按序申请资源来预防.按照某一顺序来申请资源,释放资源的顺序与之相反.
sleep()
方法没有释放锁,而 wait()
方法释放了锁 。wait()
通常被用于线程间交互/通信,sleep()
通常被用于暂停执行。wait()
方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()
或者 notifyAll()
方法。sleep()
方法执行完成后,线程会自动苏醒,或者也可以使用 wait(long timeout)
超时后线程会自动苏醒。sleep()
是 Thread
类的静态本地方法,wait()
则是 Object
类的本地方法。为什么这样设计呢?wait()
是让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁。每个对象(Object
)都拥有对象锁,既然要释放当前线程占有的对象锁并让其进入 WAITING 状态,自然是要操作对应的对象(Object
)而非当前的线程(Thread
)。
类似的问题:为什么 sleep()
方法定义在 Thread
中?
因为 sleep()
是让当前线程暂停执行,不涉及到对象类,也不需要获得对象锁
new 一个 Thread
,线程进入了新建状态。调用 start()
方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start()
会执行线程的相应准备工作,然后自动执行 run()
方法的内容,这是真正的多线程工作。 但是,直接执行 run()
方法,会把 run()
方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
总结: 调用 start()
方法方可启动线程并使线程进入就绪状态,直接执行 run()
方法的话不会以多线程的方式执行。