java 多线程学习

本贴最后更新于 2648 天前,其中的信息可能已经天翻地覆

一、如何实现多线程

  1. 实现 Runnable 接口

     public static void main(String[] args) {
    	MyThread myThread = new MyThread();// 一个实现了Runnable接口的类
    	Thread t = new Thread(myThread);// 声明一个线程
    	t.start();// 启动线程
    }
    
    public class MyThread implements Runnable {
        @Override
        public void run() {// 启动线程后执行的方法
            System.out.print("run the \"run()\" method!");
        }
    }
    
  2. 继承 Thread 类

    public static void main(String[] args) {
    	MyThread myThread = new MyThread();// 一个继承了Thread类的类
    	myThread.start();// 启动线程
    }
    
    public class extends Thread {
        @Override
        public void run() {// 启动线程后执行的方法
            System.out.print("run the \"run()\" method!");
        }
    }
    
  3. 使用 ExecutorService、Future 和 Callable 创建有返回值的线程

    public static void main(String[] args) {
    	// 创建一个ExecutorService
    	ExecutorService executorService = 
    			Executors.newCachedThreadPool();
    	// new一个MyThread线程,并交给executorService执行,
    	// 通过Future接收返回结果
    	Future future = 
    			executorService.submit(new MyThread());
    	try {
    		// 从future中获取返回值
    		Object result = future.get();
    		System.out.println(result.toString());
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    }
    
    // 一个有返回值的线程
    public class MyThread implements Callable {
    	@Override
    	public String call() {
    		System.out.print("run the \"call()\" method!");
    		return "test";
    	}
    }
    

二、实质

  • Runnable、Callable 接口和多线程的实现没有关系
    接口的作用是约束行为,Runnable 接口的作用是指定一个协议,规定所有继承这些接口的类都要有一个无参无返回值的方法
  • 多线程的实现是由类来完成的
    java 中所有的多线程最终都通过 Thread 类来实现,线程的资源分配、线程启动这些工作都是在 start()方法中完成的,严格来讲,在 start0()方法中完成的。start0()是一个 native 方法,并不是用 java 语言实现的。
  1. 可以通过两种方式来定义多线程中要完成的工作
    • 实现 Runnable 接口
      Runnable 接口中的 run()方法,没有返回值
    • 实现 Callable 接口
      Callable 接口中的 call()方法,有返回值
  2. 需要对线程进行管理(提交、启动、终止)
    1. Thread 类
      • Thread 类有一个构造函数可以传递一个 Runnable 类型的参数 target,可以通过 target 来提交想要执行的任务(实现 run()方法,然后传给 Thread 实例)
      • Thread 类本身实现了 Runnable 接口,也可以直接通过实现 Thread 类来提交想要执行的任务(重写 run()方法)
      • Thread 类的 start()方法负责启动执行线程,当 start()方法执行时
        • 若已经重写了 run()方法来执行任务,则会执行该方法
        • 若传入了 target 参数,则会调用 target 中的 run()方法
      • Thread 类中有 stop 方法负责停止线程,但是已经弃用
      • 可以通过 interrupt()方法中断线程
    2. ExecutorService 接口
      • 可以通过 executor()方法或 submit()方法提交任务
      • submit()方法提交的任务会返回一个 Future 对象,可以通过这个对象来获取返回结果
      • shutdown()和 shutdownNow()方法可以用来停止线程池

三、详解

  1. 线程的生命周期
    • new thread(新建):创建一个线程实例,比如通过 new 操作创建一个 Thread 类的实例,此时线程未被启动
    • runnable(可运行):一个线程创建好之后,需要通知 cpu 这个线程可以开始执行了,比如 thread 类的 start()方法执行后,此时线程在就绪队列中等待 cpu 分配资源
    • running(运行中):线程获得 cpu 资源后开始运行,比如运行 run()方法中的逻辑,此时除非线程自动放弃 cpu 资源或者有优先级更高的线程进入,否则将执行到线程结束
    • dead(死亡):线程正常执行结束,或者被 kill 调,此时线程将不会再次被执行
    • block(阻塞):线程主动让出 cpu 使用权、其它更高优先级的线程进入、该线程的时间片用完,但此时该线程还没有执行完成,都会使线程进入 block 状态,进入 block 状态的线程还可以回到就绪队列中等待再次执行。
  2. Thread 类中的方法
    • start:启动一个线程,这个方法会是线程进入 Runnable 状态,等待执行
    • isAlive:判断线程是否处于活动状态(Runnable 或 running)
    • sleep:强制让线程放弃当前时间片进入休眠状态一定时间,此时线程会进入 block 状态,直到休眠的时间结束,再进入 Runnable 状态。sleep 是静态方法,只能控制所在线程。sleep(0)会直接触发下一次 cpu 竞争,如果没有优先级更高的线程,则会继续工作
    • wait(override Object):放弃对象锁,进入等待池,只有针对此对象调用 notify()方法之后,才会再次进入 Runnable 状态
    • join:阻塞等待线程结束,可以接收参数 millis 和 nanos 指定等待的最大时间
    • interrupt:中断线程,这个方法并不能中断正在运行的线程,运行该方法后,只有当线程被 join(),sleep()和 wait()方法所阻塞时,才会被 interrupted 方法所中断,并抛出一个 InterruptedException 异常
    • static yield:主动放弃 cpu 使用权,回到 Runnable 状态

四、Tips

  1. block 状态
    1. 等待阻塞:运行线程执行了 wait 方法,该线程会释放占用的所有资源包括对象锁,进入等待队列中。进入等在队列的线程是不能自动唤醒的,必须依靠其它线程调用 notify()、notifyAll()来进行唤醒(该状态下线程会释放对象锁)
    2. 同步阻塞:运行的线程在获取对象同步锁时,同步锁已被其它线程占用,则该线程会进入锁队列等待获取同步锁,直到获取到同步锁之后回再次进入 Runnable 状态(该状态下线程还没有获得对象锁)
    3. 其它阻塞:运行的线程调用了 sleep()或 join()方法,或者发出 IO 请求,该线程会进入阻塞状态,知道 sleep 超时、join 所等待的线程结束或是 IO 操作完成,则会再次进入 Runnable 状态(该状态下线程只会放弃 cpu 而不会释放对象锁)
  2. sleep(0)
    sleep(0)会重新触发一次 cpu 竞争,当 Runable 队列中有大于或等于当前线程优先级的线程时,当前线程会进入 Runnable 队列将 cpu 的使用权让出,否则会继续运行
  3. sleep()和 wait()
    • sleep 方法会让出 cpu,但不会释放对象锁,等到 sleep 超时之后会自动进入 Runnable 队列
    • wait 方法会让出 cpu,并释放对象锁,需要其它线程调用 notify()、notifyAll()才能重新进入 Runnable 队列
  4. interrupt()
    interrupt 方法的作用更倾向于告诉线程,你可以结束了,而不是直接地中断线程,知道线程进入阻塞状态时,才能中断线程。对于陷入死循环、IO 等待等难以进入阻塞状态的线程来说,interrupt 方法是不能有效中断的。
  5. sleep()和 yield()
    这两个方法都会让出 cpu 使用权,sleep 会进入 block 状态,而 yield 会直接进入 Runnable 状态
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3187 引用 • 8213 回帖
  • 线程
    122 引用 • 111 回帖 • 3 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...