进程和线程的简介
**进程,**独占内存空间,保存各自运行状态,相互间不干扰可以互相切换,为并发处理任务提供了可能。进程可以视为程序的一个实例,大部分程序可以同时运行多个实例进程,有的只能启动一个进程。进程用来加载指令、管理内存、管理IO的。
**线程,**共享进程的内存资源,相互间切换(隶属于同一个进程之间的线程切换,不需要切换页目录以使用新的地址空间)更快速,支持更细粒度的任务控制,使进程内的子任务得以并发执行。
进程和线程的区别
- 进程是资源分配的最小单位,线程是CPU调度的最小单位。
- 所有与进程相关的资源,都被记录在PCB(进程控制块)中。
- 进程是抢占处理机的调度单位,拥有完整的虚拟内存地址空间,不同的进程拥有不同的内存地址空间;线程属于某个进程,进程内的多个线程共享其资源(内存地址空间)。
- 线程只有堆栈寄存器(存储当前线程内的局部变量)、程序计数器和TCB(线程控制表)组成。
- 线程不能做独立应用,必须依存于某个应用程序,而进程可看做独立应用。
- 进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径,一个线程挂掉之后,所在的进程也会挂掉。
- 线程没有独立的地址空间,多进程的程序比多线程程序健壮
- 进程的切换比线程的切换开销大。
- 进程间的通信较为复杂(同一计算机-IPC、不同计算机-HTTP等),线程间通信简单
进程和线程的关系
-
运行一个程序会产生一个进程,进程包含至少一个线程
-
每个进程对应一个JVM实例,JVM实例唯一对应一个堆,每个线程都有自己私有的栈,多个线程共享JVM里的堆
-
Java采用单线程编程模式,程序会自动创建线程称为主线程
-
主线程可以创建子线程,原则上要后与子线程完成执行
- 主线程是产生子线程的线程
- 主线程是最后完成执行,因为需要执行各种关闭操作
public class CurrentThreadDemo { public static void main(String[] args) { System.out.println("Current Thread name:"+Thread.currentThread().getName()); //查看当前线程名 } }
多线程应用-异步调用
例如,项目中视频文件需要转换格式等操作比较费时,这时new一个新的线程处理视频转换,避免阻塞主线程。
多线程应用-提升计算效率
Thread中的start和run方法的区别
package com.Interview.javabasic.thread;
public class ThreadTest {
private static void attack(){
System.out.println("Fight");
System.out.println("Current Thread name:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
attack();
}
};
System.out.println("Current main Thread name:"+Thread.currentThread().getName());
t.start();
//t.run();
}
}
通过实例,并查看源码(http://hg.openjdk.java.net/jdk8u/),start()方法会调用native原生方法去创建一个子线程并且使用子线程执行run()方法,而run()方法直接在main线程中被执行。
Thread和Runnable的关系?
Thread是类、Runnable是接口,Thread实现了Runnable接口。
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
class Thread implements Runnable
Runnable()接口不具备多线程特性,依赖Thread方法中的start()方法创建子线程再调用run()方法执行相关业务,来实现多线程。Thread类中,定义了一个可以传入Runnable实现类实例的构造器:
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
- Thread是实现了Runnable接口的类,使得run支持多线程。
- 因类的单一继承原则,为了开发的可扩展性,推荐业务多使用Runnable接口,将业务逻辑在run()方法中实现。
如何给run()方法传参?
业务逻辑是在run()方法中实现的,而run()方法无参数和返回值,传参实现的方式有三种:
- 构造函数传参
- 成员变量传参
- 回调函数传参
处理线程的返回值,实现的方式主要有三种:
-
主线程等待法,让主线程循环等待直到子线程返回值
package com.Interview.javabasic.thread; import com.sun.xml.internal.rngom.digested.DValuePattern; public class CycleWait implements Runnable { private String value; @Override public void run() { try { Thread.currentThread().sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } value = "we have data now"; } public static void main(String[] args) throws InterruptedException { CycleWait cw = new CycleWait(); Thread t = new Thread(cw); t.start(); while (cw.value == null){ Thread.currentThread().sleep(100); } System.out.println(cw.value); } }
-
使用Thread类的join()阻塞当前线程(一般是主线程)以等待子线程处理完毕。
package com.Interview.javabasic.thread; import com.sun.xml.internal.rngom.digested.DValuePattern; public class CycleWait implements Runnable { private String value; @Override public void run() { try { Thread.currentThread().sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } value = "we have data now"; } public static void main(String[] args) throws InterruptedException { CycleWait cw = new CycleWait(); Thread t = new Thread(cw); t.start(); t.join(); System.out.println(cw.value); } }
-
通过Callbale接口实现:使用FutureTask 或者 线程池获取。
Callable接口定义了一个抽象方法call();
public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }
FutureTask类,具有一个传入Callable实现类实例的构造器;
public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable }
FutureTask类,isDone()方法判断传入的实现类实例的call()方法是否执行完成
public boolean isDone() { return state != NEW; }
FutureTask类,get()方法主要是阻塞当前调用此方法的线程,直到call()方法执行完毕,取到返回值。get()还有一个可传参的方法,传入超时时间。
public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L); return report(s); }
Thread类具有一个传入Runnable实现类实例的构造器;
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
FutureTask实现继承RunnableFuture接口
public class FutureTask<V> implements RunnableFuture<V>
RunnableFuture接口继承Runnable、Future接口
public interface RunnableFuture<V> extends Runnable, Future<V> { /** * Sets this Future to the result of its computation * unless it has been cancelled. */ void run(); }
即只要实现了Runnable接口,通过传入Runnable实现类实例的构造器,Thread都能进行相应的业务处理。
package com.Interview.javabasic.thread; import java.util.concurrent.Callable; public class MyCallable implements Callable<String> { @Override public String call() throws Exception { String value = "test"; System.out.println("Ready to work"); Thread.currentThread().sleep(1000); System.out.println("task done"); return value; } }
package com.Interview.javabasic.thread; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class FutureTaskDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<String> ft = new FutureTask<String>(new MyCallable()); new Thread(ft).start(); if (!ft.isDone()){ System.out.println("please wait"); } System.out.println(ft.get()); } }
-
使用线程池进行数据的获取,可以多个提交实现Callable的类让线程池并发的执行,方便统一管理。
package com.Interview.javabasic.thread; import java.util.concurrent.*; 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("please wait"); } try { System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); }finally { newCachedThreadPool.shutdown(); } } }
线程的状态
-
新建(New):创建后尚未启动的线程的状态,即还未调用start()方法
-
运行(Runnable):包含Running(正在执行,位于可运行线程中)和Ready(等待执行,位于线程池中)
-
无限期等待(Waiting):不会被分配CPU执行时间,需要显示被唤醒
- 没有设置Timeout参数的Object.wait()
- 没有设置Timeout参数的Thread.join()
- LockSupport.park()方法
-
限期等待(Timed Waiting):在一定时间后会由系统自动唤醒
- Thread.sleep()方法
- 设置Timeout参数的Object.wait()
- 设置Timeout参数的Thread.join()
- LockSupport.parkNanos()方法
- LockSupport.parkUntil()方法
-
阻塞(Blocked):等待获取排他锁
-
结束(Terminated):已终止进程的状态,线程已经结束执行,再次唤醒start()会报错。
sleep和wait的区别
-
sleep是Thread类的方法,wait是Object类中定义的方法。
sleep是Thread类中的定义方法,是通过调用native原生方法来实现的。
public static native void sleep(long millis) throws InterruptedException;
wait是Object类中定义的方法,不传参数时,调用的是native原生方法中的wait().
public final void wait() throws InterruptedException { wait(0); }
public final native void wait(long timeout) throws InterruptedException;
-
sleep()方法可以在任何地方使用
-
wait()方法只能在synchronized方法或synchronized块中使用
-
最主要的本质区别:
- Thread.sleep只会让出CPU,不会导致锁行为的改变。
- Object.wait不仅让出CPU,还会释放已经占有的同步资源锁
package com.Interview.javabasic.thread; import sun.tools.tree.SynchronizedStatement; public class WaitSleepDemo { public static void main(String[] args) { final Object lock = new Object(); new Thread(new Runnable() { @Override public void run() { System.out.println("Thread A is waiting to get lock"); synchronized(lock){ //尝试获取锁 try { System.out.println("Thread A get lock"); Thread.sleep(20); System.out.println("Thread Ado wait method"); lock.wait(1000); System.out.println("Thread A isDone"); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(new Runnable() { @Override public void run() { System.out.println("Thread B is waiting to get lock"); synchronized(lock){ //尝试获取锁 try { System.out.println("Thread B get lock"); Thread.sleep(10); System.out.println("Thread Bdo wait method"); lock.wait(1000); System.out.println("Thread B isDone"); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); } }
以上为两个线程A、B,在run()方法中,获取同步锁执行同步代码块synchronized中的业务逻辑场景。主要区别是:一、使用lock.wait()方法使线程等待(让出CPU、释放同资源锁);二、使用Thread.sleep()方法线程等待(让出CPU、不会释放同资源锁)。
notify和notifyAll的区别
上述场景不变,Thread A中使用lock.wait()方法不传参数使线程无限期等待,Thread B中使用Thread.sleep(10)方法使线程等待10ms。此时当Thread B执行完成时,Thread A还是在阻塞等待,需要用lock.notify()或lock.notifyaAll()方法进行唤醒。
两个概念
- 锁池EntryList
- 等待池WaitSet
对于JVM中运行的对象而言都有两个池:锁池和等待池,这两个池又与Object基类的wait、notify、notifyAll和synchronized有关。
锁池
假设线程A已经拥有了某个对象的锁,而其他线程B、C想要调用这个对象的某个synchronized方法(块),由于B、C线程在进入对象的synchronized方法(块)之前必须先获得该对象锁的拥有权,而该对象的锁目前正被线程A所占用,此时B、C线程就会被阻塞,进入对象的锁池等待锁的释放。
等待池
假设线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁,同时线程A进入该对象的等待池中,进入等待池中的线程不会去竞争该对象的锁。
notify和notifyAll的区别
- notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会。
- notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会。
实例:模拟线程竞争对象锁。
总共创建4个线程分别是,竞争获取对象锁的线程WT1、WT2、WT3,随机唤醒或全部唤醒竞争线程的唤醒线程NT4。通过被volatile修饰的属性,进行锁的竞争。
package com.Interview.javabasic.thread;
public class NotificationDemo {
private volatile boolean go = false; //被volatile修饰的属性,一个线程修改后,其他线程都能知道
public static void main(String[] args) throws InterruptedException {
final NotificationDemo test = new NotificationDemo();
Runnable waitTask = new Runnable() { //使线程进入等待状态
@Override
public void run() {
try {
test.shouldGo();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" finished Execution");
}
};
Runnable notifyTask = new Runnable() { //执行线程唤醒任务
@Override
public void run() {
test.go();
System.out.println(Thread.currentThread().getName()+" finished Execution");
}
};
Thread t1 = new Thread(waitTask,"WT1");
Thread t2 = new Thread(waitTask,"WT2");
Thread t3 = new Thread(waitTask,"WT3");
Thread t4 = new Thread(notifyTask,"NT4");
t1.start();
t2.start();
t3.start();
Thread.sleep(200);
t4.start();
}
private synchronized void shouldGo() throws InterruptedException {
while (go != true){
System.out.println(Thread.currentThread().getName()+" is going to wait on this object");
wait(); //即this.wait(),当前正在执行test对象的线程
System.out.println(Thread.currentThread().getName()+" is woken up");
}
go = false;
}
private synchronized void go(){
while (go == false){
System.out.println(Thread.currentThread().getName()+" is going to notify all or one thread waiting on this object");
go = true;
notifyAll();
//notify();
}
}
}
yield函数
当调用Thread.yield()函数时,会给线程调度器一个当前线程让出CPU使用权(但是不会释放锁)的暗示,但是线程调度器可能会忽略这个暗示。
如何中断线程
目前主要使用的方法:
-
1.调用interrupt()通知线程应该中断,并且需要被标志的线程配合中断
- 如果线程处于被阻塞(sleep、wait、join)状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常,正确处理异常进行线程的中断。
- 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。
- 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
package com.Interview.javabasic.thread; public class InterruptDemo { public static void main(String[] args) throws InterruptedException { Runnable interruptTask = new Runnable() { @Override public void run() { int i = 0; try{ while (!Thread.currentThread().isInterrupted()){ //使用while循环判断是否线程被设置了中断标志 Thread.sleep(100); i++; System.out.println(Thread.currentThread().getName()+Thread.currentThread().getState() + i); } }catch (InterruptedException e){ //在被设置了中断标志,线程在循环打印时使用了sleep()方法进入阻塞状态, // 如果线程处于被阻塞(sleep、wait、join)状态,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常, // 正确处理InterruptedException,结束进程 System.out.println(Thread.currentThread().getName()+Thread.currentThread().getState() + "catch Exception"); } } }; Thread t1 = new Thread(interruptTask,"t1"); System.out.println(t1.getName()+ t1.getState()); t1.start(); System.out.println(t1.getName()+t1.getState()); Thread.sleep(300); t1.interrupt(); System.out.println(t1.getName()+t1.getState()); Thread.sleep(300); System.out.println(t1.getName()+t1.getState()); } }
线程总结
线程状态的改变过程如上图所示:
- 1.创建线程t,有两种方式:new Thread()方式、Thread类具有一个传入Runnable实现类实例的构造器,new Thread(myRunable)方式。
- 线程创建完成后,有两种方式进行运行:t.run()方法直接在main线程中被执行,会阻塞主线程(不推荐)、t.start()方法会调用native原生方法去创建一个子线程并且使用子线程执行run()方法。
- 线程进入(Runnable)状态中的Ready(等待执行,位于线程池中)状态,被OS选中后,进入Running(正在执行,位于可运行线程中)。
- 线程进入运行态(Running)后,有如下选择:
- 1.CPU时间片用完,变为可运行(Ready)状态
- 2.Thread.Yield()方法,暗示线程可以让出CPU,变为运行(Ready)状态
- 3.等待获取synchronized块或方法的锁时,进入锁池中进行等待。
- 竞争到锁,进入可运行(Ready)状态。
- 4.Thread.sleep()方法,进入阻塞状态
- 等待sleep()时间结束,进入可运行(Ready)状态。
- 5.Object.wait()方法(只能运行在synchronized方法或块中),进入线程等待队列中。
- 线程等待队列的阻塞线程有两种方式唤醒:notify()随机选择一个线程进行唤醒,进入锁池中竞争锁、notifyAll()唤醒全部线程,进入锁池。
- 6.run()、main()方法运行结束或出现异常,进入死亡状态。