Java多线程

进程与线程

当一个程序进入内存运行时,变成一个进程(Process)。
进程的特征:

  • 独立性: 进程是系统中独立存在的实体,拥有独立的资源,每一个进程都拥有自己私有的地址空间。在没有进程本身允许下,一个用户进程不可用直接访问其他进程的地址空间。
  • 动态性: 程序是静态的指令集合,进程是正在系统中活动的指令集合,进程具有自己的生命周期和各种不同状态
  • 并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

线程(Thread) 也被称为轻量级进程(Lightweight Process), 使一个进程中同时并发处理多个任务。
线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。
线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不拥有系统资源。与父进程的其他线程共享该进程所拥有的全部资源。
线程是独立运行的,线程的执行是抢占式的,一个线程可以创建和撤销另一个线程,多线程可以并发执行。

线程的创建与启动

Java使用Thread类 代表线程,所有线程对象必须是 Thread类或其子类的实例。

public class Thread implements Runnable {

}

继承Thread类创建线程类

继承Thread类,启动多线程步骤:

  1. 定义Thread类子类, 重写线程执行体:run()方法
  2. 创建该子类的实例
  3. 调用 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接口创建并启动多线程步骤:

  1. 定义 Runnable 接口实现类,重新run()方法
  2. 创建Runnable实现类的实例,此实例作为Thread的target来创建Thread对象。
  3. 调用线程对象的 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> {
}

步骤:

  1. 创建Callable实现类,实现 call()方法, 再创建该类的实例,可以直接使用Lambda创建Callable对象
  2. 使用FutureTask类包装Callable对象
  3. 使用FutureTask对象,作为Thread对象的target,并启动新线程
  4. 使用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捕获异常时, 异常不会向上传播给上一级调用者; 但使用异常处理器处理后, 异常依然会传播给上一级调用者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值