Java 并发之线程篇

Java 并发编程

Java 创建多线程的方式

  • 继承 Thread 类

    // 自定义线程类,继承 Thread 类
    public class MyThread extends Thread{
        // 线程的运行逻辑
        public void run(){
            System.out.println("My Thread run ... ");
        }
    }
    
    // 定义测试类
    public class MyThreadTest{
        public static void main(String[] args){
            // 实例化自定义的线程对象
            MyThread myThread = new MyThread();
            // 调用 start 方法启动线程
            myThread.start();
        }
    }
    
  • 实现 Runnable 接口

    // 自定义线程类,实现 Runnable 接口,覆写 run 方法
    public class MyThread implements Runnable{
        @Override
        public void run(){
            System.out.println("My Thread run ...");
        }
    }
    
    // 定义测试类
    public class MyThreadTest(){
        public static void main(String[] args){
            // 实例化自定义的线程类对象
            MyThread myThread = new MyThread();
            // 创建线程对象,并传入线程实例
            Thread thread = new Thread(myThread)
            // 调用 start 方法启动线程
            thread.start();
        }
    }
    

    当然,也可以用匿名内部类或者 Lambda 表达式来写,更加方便

    new Thread(() -> {
                System.out.println("myThread");
    }).start();
    
    new Thread(new Runnable(){
        @Override
        public void run(){
            System.out.println("myThread");
        }
    }).start();
    
  • 基于线程池

    使用线程池来管理线程,可以最大限度的利用线程资源。

    JVM 会先根据用户的参数创建指定数量的可运行线程任务,并将其放入队列中,
    在线程创建后启动这些任务,如果正在运行的线程数量超过用户设置的最大线程数量,则超出的线程排队等候,
    在有线程执行完成后,线程池调度器会从队列中取出一个等待的任务并执行

    public class MyThreadByThreadPool {
        @Test
        public void myThreadByThreadPool(){
            // 创建线程池,初始化大小
            ExecutorService pool = Executors.newFixedThreadPool(10);
            // 循环提交多个线程任务
            for (int i = 0; i < 10; i ++){
                pool.execute(() -> System.out.println(Thread.currentThread().getName() + "running"));
            }
        }
    }
    
  • 通过 ExecutorServiceCallable<Class> 实现

    适用于有返回值得线程。比如有时候需要并发执行一个任务,开启多个线程,然后收集各个线程执行返回的结果并将最终的结果汇总起来。

    /**
     * 创建线程,实现 Callable 接口
     */
    class MyCallable implements Callable<String> {
    
        private String name;
    
        // 构造函数为线程传参
        public MyCallable(String name){
            this.name = name;
        }
    
        /**
         * 覆写 call 方法,编写线程实现逻辑
         * @return
         * @throws Exception
         */
        @Override
        public String call() throws Exception {
            return name;
        }
    }
    
    public class MyThreadByCallable{
        @Test
        public void myThreadByCallableTest() throws ExecutionException, InterruptedException {
            // 创建一个线程池,指定大小 5
            ExecutorService pool = Executors.newFixedThreadPool(5);
            // 创建多个有返回值的任务
            List<Future> list = new ArrayList<>();
            for (Future item : list){
                Callable callable = new MyCallable(item.toString() );
                // 提交线程,获取 Future 对象并将其保存到 list 中
                Future future = pool.submit(callable);
                System.out.println("Submit a callable thread ...");
                list.add(future);
            }
            // 关闭线程池,等待线程执行结束
            pool.shutdown();
            // 获取所有线程的运行结果
            for (Future future: list){
                String result = future.get().toString();
                System.out.println(result);
            }
        }
    }
    

常用的线程池

  • newCachedThreadPool

    缓存线程池,线程会在任务完成后,继续在缓存区中保留一定时间(默认 60s),创建新线程时如果缓存区中有可重用线程,则重用它们。对于任务量大,但执行时间较短的场景非常适用。

  • newFixedThreadPool

    最普遍的线程池。创建固定线程数量的线程池,并将线程资源存放在队列中循环使用。

  • newScheduledThreadPool

    可定时调度的线程池。可以设置在给定的延迟时间后执行或者定期指定某线程任务。

  • newSingleThreadExecutor

    该线程池永远保证有且只有一个可用线程,如该线程终止,则线程池会立即补充一个新线程替代之。

  • newWorkStealingPool

    JDK 1.8 新增的线程池。创建足够大小的线程池,通过创建多个队列来减少各线程调度产生的竞争,这里的足够大指的是最大程度的利用系统资源,申请足够的线程数。

线程生命周期

调用 new 方法
调用start方法
调用run方法
调用stop方法/异常/执行完毕
阻塞状态
继续运行
新建状态
就绪状态
运行状态
死亡状态
  • 线程的生命周期分为新建(new)、就绪(Runnable)、运行(Running)、死亡(Dead)、阻塞(Blocked) 5 种状态
  • 线程调用 new 方法后,处于新建状态
  • 线程调用 start 方法后,处于就绪状态(此时,并非立即运行,只能说明该线程可运行)
  • 线程调用 run 方法后,处于运行状态(就绪状态的线程竞争到 CPU 的使用权后,会自动执行 run 方法进入运行状态,执行线程中的逻辑代码)
  • 线程运行状态中,被动(I/O阻塞、等待同步锁、等待通知) 或者 主动(调用sleep方法、调用suspend方法) 的放弃 CPU 使用权,会进入阻塞状态,进入阻塞状态后,需要 重新竞争(I/O成功返回结果、获得同步锁、收到通知、sleep时间到、调用resume方法) CPU 资源成功后才可重新进入运行状态。
  • 线程运行状态中,也可以直接让线程 重回就绪状态(调用yield方法)
  • 线程运行状态中,被动(Error、Exception、run/call方法执行完成) 或者 主动(调用stop方法) 的结束运行状态,会进入死亡状态,进入死亡状态后,线程结束,无法再次唤醒。

线程的状态与方法

上面说到线程生命周期的 5 种状态,而线程的状态,除了这 5 个生命周期之外,还有 3 个状态:等待(Waiting)、超时等待(Time Waiting)、就绪(Ready)

线程常用的方法并不多:

  • start
    开启线程,使线程进入就绪状态
  • run
    运行线程,使线程进入运行状态
  • wait
    等待,使线程进入等待状态
  • sleep
    休眠,使线程进入超时等待状态。与 wait 方法不同的是,sleep 方法不会释放当前占有的锁,wait 方法会释放对象的锁;sleep 方法会在休眠指定时间后自动恢复运行状态,wait 方法会持续等待直到其他线程将其唤醒或者中断
    -yield
    释放CPU资源,使线程重回就绪状态,重新与其他就绪状态的线程一起竞争CPU的使用权
  • interrupt
    中断,向线程发送中断信号。只会影响一个中断标识,并不会真正改变线程的状态,最终线程的状态需要等待接收到的中断标识的程序处理。
  • join
    加入,通俗的来将,就像插队。如果线程1调用了 join 线程2 的方法,那么线程1会进入阻塞状态,等待线程2执行完成后,线程1才会转为就绪状态继续等待获取CPU的使用权。通常应用于某一线程的逻辑需要依赖于其他子线程的场景下。
  • notify
    唤醒,使线程从等待状态进入就绪状态
  • setDaemon
    设置为守护线程。守护线程是一种特殊的线程,其依赖于 JVM,如果 JVM 中只剩下守护线程,那么 JVM 就会退出 ,JVM 的垃圾回收期(GC)就是典型的守护线程。

终止线程

  • 自动终止(线程运行完成后自动结束)

  • stop 方法( 极不推荐
    Thread.stop 方法强行终止线程,会抛出 ThreadDeatherror 错误,并释放所有锁,从而导致被锁保护的资源因突然释放而出现不一致或者数据丢失

  • boolean 型变量控制退出
    使用一个 volatile 修饰的 boolean 型变量,控制退出

    public class MyThread extends Thread {
    	public volatile boolean exit = false;
    	while(!exit){
    		// TODO
    		if(退出条件)
    			exit = true;
    	}
    }
    

    代码看上去逻辑非常简单,至于为什么强调使用 volatile 关键字修饰,是因为这里需要利用 volatile 关键字的特性来保证同一时间只有一个线程可以修改 exit 的值。(volatile 的特性之前在)单例设计模式的双重锁校验部分已经做过一次介绍,点此可以直接跳转查看

  • interrupt 方法
    调用 interrupt 方法会使线程的中断标识设置为 true,切记,并不是立即终止线程,仅仅是设置其中断标识,然后使用 isInterrupted 方法循环判断线程中断标志,等到为 true 时会等待执行线程资源释放完成后安全退出线程。

    需要注意的是,如果线程处于阻塞状态,调用 interrupt 会抛出 InterruptedException 异常,需要在 catch 语句中捕获该异常并 break 跳出循环,才可以安全退出线程。

    public class MyThread extends Thread {
    	public void run (){
    		// 正常情况下,通过判断 isInterrupted 的值来决定是否退出
    		while(!isInterrupted())
    		// 如果 isInterrupted 的值为 false,再判断是否有阻塞情况下异常
    			try{
    				Thread.sleep(1000);
    			} catch(InterruptedException e) {
    				break; // 跳出循环
    			}
    	}
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HolaSecurity

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值