浅谈之Java多线程

这篇具有很好参考价值的文章主要介绍了浅谈之Java多线程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Java多线程是Java语言中一个非常重要的特性,它允许程序同时执行多个任务。通过多线程,程序可以同时处理多项任务,从而缩短程序的执行时间。另外,多线程也有助于利用多核处理器,更好地发挥计算机硬件的性能。

那我们在Java中如何去实现多线程呢?

今天我们就从最简单的的ThreadRunnable讲到现在比较常用的CompletableFuture

Thread

Java中通过创建Thread类的实例来创建线程。创建线程的最简单方式是扩展Thread类并覆盖run()方法。在run()方法中编写要执行的代码,然后在程序中调用start()方法启动该线程。例如:

public class MyThread extends Thread {
    public void run() {
        // 线程要执行的代码
        for (int i = 0; i < 10; i++) {
            System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + i + "}");
        }
    }
}

public static void main(String[] args) {
	System.out.println("start");
	MyThread myThread1 = new MyThread();
	MyThread myThread2 = new MyThread();
	myThread1.start();
	myThread2.start();
	System.out.println("end");
}

当然你要是嫌麻烦的话你可以直接使用匿名内部类

		// 效果是一样的
        System.out.println("start");
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "{" + i + "}");
            }
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "{" + i + "}");
            }
        }).start();
        System.out.println("end");
       

这边注意千万不用使用run()方法,这会使线程变成同步,同时如果你想让多线程的内容执行完之后再去执行之后的代码,那你可以使用join()这个方法,它可以使该线程执行完之后再去执行下面的操作。
当然你也可以使用sleep() 让主线程睡眠一段时间,当然这个睡眠时间是主观的时间,是我们自己定的,这个方法不推荐

Runnable

此外,我们还可以使用Runnable接口来创建线程。Runnable接口只有一个run()方法,在其中编写要执行的代码,然后将Runnable对象作为参数传递给Thread类的构造函数,并调用start()方法启动线程。例如:

public class MyThread implements Runnable {
    @Override
    public void run() {
        // 线程要执行的代码
        for (int i = 0; i < 10; i++) {
            System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + i + "}");
        }
    }
}

其实和Thread差不多,只是一个实现接口,一个继承类,直接使用的话这两个没有太大的区别
不过有的地方会说Runnable便于实现资源共享,而Thread不能,但是经过的我的测试认为Thread也可以实现资源共享

测试代码

public class MyThread extends Thread {

    private int ticket = 5;

    @Override
    public void run() {
    	// 双检索机制——保证线程的安全
        if (ticket > 0) {
            synchronized (this) {
                if (ticket > 0) {
                    while (true) {
                        System.out.println("Thread:" + Thread.currentThread().getName() + "--Thread ticket = " + ticket--);
                        if (ticket < 0) {
                            break;
                        }
                    }
                }
            }
        }
    }

}

public class Test {
    public static void main(String[] args) {

        MyThread myThread1 = new MyThread();
        new Thread(myThread1).start();
        new Thread(myThread1).start();
        new Thread(myThread1).start();
        new Thread(myThread1).start();
        new Thread(myThread1).start();
        new Thread(myThread1).start();
    }
}

执行结果如下:
Thread:Thread-1--Thread ticket = 5
Thread:Thread-1--Thread ticket = 4
Thread:Thread-1--Thread ticket = 3
Thread:Thread-1--Thread ticket = 2
Thread:Thread-1--Thread ticket = 1
Thread:Thread-1--Thread ticket = 0

我们看Thread的源代码发现:其实Thread也就是实现了Runnable接口,提供了更多的方法而已。所以说ThreadRunnable并没有什么区别。如果硬要说有什么区别的话,那就是类与接口的区别,继承与实现的区别

Callable(搭配Future)

对于子线程,我们有时候可能会有两种需求:

  1. 获取子线程运行结果
  2. 获取子线程运行状态(成功、失败、异常)

ThreadRunnable都不满足这两个要求,Runnable可以获取状态但不能获取结果,于是出现了CallableCallable配合Future使用可以获得子线程执行结果。
Future是Java多线程中的一种异步计算方式,可以用来获取在执行任务期间进行异步计算的结果)

public class MyThread implements Callable<Integer> {

    private Integer number;

    public Integer getNumber(){return number;}

    public MyThread (Integer number) {
        this.number = number;
    }

    @Override
    public Integer call() throws Exception {
        int result = 0;
        for (int i = 0; i < number; i++) {
            result ++;
            System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + result + "}");
        }
        return result;
    }
}

    public static void main(String[] args) throws Exception {
        System.out.println("start");
        MyThread myThread1= new MyThread (10);
        MyThread myThread2= new MyThread (15);
        //使用Executors工厂类创建一个简单的线程池(线程数:2)
        ExecutorService executor = Executors.newFixedThreadPool(2);
		// 将任务提交给线程池	
        Future<Integer> submit1 = executor.submit(myThread1);
        Future<Integer> submit2 = executor.submit(myThread2);
        // 获取任务的执行结果
        System.err.println(submit1.get());
        System.err.println(submit2.get());
        executor.shutdown();
        System.out.println("end");
    }

在这个例子中,我们实现了MyThread 类,它实现了Callable接口,并重写了call()方法。在call()方法中,我们模拟了一个长时间运行的任务,并返回一个计算结果作为执行结果。在main方法中,我们首先创建了一个固定线程数的线程池ExecutorService,然后将MyThread的实例传入submit方法来提交任务,并得到一个Future类型的对象。通过调用该对象的get()方法,我们可以获取任务的执行结果。最后,我们关闭了线程池。

需要注意的是,由于调用Futureget()方法是阻塞的,所以我们可能需要对其进行try-catch处理。此外,在完成任务后,我们必须关闭线程池以释放资源。

这是一个简单的使用Callable的示例,通过组合多个Future对象可以实现更复杂的并发计算。

Future

当然 Future也可以进行单独使用。
在使用Future时,可以调用get()方法来阻塞等待任务执行完毕并获取计算结果;也可以调用isDone()方法来判断任务是否执行完毕;如果任务执行过程中发生异常,可以通过调用get()方法获取异常信息

public static void main(String[] args) throws Exception {
        System.out.println("start");
        
        ExecutorService executor = Executors.newFixedThreadPool(2);
        Future future1 = executor.submit(() -> {
            int result = 0;
        for (int i = 0; i < number; i++) {
            result ++;
            System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + result + "}");
        }
        return result;
        });
		Future future2 = executor.submit(() -> {
            int result = 0;
        for (int i = 0; i < number; i++) {
            result ++;
            System.out.println("id:" + Thread.currentThread().getId() + ",name:" + Thread.currentThread().getName() + "====={" + result + "}");
        }
        return result;
        });
        String result1 = (String) future1.get();
        String result2 = (String) future2.get();
        System.out.println(result1);
		System.out.println(result2);
        executor.shutdown();
        System.out.println("end");
    }

其实和上一个方法是一样的,只不过是这边把call()方法中的业务逻辑冗余到了主代码中,耦合性更高了,如果只是一些简单的代码可以使用,复杂的话还是建议搭配callable 一起使用比较好

CompletableFuture

CompletableFuture是Java 8引入的一个非常有用的功能,它可以让我们更轻松地处理异步操作,特别是当这些操作涉及到多个阶段时。
CompletableFuture 继承自 Future 接口,可以在计算完成后获取计算结果,因此它也具备了 Future 的特性。除此之外,它还提供了一些其他的特性:

  1. 异步执行和串行执行功能
  2. 任务执行完毕后可以触发观察者机制
  3. 可以将两个任务组合到一起执行等,处理更加复杂的异步场景。

现在开发来说使用较为复杂的逻辑普遍会使用CompletableFuture

创建CompletableFuture

我们可以使用runAsync()方法直接去异步执行

CompletableFuture.runAsync(() -> {
    // 在这里执行一些耗时的操作
   
});

异步处理CompletableFuture的结果

我们可以使用CompletableFuture的静态方法supplyAsync()创建一个异步执行的CompletableFuture,并提供一个lambda表达式作为其计算发生器:

CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
    // 在这里执行一些耗时的操作
    return "some result";
});

completableFuture.thenAccept(result -> {
    System.out.println("Got result: " + result);
});
// 这里可以继续执行其他任务,completableFuture将在后台继续执行并在完成后触发回调函数。

操作两个或多个CompletableFuture

如果我们需要等待多个CompletableFuture都完成后执行某些任务,那么我们可以使用CompletableFuture的静态方法allOf()anyOf()

CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
    // 在这里执行一些耗时的操作
    return "Result of future 1";
});

CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
    // 在这里执行一些耗时的操作
    return "Result of future 2";
});

CompletableFuture<Void> allFutures = CompletableFuture.allOf(future1, future2);

allFutures.thenRun(() -> {
	//这边join和get方法都可以 只是抛出异常的区别
    String result1 = future1.join();
    String result2 = future2.join();
    System.out.println("Got both results: " + result1 + " and " + result2);
});

CompletableFuture 默认使用的是ForkJoinPool线程池,如果你想自己定义线程池的话可以使用Executors直接创建一个,但是Executors 是一个工厂类,提供了一些静态方法用于创建线程池,而不是一个线程池本身,因此无法设置线程的详细参数例如:核心线程数,最大线程数,拒绝策略等。如果需要设置这些参数,应该使用 ThreadPoolExecutor 类来手动创建线程池

    /**
     * public ThreadPoolExecutor(int corePoolSize,
     *                               int maximumPoolSize,
     *                               long keepAliveTime,
     *                               TimeUnit unit,
     *                               BlockingQueue<Runnable> workQueue,
     *                               ThreadFactory threadFactory,
     *                               RejectedExecutionHandler handler) {}
     * 1. corePoolSize:核心线程数;当提交的任务数大于 corePoolSize 时,线程池会自动扩容。
     *
     * 2. maximumPoolSize:线程池最大线程数;当活动线程数达到该值,并且 workQueue 队列已满,则执行拒绝策略。
     *
     * 3. keepAliveTime:线程空闲超时时间,超过该时间则进行回收。
     *
     * 4. unit:keepAliveTime 的时间单位。
     *
     * 5. workQueue:任务阻塞队列,用于存储提交但尚未执行的任务。
     *
     * 6. threadFactory:线程工厂,用于创建线程。
     *
     * 7. handler:拒绝策略,当线程数量已经达到 maximumPoolSize 并且队列已满时,采取的策略。
     */
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5,
                10,
                10,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue(),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.CallerRunsPolicy());

springboot中有个方法也用的很频繁——@async,只需要在方法上加上这个注解并在启动类上加上@Enableasync 就可以直接实现多线程异常操作,不过这个多线程的方法和上面讲的还是有些区别的,@async 是调用方法的时候进行多线程异步操作,但是方法中的业务逻辑还是同步的,所以正常可以搭配ComplatableFuture一起使用(当然也可以吧中间的业务逻辑提取出方法再加个@async 哈哈哈哈 都可以的)

Java的多线程机制是Java编程中不可或缺的一部分,了解和熟悉Java多线程编程能力将极大地提升程序员的工作效率和编程水平。文章来源地址https://www.toymoban.com/news/detail-429791.html

到了这里,关于浅谈之Java多线程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • Java 21新特性-虚拟线程 审核中

    本文翻译自国外论坛 medium,原文地址:https://medium.com/@benweidig/looking-at-java-21-virtual-threads-0ddda4ac1be1 Java 21 版本更新中最重要的功能之一就是虚拟线程 (JEP 444)。这些轻量级线程减少了编写、维护和观察高吞吐量并发应用程序所需的工作量。 正如我的许多其他文章一样,在推出

    2024年02月08日
    浏览(13)
  • Java 21 新特性:虚拟线程(Virtual Threads)

    在Java 21中,引入了虚拟线程(Virtual Threads)来简化和增强并发性,这使得在Java中编程并发程序更容易、更高效。 虚拟线程,也称为“用户模式线程(user-mode threads)”或“纤程(fibers)”。该功能旨在简化并发编程并提供更好的可扩展性。虚拟线程是轻量级的,这意味着它

    2024年02月08日
    浏览(9)
  • 【软考:网工】协议篇(非常重要)

    【软考:网工】协议篇(非常重要)

    还是来自summer老师的,B站可以搜“summer”课堂,看相关视频哦~ 协议分类 (1)以太网:一般用于局域网 以太网帧结构: (2)帧中继:一种广域网技术 例题:下列分组父换网络中,米用的交换技术与其他3个不同的是( )网。 A.IP B.X.25 C.帧中继 D.ATM (3)HDLC (1)ipv4 TTL经过一

    2024年02月08日
    浏览(7)
  • 【Java】JDK 21中的虚拟线程以及其他新特性

    【Java】JDK 21中的虚拟线程以及其他新特性

      目录 一、字符串模板(String Templates) 二、序列化集合(Sequenced Collections) 三、分代ZGC(Generational ZGC) 四、记录模式(Record Patterns) 五、Fibers(纤程) 结论 JDK 21是Java开发工具包的最新版本,它引入了许多令人振奋的新特性,旨在提高开发人员的生产力和代码质量。在本

    2024年02月08日
    浏览(10)
  • java八股文面试[多线程]——并发三大特性 原子 可见 顺序

    java八股文面试[多线程]——并发三大特性 原子 可见 顺序

        AutomicInteger :  volatile + CAS 总线LOCK  MESI 两个协议 TODO volatile的可见性和禁止重排序是怎么实现的: DCL场景:  new操作会在字节码层面生成两个步骤: 分配内存、调用构造器 然后把引用赋值给singleton 不加volatile则会发生指令重排,可能得到不完整的对象 知识来源: 【并

    2024年02月11日
    浏览(18)
  • 计算机网络-IP地址计算专题(非常重要)

    计算机网络-IP地址计算专题(非常重要)

    软考中的地址计算题都只需要计算出某个IP地址所在的地址范围即可。 计算就是三步。【前提是在一个字节范围类计算】 小船过河,每条小船上只能容纳2^N个小朋友 假如让你设计网络 这里转载一个大佬的博客,写得灰常好,将网络通信中的网络设备的由来讲得很通透。

    2024年02月13日
    浏览(19)
  • 浅谈字符串的定义(c++)非常详细,建议收藏

    浅谈字符串的定义(c++)非常详细,建议收藏

    C++ 有一个新的数据类型——字符串类型。这里的 string 是 C++ 所独有的,C语言没有。 定义一个值为 Initial string 的字符串 s s s ,包括空格。 定义一个空字符串 s s s 。 定义一个 s 0 s0 s 0 字符串的copy字符串 s s s 。 定义一个字符串 s 3 s3 s 3 ,该字符串的值为从 s 0 s0 s 0 的第 8

    2024年02月08日
    浏览(11)
  • 【非常重要】Hadoop成功启动的验证与集群的基本应用

    【非常重要】Hadoop成功启动的验证与集群的基本应用

    master上 slave上 master:9870 这是Hadoop自带的web监测软件,提供丰富的系统状态信息 自己理解:9870端口用来查看Hadoop中的hdfs运行状态 master:18088 监测yarn的运行状况 自己理解:18088端口用来查看Hadoop中的yarn运行状态 这个程序是验证hadoop最重要的一环,虽然在1和2的验证中都通过,

    2023年04月09日
    浏览(62)
  • MySQL 主从复制[异步 同步 半同步复制] 读写分离 优化 (非常重要)

    MySQL 主从复制[异步 同步 半同步复制] 读写分离 优化 (非常重要)

    1、什么是读写分离? 读写分离,基本的原理是让主数据库处理事务性增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。 2、为什么要读写分离呢? 因为数据库的“写”(写10000条数据

    2024年02月11日
    浏览(12)
  • 浅谈压力测试的重要目标及意义

    浅谈压力测试的重要目标及意义

    随着互联网应用的快速发展,软件系统的稳定性和性能成为了用户和企业关注的焦点。用户期望应用程序能够在高负载下依然保持稳定和高效。为了满足这一需求,压力测试成为了不可或缺的一环。本文将探讨压力测试的重要性以及如何进行压力测试。 一、压力测试的目标和

    2024年02月04日
    浏览(8)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包