创建线程的三种方式

理解进程和线程

进程(Process)

  • 是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
  • 在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

线程(thread)

  • 是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
  • 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

进程:
(1)指在系统中正在运行的一个应用程序;
(2)程序一旦运行就是进程;
(3)进程是资源分配的最小单位。

线程:
(1)系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元
执行流。
(2)线程是程序执行的最小单位。


为什么要使用多线程?
  1. 使用多线程的理由之一是和进程相比,它是一种非常花销小,切换快,更"节俭"的多任务操作方式。
  2. 在Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
  3. 线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,还有以下的优点:
(1)更多的处理器核心
一个单线程程序在运行时只能使用一个处理器核心,那么再多的处理器核心加入也无法显著该程序的执行效率。相反,如果该程序使用多线程技术,将计算逻辑分配到多个处理器核心上,就会显著减少程序的处理时间,并且随着更多处理器核心的加入而变得更有效率。

(2)更快的响应时间
可以使用多线程技术,将数据一致性不强的操作派发给其他线程处理(也可以使用消息队列)。这样做的好处是响应用户请求的线程能够尽可能快地处理完成,缩短响应时间,提升用户体验。

例如:
用户提交一次订单,后续会等待一系列操作全部完成才能看到订购成功的结果。对于其中的一些业务操作,可以考虑交给其他线程处理,从而使其更快地完成。

(3)更好的编程模型
Java为多线程编程提供了良好、考究并且一致的编程模型,当开发人员为所遇到的问题建立合适的模型之后,稍作修改就能够方便地映射到Java提供的多线程编程模型上。


创建线程的方式

1.继承Thread类
(1)定义 Thread 类的子类,并重写该类的 run() 方法,该 run() 方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建 Thread 子类的实例,即创建了线程对象。
(3)调用线程对象的 start() 方法来启动该线程。

import java.util.concurrent.*;

public class TestThread {
    public static void main(String[] args) throws Exception {
        testExtends();
    }

    public static void testExtends() throws Exception {
        Thread t1 = new MyThreadExtends();
        Thread t2 = new MyThreadExtends();
        t1.start();
        t2.start();
    }
}

class MyThreadExtends extends Thread {
    @Override
    public void run() {
        System.out.println("通过继承Thread,线程号:" + currentThread().getName());
    }
}

2.实现Runnable接口 
(1)定义 Runnable 接口的实现类,并重写该接口的 run() 方法,该run() 方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable 实现类的实例,并依此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
(3)调用线程对象的 start() 方法来启动该线程。

mport java.util.concurrent.*;
//测试类
public class TestThread {
    public static void main(String[] args) throws Exception {
         testImplents();
    }

    public static void testImplents() throws Exception {
        MyThreadImplements myThreadImplements = new MyThreadImplements();
        Thread t1 = new Thread(myThreadImplements);
        Thread t2 = new Thread(myThreadImplements, "my thread -2");
        t1.start();
        t2.start();
    }
}
//线程类
class MyThreadImplements implements Runnable {
    @Override
    public void run() {
        System.out.println("通过实现Runable,线程号:" + Thread.currentThread().getName());
    }
}

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

import java.util.concurrent.*;

public class TestThread {
    public static void main(String[] args) throws Exception {
        testCallable();
    }

    public static void testCallable() throws Exception {
        Callable callable = new MyThreadCallable();
        FutureTask task = new FutureTask(callable);
        new Thread(task).start();
        System.out.println(task.get());
        Thread.sleep(10);//等待线程执行结束
        //task.get() 获取call()的返回值。若调用时call()方法未返回,则阻塞线程等待返回值
        //get的传入参数为等待时间,超时抛出超时异常;传入参数为空时,则不设超时,一直等待
        System.out.println(task.get(100L, TimeUnit.MILLISECONDS));
    }
}

class MyThreadCallable implements Callable {

    @Override
    public Object call() throws Exception {
        System.out.println("通过实现Callable,线程号:" + Thread.currentThread().getName());
        return 10;
    }
}
三种方式的优缺点

继承Thread类方式:

  • 优点:编写简单,如果需要访问当前线程,无需使用 Thread.currentThread() 方法,直接使用 this ,即可获得当前线程。
  • 缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。

实现Runnable接口方式:

  • 优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
  • 缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

Runnable和Callable的区别:

  • 1)Callable规定的方法是 call() ,Runnable规定的方法是 run() .
  • 2)Callable 的任务执行后可返回值,而Runnable的任务是不能有返回值
  • 3)call() 方法可以抛出异常,run() 方法不可以,因为run() 方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常
  • 4)运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过 Future 对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

start()和run()的区别

  • start() 方法用来开启线程,但是线程开启后并没有立即执行,他需要获取cpu的执行权才可以执行
  • run() 方法是由 jvm 创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值