多线程实现的四种方式

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程实现的四种方式相关的知识,希望对你有一定的参考价值。

参考技术A

多线程实现的四种方式Thread裸线程、Executor服务、ForkJoin框架、Actor模型。

1、Thread裸线程

线程是并发最基本的单元。Java线程本质上被映射到操作系统线程,并且每个线程对象对应着一个计算机底层线程。每个线程有自己的栈空间,它占用了JVM进程空间的指定一部分。

线程的接口相当简明,你只需要提供一个Runnable,调用start开始计算。没有现成的API来结束线程,你需要自己来实现。

优点是很接近并发计算的操作系统/硬件模型,并且这个模型非常简单。多个线程运行,通过共享内存通讯。最大劣势是,开发者很容易过分的关注线程的数量。

线程是很昂贵的对象,创建它们需要耗费大量的内存和时间。这是一个矛盾,线程太少,你不能获得良好的并发性;线程太多,将很可能导致内存问题,调度也变得更复杂。如果你需要一个快速和简单的解决方案,你绝对可以使用这个方法,不要犹豫。

2、Executor服务

另一个选择是使用API来管理一组线程。幸运的是,JVM为我们提供了这样的功能,就是Executor接口。它隐藏了如何处理Runnable的细节。

它仅仅说,“开发者!给我任务,我会处理它!”更酷的是,Executors类提供了一组方法,能够创建拥有完善配置的线程池和executor。我们将使用newFixedThreadPool,它创建预定义数量的线程,并且不允许线程数量超过这个预定义值。

这意味着,如果所有的线程都被使用的话,提交的命令将会被放到一个队列中等待;当然这是由executor来管理的。在它的上层,有ExecutorService管理executor的生命周期,以及CompletionService会抽象掉更多细节,作为已完成任务的队列。

如果你需要精确的控制程序产生的线程数量,以及它们的精确行为,那么executor和executor服务将是正确的选择。例如,需要仔细考虑的一个重要问题是,当所有线程都在忙于做其他事情时,需要什么样的策略?

增加线程数量或者不做数量限制?把任务放入到队列等待?如果队列也满了呢?无限制的增加队列大小?

感谢JDK,已经有很多配置项回答了这些问题,并且有着直观的名字,例如上面的
Executors.newFixedThreadPool(4)。

线程和服务的生命周期也可以通过选项来配置,使资源可以在恰当的时间关闭。唯一的不便之处是,对新手来说,配置选项可以更简单和直观一些。然而,在并发编程方面,你几乎找不到更简单的了。总之,对于大型系统,使用executor最合适。

3、ForkJoin框架

通过并行流,使用ForkJoinPool(FJP),Java8中加入了并行流,从此我们有了一个并行处理集合的简单方法。它和lambda一起,构成了并发计算的一个强大工具。

如果你打算运用这种方法,那么有几点需要注意。首先,你必须掌握一些函数编程的概念,它实际上更有优势。其次,你很难知道并行流实际上是否使用了超过一个线程,这要由流的具体实现来决定。如果你无法控制流的数据源,你就无法确定它做了什么。

另外,你需要记住,默认情况下是通过ForkJoinPool.commonPool实现并行的。这个通用池由JVM来管理,并且被JVM进程内的所有线程共享。这简化了配置项,因此你不用担心。

ForkJoin是一个很好的框架,当需要写一个包含并行处理的小型程序时,它是第一选择。它最大的缺点是,你必须预见到它可能产生的并发症。如果对JVM没有整体上的深入了解,这很难做到。这只能来自于经验。

4、Actor模型

JDK中没有actor的实现;因此你必须引用一些实现了actor的库。

简短地说,在actor模型中,你把一切都看做是一个actor。一个actor是一个计算实体,就像上面第一个例子中的线程,它可以从其他actor那里接收消息,因为一切都是actor。

在应答消息时,它可以给其他actor发送消息,或者创建新的actor并与之交互,或者只改变自己的内部状态。相当简单,但这是一个非常强大的概念。生命周期和消息传递由你的框架来管理,你只需要指定计算单元是什么就可以了。

另外,actor模型强调避免全局状态,这会带来很多便利。你可以应用监督策略,例如免费重试,更简单的分布式系统设计,错误容忍度等等。Akka Actors有Java接口,是最流行的JVM Actor库之一。

实际上,它也有Scala接口,并且是Scala目前默认的actor库。Scala曾经在内部实现了actor。不少JVM语言都实现了actor,比如Future。这些说明了Actor模型已经被广泛接受,并被看做是对语言非常有价值的补充。

Akka actor在内部使用ForkJoin框架来处理工作。Actor模型的强大之处来自于Props对象的接口,通过接口我们可以为actor定义特定的选择模式,定制的邮箱地址等。结果系统也是可配置的,只包含了很少的活动件。

这是一个很好的迹象!使用Actor模型的一个劣势是,它要求你避免全局状态,因此你必须小心的设计你的应用程序,而这可能会使项目迁移变得很复杂。同时,它也有不少优点,因此学习一些新的范例和使用新的库是完全值得的。

可以看出Scala非常简单,它的并发线程你无需跟线程啊,锁啊,线程间通信,线程间协同等难题打交道。它把这些都封装起来了。

Java多线程的四种实现方式

1.Java多线程实现的方式有四种:
1.继承Thread类,重写run方法
2.实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target
3.通过Callable和FutureTask创建线程
4.通过线程池创建线程
2.Thread实现方式
继承Thread类,重写run()方法,创建Thread对象调用start()方法启动线程。

public class ThreadDemo extends Thread {
    @Override
    public void run() {
        int t = 1;
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + (t++));
        }
    }

    public static void main(String[] args) {
        ThreadDemo td1 = new ThreadDemo();
        ThreadDemo td2 = new ThreadDemo();
        td1.setName("Thread1");
        td2.setName("Thread2");
        td1.start();
        td2.start();
    }
}

结果:

3.Runnable实现方式
实现Runnable接口,实现run()方法,接口的实现类的实例作为Thread的target传入带参的Thread构造函数,调用start()方法启动线程。

public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        int t = 1;
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + (t++));
        }
    }

    public static void main(String[] args) {
        RunnableDemo rd = new RunnableDemo();
        Thread tr1 = new Thread(rd);
        Thread tr2 = new Thread(rd);
        tr1.setName("Thread1");
        tr2.setName("Thread2");
        tr1.start();
        tr2.start();
    }
}
}

结果:

3.Callable和FutureTask创建线程实现方式
(1)创建Callable接口的实现类 ,并实现Call方法;
(2)创建Callable实现类的实现,使用FutureTask类包装Callable对象,该FutureTask对象封装了Callable对象的Call方法的返回值 ;
(3)使用FutureTask对象作为Thread对象的target创建并启动线程;
(4)调用FutureTask对象的get()来获取子线程执行结束的返回值。

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

public class CallableFutureTaskDemo implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int t = 1;
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + (t++));
        }
        return t;
    }

    public static void main(String[] args) {
        Callable<Integer> cftd1 = new CallableFutureTaskDemo();
        Callable<Integer> cftd2 = new CallableFutureTaskDemo();
        FutureTask<Integer> ft1 = new FutureTask<>(cftd1);
        FutureTask<Integer> ft2 = new FutureTask<>(cftd2);
        Thread t1 = new Thread(ft1);
        Thread t2 = new Thread(ft2);
        t1.setName("Thread1");
        t2.setName("Thread2");
        t1.start();
        t2.start();
        try {
            System.out.println(ft1.get());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

结果:

5.线程池实现方式

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorDemo implements Runnable {
    private static int task_num = 2;  //任务数量

    @Override
    public void run() {
        int t = 1;
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "  " + (t++));
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i <task_num; i++) {
            ExecutorDemo ed = new ExecutorDemo();
            executorService.execute(ed);
        }
        executorService.shutdown();
    }
}

结果:

java里面的线程池的顶级接口是Executor,Executor并不是一个线程池,而只是一个执行线程的工具,而真正的线程池是ExecutorService。
java中的有哪些线程池?
1.newCachedThreadPool创建一个可缓存线程池程
2.newFixedThreadPool 创建一个定长线程池
3.newScheduledThreadPool 创建一个定长线程池
4.newSingleThreadExecutor 创建一个单线程化的线程池
这里的例子用到的就是newFixedThreadPool 。

6.总结
(1)实现Runnable接口比继承Thread类更具有优势!Runnable接口适合多个相同的程序代码的线程去处理同一个资源,可以避免java中的单继承的限制,增加程序的健壮性,而且代码可以被多个线程共享,实现代码和数据独立。

(2)使用线程池可以减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务,可以根据系统的承受能力,调整线程池中工作线线程的数量。

(3)实际应用中,可以通过一个boolean标志位来结束线程。

(4)ExecutorService、Callable都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,还有Future接口也是属于这个框架,有了这种特征得到返回值更方便。
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的值(由泛型决定)了。get()方法是阻塞的,即线程无返回结果,get方法会一直等待。此外,ExecutoreService提供了submit()方法,传递一个Callable或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。

以上是关于多线程实现的四种方式的主要内容,如果未能解决你的问题,请参考以下文章

Java实现多线程的四种方式

JAVA多线程实现的四种方式

JAVA多线程实现的四种方式

Java多线程的四种实现方式

Java多线程实现的四种方式

Java多线程实现的四种方式