java多线程学习笔记

java多线程学习笔记,第1张

java多线程学习笔记

文章目录
    • 一、实现方法简介
    • 二、继承Thread类
    • 三、实现Runnable接口
    • 四、两种方法的比较
    • 五、start()和run()方法的区别
    • 六、通过Callable和Future创建线程
    • 七、使用 CompletableFuture 实现非阻塞异步编程
    • CompletableFuture简介

一、实现方法简介

java创建线程一共有三种实现方法

常用第二种接口实现,因为实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度。

二、继承Thread类

Thread类是所有线程类的父类,实现了对线程的抽取和封装
实现步骤:

  1. 定义一个类,继承Thread类,并重写该类的run方法,run方法体为完成的任务。
  2. 创建Thread类的对象,即创建子线程
  3. 用线程对象的start方法启动该线程

创建一个售票系统Demo

package com.example.threaddemo;

//定义一个类,继承Thread类
public class SellTickets extends Thread{
    private int count = 100;

    //重写该类的run方法,run方法体为完成的任务。
    @Override
    public void run() {
        while (count > 0){
            count--;
            System.out.println(Thread.currentThread().getName() + "剩余" + count + "张票");
        }
    }
}

编写测试类

package com.example.threaddemo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ThreadDemoApplicationTests {

    public static void main(String[] args) {
        //创建Thread类的对象,即创建子线程
        SellTickets s1 = new SellTickets();
        SellTickets s2 = new SellTickets();
        SellTickets s3 = new SellTickets();

        //用线程对象的start方法启动该线程
        s1.start();
        s2.start();
        s3.start();
    }

}

结果


说明三个线程各自执行,并没有先后顺序

三、实现Runnable接口

使用Runnanle接口并启动多线程步骤:

  1. 编写实现类实现Runnable接口,并重写该类的run方法,run方法体为完成的任务。
  2. 创建实现了Runnable接口的类的对象
  3. 使用实现类对象创建子线程
  4. 调用线程对象的start方法启动线程

售票类实现Runnable接口

package com.example.threaddemo;

//定义一个类,实现Runnable接口
public class SellTickets implements Runnable{
    private int count = 100;

    //重写该类的run方法,run方法体为完成的任务。
    @Override
    public void run() {
        while (count > 0){
            count--;
            System.out.println(Thread.currentThread().getName() + "剩余" + count + "张票");
        }
    }
}

编写测试类

package com.example.threaddemo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ThreadDemoApplicationTests {

    public static void main(String[] args) {
        //创建实现了Runnable接口的类的对象
        SellTickets s = new SellTickets();
        //使用对象创建子线程
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);

        //用线程对象的start方法启动该线程
        t1.start();
        t2.start();
        t3.start();
    }

}

结果

四、两种方法的比较
  • 继承Thread:

优点: 编码简单,要访问当前线程除了使用Thread.currentThread(),还可以使用super关键字
缺点: 因为java是单继承多实现,继承了Thread就不能继承其他类

  • 实现Runnable

优点: 多实现,可以继承其他类。多个线程可以共享同一个对象,适合处理同一资源。
缺点: 稍现复杂,只能使用Thread.currentThread()访问当前线程

五、start()和run()方法的区别
  • start()方法会新建一个线程,并且让这个线程执行run()方法。
  • 调用run()也能正常执行。但是,却不能新建一个线程,而是在当前线程调用run()方法,只是作为一个普通的方法调用
六、通过Callable和Future创建线程

Java提供了Callable接口,该接口是Runnable接口的增强版,Callable接口提供了一个call()方法,可以看作是线程的执行体,但call()方法比run()方法更强大。

  • call()方法可以有返回值。
  • call()方法可以声明抛出异常。

步骤如下:

  1. 创建Callable接口的实现类,并实现call()方法,该call()方法将作为该线程的执行体,且该call()方法有返回值,再创建Callable的实例。从Java 8开始,可以直接使用Lamda表达式创建Callable对象。
  2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程。
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

测试类代码

package com.example.threaddemo;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

@SpringBootTest
class ThirdThread {

    public static void main(String[] args)
    {
        // 创建Callable对象
        ThirdThread rt = new ThirdThread();
        // 先使用Lambda表达式创建Callable对象
        // 使用FutureTask来包装Callable对象
        FutureTask task = new  FutureTask((Callable)() -> {
            int i = 0;
            for ( ; i < 100 ; i++ )
            {
                System.out.println(Thread.currentThread().getName()
                        + " 的循环变量i的值:" + i);
            }
            // call()方法可以有返回值
            return i;
        });
        for (int i = 0 ; i < 100 ; i++)
        {
            System.out.println(Thread.currentThread().getName()
                    + " 的循环变量i的值:" + i);
            if (i == 20)
            {
                // 实质还是以Callable对象来创建、并启动线程
                new Thread(task , "有返回值的线程").start();
            }
        }
        try
        {
            // 获取线程返回值
            System.out.println("子线程的返回值:" +  task.get());
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }
    
}

结果

说明
程序先使用使用Lamda表达式创建一个Callable对象,然后将该实例包装成一个FutureTask对象。主线程中当循环变量i等于20时,程序启动以FutrueTask对象为target的线程。程序最后调用FutrueTask对象的get()方法来返回call()方法的返回值——该方法将导致主线程被阻塞,直到call()方法结束并返回为止,所以“子线程的返回值:100”永远是最后输出。

七、使用 CompletableFuture 实现非阻塞异步编程 CompletableFuture简介

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/4017846.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-10-22
下一篇 2022-10-22

发表评论

登录后才能评论

评论列表(0条)

保存