进程和线程的区别
进程和线程的由来
进程是资源分配的最小单位,线程是CPU调度的最小单位
1.所有与进程相关的资源,都被记录在PCB中(Process Control Block 进程管理块)
2.进程是抢占处理机的调度单位,线程属于某个进程,共享其资源
3.线程只由相关堆栈寄存器,程序计数器和TCB组成
总结
1.线程不能看做独立应用,而进程可以看做独立应用
2.进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径
3.线程没有独立的地址空间,多进程程序比多线程程序健壮
4.进程的切换比线程的切换开销大
Java进程和线程的关系
1.java对操作系统提供的功能进行封装,包括进程和线程
2.运行一个程序会产生一个进程,进程包含至少一个线程
3.每个进程对应一个JVM实例,多个线程共享JVM里的堆
4.java采用单线程编程模型,程序会自动创建主线程
5.主线程可以创建子线程,原则上要后于子线程完成执行
Thread的start和run方法的区别
/*
举例start和run的区别
* */
public class ThreadTest {
private static void attack(){
System.out.println("Fight");
System.out.println("Current Thread is : " + Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run() {
attack();
}
};
System.out.println("Current Main Thread is " + Thread.currentThread().getName());
thread.run(); //输出 Current Thread is : main
//thread.start(); //Current Thread is : Thread-0
}
}
调用run的时候,会沿用主线程来执行方法。调用start则会使用非main线程来执行attack方法。
在线查看jdk1.8源码
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/3560e0ebe876
Thread属于java.lang包里
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/3560e0ebe876/src/share/native/java/lang
查看start源码之后,发现调用的是
private native void start0();
通过查看Thread.c源码,发现start0 调用的是
{"start0", "()V", (void *)&JVM_StartThread},
然后再去查看jvm.cpp源码
http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/3ece33697a35/src/share/vm/prims/jvm.cpp
在方法中
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
发现 语句会创建一个新的线程
native_thread = new JavaThread(&thread_entry, sz);
查看 thread_entry方法 中发现 会call虚拟机 并且传入执行方法的名字
最终会创建一个线程 然后去执行run方法里面的内容
static void thread_entry(JavaThread* thread, TRAPS) {
HandleMark hm(THREAD);
Handle obj(THREAD, thread->threadObj());
JavaValue result(T_VOID);
JavaCalls::call_virtual(&result,
obj,
KlassHandle(THREAD, SystemDictionary::Thread_klass()),
vmSymbols::run_method_name(),
vmSymbols::void_method_signature(),
THREAD);
}
JAVA线程创建调用的关系图
总结
1.调用start()方法会创建一个新的子线程并且启动
2.run()方法只是Thread的一个普通方法的调用,还是在主线程中执行
Thread和Runnable是什么关系
Thread是一个类,Runnable是一个接口
查看Thread源码 发现
class Thread implements Runnable
并且 Runnable 接口中只有一行 抽象方法
public abstract void run();
使用Thread 实现多线程
public class MyThread extends Thread {
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
for(int i = 0;i < 5; i ++){
System.out.println("Thread start : " + this.name + " i = " + i);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread thread = new MyThread("Thread1");
thread.start();
}
}
结果
Thread start : Thread1 i = 0
Thread start : Thread1 i = 1
Thread start : Thread1 i = 2
Thread start : Thread1 i = 3
Thread start : Thread1 i = 4
使用Runnable实现多线程
public class MyRunnable implements Runnable {
private String name;
public MyRunnable(String name){
this.name = name;
}
@Override
public void run() {
for(int i = 0;i < 5; i ++){
System.out.println("Thread start : " + this.name + " i = " + i);
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable("Runnable1");
Thread thread = new Thread(runnable);
thread.start();
}
}
结果
Thread start : Runnable1 i = 0
Thread start : Runnable1 i = 1
Thread start : Runnable1 i = 2
Thread start : Runnable1 i = 3
Thread start : Runnable1 i = 4
总结
1.Thread是实现Runnable接口,使得run支持多线程
2.因为单一继承原则,推荐多使用Runnable接口
如何给run()方法传参
实现的方式有主要三种
1.构造函数传参
2.成员变量传参
3.回调函数传参
如何实现处理线程的返回值
1.主线程等待法
优点:实现简单。缺点:需要自己实现循环等待的逻辑,当需要等待的变量变多,整个代码块很显得很臃肿,并且不能做到精准控制
2.使用Thread类的join()阻塞当前线程以等待子线程处理完毕
优点:比主线程等待法更精准的控制,并且实现更简单。缺点:力度不够细
3.通过Callable接口实现:通过FutureTask获取 或者 线程池获取
使用线程池可以提交多个Callable方法的类,让线程池并发的处理结果,这样可以对Callable的执行方式做统一的管理
主线程等待法 使用Thread类的join()阻塞当前线程以等待子线程处理完毕代码实现
public class CycleWait implements Runnable {
private String value;
@Override
public void run() {
try {
Thread.currentThread().sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
value = "我是value,我现在有数据了";
}
public static void main(String[] args) {
CycleWait cycleWait = new CycleWait();
Thread thread = new Thread(cycleWait);
thread.start();
//实现处理线程的返回值 方法一 主线程等待法
while (cycleWait.value == null){ //核心代码 循环等待
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//实现处理线程的返回值 方法二 使用Thread类的join()阻塞当前线程以等待子线程处理完毕
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(cycleWait.value);
}
}
通过Callable接口实现:通过FutureTask获取 或者 线程池获取 代码实现
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
String value = "test";
System.out.println("开始工作");
Thread.currentThread().sleep(5000);
System.out.println("任务完成");
return value;
}
}
//方式1 通过FutureTask获取
public class FutureTasDemo {
public static void main(String[] args) {
FutureTask<String> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
if(!task.isDone()){
System.out.println("任务还没做完,再等待");
}
try {
System.out.println("返回值是:" + task.get());
} catch (Exception e) {
e.printStackTrace();
}
}
}
//方式2 通过线程池获取
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
Future<String> future = newCachedThreadPool.submit(new MyCallable());
if(!future.isDone()){
System.out.println("还没做完,再等待");
}
try {
System.out.println("返回值是" + future.get());
} catch (Exception e) {
e.printStackTrace();
} finally {
newCachedThreadPool.shutdown();
}
}
}
线程的状态
public static enum ThreadState
extends Enum<Thread.State>
A thread state. A thread can be in one of the following states:
NEW
A thread that has not yet started is in this state.
RUNNABLE
A thread executing in the Java virtual machine is in this state.
BLOCKED
A thread that is blocked waiting for a monitor lock is in this state.
WAITING
A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
TIMED WAITING
A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
TERMINATED
A thread that has exited is in this state.
A thread can be in only one state at a given point in time. These states are virtual machine states which do not reflect any operating system thread states
公共静态枚举线程状态
扩展枚举<thread.state>
线程状态。线程可以处于以下状态之一:
新建
尚未启动的线程处于此状态。
可运行的
Java虚拟机中执行的线程处于此状态。
阻塞
等待监视器锁定被阻止的线程处于此状态。
等待
无限期等待另一线程执行特定操作的线程处于此状态。
定时等待
在指定等待时间内等待另一线程执行操作的线程处于此状态。
结束
已退出的线程处于此状态。
一个线程只能在给定的时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态
六个状态
1.新建(NEW):创建后尚未启动的线程状态
2.运行(Runnable):包含Running和Ready
3.无限期等待(Waiting):不会被分配CPU执行时间,需要显示被唤醒
以下方法都会使线程进入Waiting状态
没有设置Timeout参数的Object.wait()方法
没有设置Timeout参数的Thread.join()方法
LockSupport.part()方法
4.限期等待(Timed Waiting):在一定时间后会由系统自动唤醒
以下方法会使线程进入Time Waiting状态
Thread.sleep()方法
设置了Timeout参数的Object.wait()方法
设置了Timeout参数的Thread.join()方法
LockSupport.parkNanos()方法
LockSupport.parkUntill()方法
5.阻塞(Blocked):等待获取排它锁
6.结束(Terminated):已终止线程的状态,线程已经结束执行
thread.join(); thread.start(); 调用join方法之后再调用start方法会抛出 IllegalThreadStateException 异常。已经结束的线程不能在复活了
线程状态以及状态之间的转换
通过实现Runable接口或者继承 Thread类 可以创建一个线程
新建——>可运行
调用线程的start方法,线程就会进入到 可运行(runable) 状态
可运行——>运行
可运行状态的线程 被OS选中 并且获得时间片之后 就会进入到运行(running)状态
运行——>可运行
如果运行(running)状态 调用yield 方法 会让出CPU 回到 可运行(runnable)状态 (取决于操作系统的调度 yield只是起到建议的作用)
如果时间片已经用完 运行状态 也会进入到 可运行状态
运行——>阻塞
如果处于运行状态的线程,如果要等待用户的输入I/O输入,或者调用sleep()或者join()方法 会进入到阻塞的状态。此时会让出CPU。如果当前线程获得锁,上述情况并不会对锁的占用会有任何影响。即不会释放已经获得的锁。
运行,可运行——>阻塞
处于 运行(running)状态 或者 可运行(runnable) 状态的线程,执行 synchronized 方法 或者 synchronized 方法块的时候。发现并未获取到相应的锁,也会进入到阻塞状态。同时会被放入对象的锁池当中。
运行——>等待队列
如果处于运行(running)状态的线程,调用wait方法。就会进入限期或者非限期等待状态。同时还会被放入锁对象的等待队列当中。
等待队列——>锁池
如果处于等待队列中的线程,wait时间到或者被其他线程调用notify 或者 notifyAll 唤醒的话,就会被放入锁池当中。
锁池——>可运行
处于锁池当中的线程获得了锁,就会转为可运行(runnable)当中。
可运行——>运行
等待OS选中后,就会回到运行(runable)状态。
运行——>死亡
最后处于运行(running状态)线程,等待方法执行完毕或者异常退出之后。线程就会结束,进入死亡状态。
sleep和wait的区别
基本差别
1.sleep是Thread类的方法,wait是Object类中定义的方法
2.sleep()方法可以在任何地方使用
3.wait()方法只能在synchronized方法或者synchronized块使用
最主要本质的区别
1.Thread.sleep只会让出CPU,不会导致锁行为的改变
2.Object.wait不仅让出CPU,还会释放已经占有的同步锁资源
用代码来证明本质区别
public class WaitSleepDemo {
/* lock.wait(1000); 获取同步锁只会 会wait 1s 在这段时间
* 第二个线程(sleep)开始执行
* 如果线程1没有释放lock 线程2就会被进入阻塞状态 不能执行线程2 synchronized 里面的逻辑
* 如果wait可以释放锁 那么 线程2 synchronized 将会被执行
* 如果将sleep放在线程1 而 wait放在线程2 那么线程2 synchronized 的代码不会被执行*/
public static void main(String[] args) {
final Object lock = new Object();
//wait逻辑
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程 A 正在等待得到lock");
synchronized (lock){
try {
System.out.println("线程 A 得到lock");
Thread.sleep(20);
System.out.println("线程 A 等待方法");
lock.wait(1000); //如果不传入时间 线程就会进入Waiting(无限期等待)状态
System.out.println("线程 A 已经执行完成");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();
//使wait方法先执行
try{
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
//sleep逻辑
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程 B 正在等待得到lock");
synchronized (lock){
try {
System.out.println("线程 B 得到lock");
System.out.println("线程 B 休眠 10 ms");
Thread.sleep(10);
System.out.println("线程 B 已经执行完成");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();
}
}
结果
线程 A 正在等待得到lock
线程 A 得到lock
线程 B 正在等待得到lock
线程 A 等待方法
线程 B 得到lock
线程 B 休眠 10 ms
线程 B 已经执行完成
线程 A 已经执行完成
notify和notifyAll的区别
当线程进入到 无限等待状态(Waiting) 时 可以使用notyfy和notyfyAll 唤醒线程
了解两个概念
锁池EntryList
假设线程A已经拥有了某个对象(不是类)的锁,而其它线程B、C想要调用这个对象的某个synchronized方法(或者块),由于B、C线程在进入对象的synchronized方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程A所占用,此时B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池
等待池WaitSet
假设线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁。
总结
notifyAll会让所有处于等待池的线程全部j进入锁池去竞争获取锁的机会
没有获取到锁,或者已经呆在锁池中的只能等待其他机会去获取锁,而不能主动回到等待池当中
notify只会随机选取一个处于等待池中的线程进入锁池去竞争取锁的机会
yield函数
JDK介绍yield
A hint to the scheduler that the current thread is willing to yield its current use of a processor. The scheduler is free to ignore this hint.
向调度程序提示当前线程愿意放弃当前对处理器的使用。调度程序可以忽略此提示。
概念
当调用Thread.yield()函数时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示。
yield并不会使线程让出锁
public class YieldDemo {
public static void main(String[] args) {
Runnable yieldTask = new Runnable() {
@Override
public void run() {
for(int i = 1; i <= 10; i ++){
System.out.println(Thread.currentThread().getName() + i);
if(i == 5){
Thread.yield();
}
}
}
};
Thread threadA = new Thread(yieldTask,"A");
Thread threadB = new Thread(yieldTask,"B");
threadA.start();
threadB.start();
}
}
如何中断线程
已经被抛弃的方法
通过调用stop()方法停止线程
这个方法太过于暴力,并且不安全。比如说线程A调用线程B的stop方法,去停止线程B。调用这个方法的时候,线程A并不知道线程B执行的具体情况,这种突然的停止,会导致线程B的清理工作无法完成。还有一种情况,执行stop方法之后线程B,会马上释放锁,会有可能引发数据不同步的问题。
通过调用suspend()和resume()方法
目前使用的方法
调用interrupt(),通知线程应该中断了
1.如果线程处于被阻塞状态,那么线程将立即退出阻塞状态(sleep,wait,join),并抛出y一个InterruptedException异常
2.如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志饿线程将继续正常运行,不受影响。
需要被调用的线程配合中断
1.在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程
2.如果线程处于正常活动的状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响
下面代码展示使用interrupt()停止线程
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Runnable interruptTask = new Runnable() {
@Override
public void run() {
int i = 0;
try{
//在正常运行任务时,经常检查本线程的中断标志,如果被设置了中断标志j就自行停止线程
while (!Thread.currentThread().isInterrupted()){
Thread.sleep(100);//休眠一会
i ++;
System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + " ) loop " + i);
}
}catch (InterruptedException e){
//在调用阻塞方法时正确处理 InterruptedException 异常。(例如catch 异常后就结束线程)
System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + " ) catch InterruptedException" );
}
}
};
Thread thread1 = new Thread(interruptTask,"A");
System.out.println(thread1.getName() + " (" + thread1.getState() + " ) 已经被创建");
thread1.start();
System.out.println(thread1.getName() + " (" + thread1.getState() + " ) 已经开始运行");
//主线程休眠 然后主线程给thread1 发"中断指令"
Thread.sleep(300);
thread1.interrupt();
System.out.println(thread1.getName() + " (" + thread1.getState() + " ) 发出中断指令");
//主线程休眠 然后查看thread1状态
Thread.sleep(300);
System.out.println(thread1.getName() + " (" + thread1.getState() + " ) 已经被中断");
}
}
结果
A (NEW ) 已经被创建
A (RUNNABLE ) 已经开始运行
A (RUNNABLE ) loop 1
A (RUNNABLE ) loop 2
A (TIMED_WAITING ) 发出中断指令
A (RUNNABLE ) catch InterruptedException
A (TERMINATED ) 已经被中断