进程与线程
当一个程序进入内存运行时,变成一个进程(Process)。
进程的特征:
- 独立性: 进程是系统中独立存在的实体,拥有独立的资源,每一个进程都拥有自己私有的地址空间。在没有进程本身允许下,一个用户进程不可用直接访问其他进程的地址空间。
- 动态性: 程序是静态的指令集合,进程是正在系统中活动的指令集合,进程具有自己的生命周期和各种不同状态
- 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。
线程(Thread) 也被称为轻量级进程(Lightweight Process), 使一个进程中同时并发处理多个任务。
线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。
线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不拥有系统资源。与父进程的其他线程共享该进程所拥有的全部资源。
线程是独立运行的,线程的执行是抢占式的,一个线程可以创建和撤销另一个线程,多线程可以并发执行。
线程的创建与启动
Java使用Thread类 代表线程,所有线程对象必须是 Thread类或其子类的实例。
public class Thread implements Runnable {
}
继承Thread类创建线程类
继承Thread类,启动多线程步骤:
- 定义Thread类子类, 重写线程执行体:run()方法
- 创建该子类的实例
- 调用 start()方法启动多线程
public class FirstThread extends Thread {
private int i;
public void run(){
for (; i < 100; i++){
System.out.println(getName() + " " + i);
}
}
public static void main(String[] args){
for(int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
if(i == 20){
new FirstThread().start();
new FirstThread().start();
}
}
}
}
//Output
...
main 18
main 19
main 20
main 21
Thread-0 0
Thread-0 1
Thread-0 2
main 22
Thread-1 0
Thread-1 1
Thread-1 2
Thread-1 3
...
Thread.currentThread()是 Thread的静态方法, 返回当前正在执行的线程对象。
getName(): 返回线程名字
setName(): 可以设置线程名字
main()方法是Java程序的主线程。
使用Thread类方法创建的线程类, 多个线程无法共享线程类的实例变量,实例变量 i 是不连续的。
使用 Runnable
@FunctionalInterface
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();
}
使用Runnable接口创建并启动多线程步骤:
- 定义 Runnable 接口实现类,重新run()方法
- 创建Runnable实现类的实例,此实例作为Thread的target来创建Thread对象。
- 调用线程对象的 start()方法启动线程
public class FirstThread implements Runnable {
private int i;
public void run(){
for (; i < 100; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args){
for(int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
if(i == 20){
FirstThread firstThread = new FirstThread();
new Thread(firstThread, "新线程1").start();
new Thread(firstThread, "新线程2").start();
}
}
}
}
//Output
...
新线程2 81
新线程2 83
新线程2 84
新线程2 85
main 22
新线程2 86
新线程1 82
新线程1 88
新线程1 89
新线程1 90
...
Runnable是函数式接口,可使用Lambda表达式创建。
两个子线程的 变量 i 是连续的, 采用 Runnable 接口方式创建的多个线程可以共享线程类的实例
Callable 和 Future
Callable接口创建多线程,
提供一个 call()方法作为线程执行体,
call()方法可以有返回值, 可以声明抛出异常。
@FunctionalInterface
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;
}
Callable接口不是 Runnable接口的子接口, 因此Callable对象不能作为Thread的target。
Future 接口来代表Callable接口里call()方法的返回值, 并为Future接口实现了FutureTask实现类,实现了Runnable接口。可以作为Thread的target。
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
*/
void run();
}
public class FutureTask<V> implements RunnableFuture<V> {
}
步骤:
- 创建Callable实现类,实现 call()方法, 再创建该类的实例,可以直接使用Lambda创建Callable对象
- 使用FutureTask类包装Callable对象
- 使用FutureTask对象,作为Thread对象的target,并启动新线程
- 使用FutureTask对象的get()获取返回值
public class FirstThread{
public static void main(String[] args){
FirstThread firstThread = new FirstThread();
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
int i = 0;
for (; i < 100; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
});
for(int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName() + " " + i);
if(i == 20){
new Thread(task, "新线程").start();
}
}
try{
System.out.println("新线程返回时 " + task.get());
}
catch (Exception e){
e.printStackTrace();
}
}
}
//Output
...
新线程 96
新线程 97
新线程 98
新线程 99
新线程返回时 100
三种方式对比
采用 Runnable、Callable接口方式:
线程类实现了接口, 还可以继承其他类。
多个线程共享一个target对象,适合处理同一资源的情况。
继承Thread类方式, 已经继承了Thread类, 不能再继承其他父类。
线程组
Java使用ThreadGroup来表示线程组, 对一批线程进行分类管理。
如果未指定,则属于默认线程组。
子线程和创建它的父线程属于同一个线程组。
Thread类提供下面构造器设置新创建线程属于的线程组:
public Thread(ThreadGroup group, String name)
public Thread(ThreadGroup group, Runnable target)
public Thread(ThreadGroup group, Runnable target, String name)
线程运行中不能改变所属的线程组, 所以Thread类没有setThreadGroup()方法,有 getThreadGroup() 方法返回所属的线程组。
public
class ThreadGroup implements Thread.UncaughtExceptionHandler {
}
ThreadGroup提供下面几个常用方法用于操作线程组里所有的线程:
- activeCount() : 返回线程组中所有活动线程数目
- interrupt():中断所有线程
- isDaemon(): 是否后台线程组
- setDaemon(): 设置后台线程组
- setMaxPriority(): 设置优先级
class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
public MyThread(ThreadGroup group, String name) {
super(group, name);
}
public void run(){
for(int i = 0; i < 20; i++){
System.out.println(getName() + " i : " + i);
}
}
}
public class ThreadGroupTest {
public static void main(String[] args){
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
System.out.println("main thread group : " + threadGroup.getName());
System.out.println("main thread group is Daemon : " + threadGroup.isDaemon());
new MyThread("main thread group thread").start();
ThreadGroup newGroup = new ThreadGroup("new group");
newGroup.setDaemon(true);
System.out.println("new group is Damemon : " + newGroup.isDaemon());
MyThread thread1 = new MyThread(newGroup, "new group thread 1");
thread1.start();
new MyThread(newGroup, "new group thread 2").start();
}
}
//Output
main thread group : main
main thread group is Daemon : false
new group is Damemon : true
main thread group thread i : 0
main thread group thread i : 1
main thread group thread i : 2
main thread group thread i : 3
main thread group thread i : 4
main thread group thread i : 5
new group thread 1 i : 0
main thread group thread i : 6
...
异常处理
线程执行中抛出一个未处理异常,JVM会自动查找 Thread.UncaughtExceptionHandler对象。
@FunctionalInterface
public interface UncaughtExceptionHandler {
/**
* Method invoked when the given thread terminates due to the
* given uncaught exception.
* <p>Any exception thrown by this method will be ignored by the
* Java Virtual Machine.
* @param t the thread
* @param e the exception
*/
void uncaughtException(Thread t, Throwable e);
}
Thread.UncaughtExceptionHandler 是 Thread的静态内部接口。
Thread类提供
thread1.setUncaughtExceptionHandler(); 指定线程设置异常处理器
Thread.setDefaultUncaughtExceptionHandler(); 为该线程类的所有实例设置异常处理器
class MyExHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(t + " 出现 异常: " + e);
}
}
public class ExHandler {
public static void main(String[] args){
Thread.currentThread().setUncaughtExceptionHandler(new MyExHandler());
int a = 5/ 0;
System.out.println("done ");
}
}
//Output
Thread[main,5,main] 出现 异常: java.lang.ArithmeticException: / by zero
异常被处理, 但程序没有正常结束。
异常处理器与catch捕获异常不同:
catch捕获异常时, 异常不会向上传播给上一级调用者; 但使用异常处理器处理后, 异常依然会传播给上一级调用者。