java多线程笔记part(Java多线程详解)

发布时间:2022-11-13

本文目录一览:

  1. java 多线程
  2. 如何使用Java编写多线程程序(1)
  3. java 如何实现多线程
  4. java多核多线程
  5. 多线程回顾笔记总结
  6. 在Java 中多线程的实现方法有哪些,如何使用

java 多线程

所要执行的指令,也包括了执行指令所需的系统资源,不同进程所占用的系统资源相对独立。所以进程是重量级的任务,它们之间的通信和转换都需要操作系统付出较大的开销。 线程是进程中的一个实体,是被系统独立调度和分派的基本单位。线程自己基本上不拥有系统资源,但它可以与同属一个进程的其他线程共享进程所拥有的全部资源。所以线程是轻量级的任务,它们之间的通信和转换只需要较小的系统开销。 Java支持多线程编程,因此用Java编写的应用程序可以同时执行多个任务。Java的多线程机制使用起来非常方便,用户只需关注程序细节的实现,而不用担心后台的多任务系统。 Java语言里,线程表现为线程类。Thread线程类封装了所有需要的线程操作控制。在设计程序时,必须很清晰地区分开线程对象和运行线程,可以将线程对象看作是运行线程的控制面板。在线程对象里有很多方法来控制一个线程是否运行、睡眠、挂起或停止。线程类是控制线程行为的唯一的手段。一旦一个Java程序启动后,就已经有一个线程在运行。可通过调用Thread.currentThread方法来查看当前运行的是哪一个线程。

class ThreadTest{
    public static void main(String args[]){
        Thread t = Thread.currentThread();
        t.setName("单线程"); //对线程取名为"单线程"
        t.setPriority(8);
        //设置线程优先级为8,最高为10,最低为1,默认为5
        System.out.println("The running thread: " + t);
        // 显示线程信息
        try{
            for(int i=0;i<3;i++){
                System.out.println("Sleep time " + i);
                Thread.sleep(100); // 睡眠100毫秒
            }
        }catch(InterruptedException e){// 捕获异常
            System.out.println("thread has wrong");
        }
    }
}

多线程的实现方法

继承Thread类

可通过继承Thread类并重写其中的run()方法来定义线程体以实现线程的具体行为,然后创建该子类的对象以创建线程。 在继承Thread类的子类ThreadSubclassName中重写run()方法来定义线程体的一般格式为:

public class ThreadSubclassName extends Thread{
    public ThreadSubclassName(){
        // 编写子类的构造方法,可缺省
    }
    public void run(){
        // 编写自己的线程代码
    }
}

用定义的线程子类ThreadSubclassName创建线程对象的一般格式为:

ThreadSubclassName ThreadObject = new ThreadSubclassName();

然后,就可启动该线程对象表示的线程:

ThreadObject.start(); //启动线程

应用继承类Thread的方法实现多线程的程序。本程序创建了三个单独的线程,它们分别打印自己的“Hello World!”。

class ThreadDemo extends Thread{
    private String whoami;
    private int delay;
    public ThreadDemo(String s,int d){
        whoami=s;
        delay=d;
    }
    public void run(){
        try{
            sleep(delay);
        }catch(InterruptedException e){
            System.out.println("Hello World!" + whoami + " " + delay);
        }
    }
}
public class MultiThread{
    public static void main(String args[]){
        ThreadDemo t1,t2,t3;
        t1 = new ThreadDemo("Thread1", (int)(Math.random()*2000));
        t2 = new ThreadDemo("Thread2", (int)(Math.random()*2000));
        t3 = new ThreadDemo("Thread3", (int)(Math.random()*2000));
        t1.start();
        t2.start();
        t3.start();
    }
}

实现Runnable接口

编写多线程程序的另一种的方法是实现Runnable接口。在一个类中实现Runnable接口(以后称实现Runnable接口的类为Runnable类),并在该类中定义run()方法,然后用带有Runnable参数的Thread类构造方法创建线程。 创建线程对象可用下面的两个步骤来完成:

  1. 生成Runnable类ClassName的对象
ClassName RunnableObject = new ClassName();
  1. 用带有Runnable参数的Thread类构造方法创建线程对象。新创建的线程的指针将指向Runnable类的实例。用该Runnable类的实例为线程提供 run()方法---线程体。
Thread ThreadObject = new Thread(RunnableObject);

然后,就可启动线程对象ThreadObject表示的线程:

ThreadObject.start();

在Thread类中带有Runnable接口的构造方法有:

  • public Thread(Runnable target);
  • public Thread(Runnable target, String name);
  • public Thread(String name);
  • public Thread(ThreadGroup group, Runnable target);
  • public Thread(ThreadGroup group, Runnable target, String name); 其中,参数Runnable target表示该线程执行时运行target的run()方法,String name以指定名字构造线程,ThreadGroup group表示创建线程组。 用Runnable接口实现的多线程。
class TwoThread implements Runnable{
    TwoThread(){
        Thread t1 = Thread.currentThread();
        t1.setName("第一主线程");
        System.out.println("正在运行的线程: " + t1);
        Thread t2 = new Thread(this,"第二线程");
        System.out.println("创建第二线程");
        t2.start();
        try{
            System.out.println("第一线程休眠");
            Thread.sleep(3000);
        }catch(InterruptedException e){
            System.out.println("第一线程有错");
        }
        System.out.println("第一线程退出");
    }
    public void run(){
        try{
            for(int i = 0; i < 5; i++){
                System.out.println("第二线程的休眠时间:" + i);
                Thread.sleep(1000);
            }
        }catch(InterruptedException e){
            System.out.println("线程有错");
        }
        System.out.println("第二线程退出");
    }
    public static void main(String args[]){
        new TwoThread();
    }
}

程序运行结果如下:

正在运行的线程: Thread[第一主线程,5,main]
创建第二线程
第一线程休眠
第二线程的休眠时间:0
第二线程的休眠时间:1
第二线程的休眠时间:2
第一线程退出
第二线程的休眠时间:3
第二线程的休眠时间:4
第二线程退出

如何使用Java编写多线程程序(1)

一、简介

  1. 什么是线程 要说线程,就必须先说说进程,进程就是程序的运行时的一个实例。线程呢可以看作单独地占有CPU时间来执行相应的代码的。对早期的计算机(如DOS)而言,线程既是进程,进程既是进程,因为她是单线程的。当然一个程序可以是多线程的,多线程的各个线程看上去像是并行地独自完成各自的工作,就像一台一台计算机上运行着多个处理机一样。在多处理机计算机上实现多线程时,它们确实可以并行工作,而且采用适当的分时策略可以大大提高程序运行的效率。但是二者还是有较大的不同的,线程是共享地址空间的,也就是说多线程可以同时读取相同的地址空间,并且利用这个空间进行交换数据。
  2. 为什么要使用线程 学过《计算机体系结构》的人都知道。将顺序执行程序和采用多线程并行执行程序相比,效率是可以大大地提高的。比如,有五个线程thread1, thread2, thread3, thread4, thread5,所耗的CPU时间分别为4,5,1,2,7。(假设CPU轮换周期为4个CPU时间,而且线程之间是彼此独立的)顺序执行需要花费1Array个CPU时间,而并行需要的时间肯定少于1Array个CPU时间,至于具体多少时间要看那些线程是可以同时执行的。这是在非常小规模的情况下,要是面对大规模的进程之间的交互的话,效率可以表现得更高。
  3. java中是如何实现多线程的 与其他语言不一样的是,线程的观念在java是语言中是重要的,根深蒂固的,因为在java语言中的线程系统是java语言自建的, java中有专门的支持多线程的API库,所以你可以以最快的速度写一个支持线程的程序。在使用java创建线程的时候,你可以生成一个Thread类或者他的子类对象,并给这个对象发送start()消息(程序可以向任何一个派生自 Runnable 接口的类对象发送 start() 消息的),这样一来程序会一直执行,直到run返回为止,此时该线程就死掉了。在java语言中,线程有如下特点:
  • 在一个程序中而言,主线程的执行位置就是main。而其他线程执行的位置,程序员是可以自定义的。值得注意的是对Applet也是一样。
  • 每个线程执行其代码的方式都是一次顺序执行的。
  • 一个线程执行其代码是与其他线程独立开来的。如果诸线程之间又相互协作的话,就必须采用一定的交互机制。
  • 前面已经说过,线程是共享地址空间的,如果控制不当,这里很有可能出现死锁。 各线程之间是相互独立的,那么本地变量对一个线程而言就是完全独立,私有的。所以呢,线程执行时,每个线程都有各自的本地变量拷贝。对象变量(instance variable)在线程之间是可以共享的,这也就是为什么在java中共享数据对象是如此的好用,但是java线程不能够武断地访问对象变量:他们是需要访问数据对象的权限的。

二、准备知识

在分析这个例子之前,然我们先看看关于线程的几个概念,上锁,信号量,和java所提供的API。

上锁

对于大多数的程序而言,他们都需要线程之间相互的通讯来完成整个线程的生命周期,二实现线程之间同步的最简单的办法就是上锁。为了防止相互关联的两个线程之间错误地访问共享资源,线程需要在访问资源的时候上锁和解锁,对于锁而言,有读锁,写锁和读写锁等不同的同步策略。在java中,所有的对象都有锁;线程只需要使用synchronized关键字就可以获得锁。在任一时刻对于给定的类的实例,方法或同步的代码块只能被一个线程执行。这是因为代码在执行之前要求获得对象的锁。

信号量

通常情况下,多个线程所访问为数不多的资源,那怎么控制呢?一个比较非常经典而起非常简单的办法就是采用信号量机制。信号量机制的含义就是定义一个信号量,也就是说能够提供的连接数;当有一个线程占用了一个连接时,信号量就减一;当一个线程是放了连接时,信号量就加一。

java 如何实现多线程

java中多线程的实现方式有两种,一种是继承java.lang.Thread类,另一种是实现java.lang.Runnable接口。下面是两种方式的简单代码。

继承Thread类方式:

import java.lang.Thread;
//用集成Thread类方式实现多线程。
public class Test{
    public static void main(String arg[]){
        T t1=new T();
        T t2=new T();
        //更改新线程名称
        t1.setName("t1");
        t2.setName("t2");
        //启动线程
        t1.start();
        t2.start();
    }
}
class T extends Thread{
    //重写run()方法
    public void run(){
        System.out.println(this.getName());
    }
}

输出结果为:

t1
t2

实现Runnable接口方式:

在使用Runnable接口时需要建立一个Thread实例。因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例。

import java.lang.*;
//用实现Runnable接口的方式实现多线程。
public class Test{
    public static void main(String arg[]){
        T t1=new T();
        T t2=new T();
        //一定要实例化Thread对象,将实现Runnable接口的对象作为参数传入。
        Thread th1=new Thread(t1,"t1");
        Thread th2=new Thread(t2,"t2");
        //启动线程
        th1.start();
        th2.start();
    }
}
class T implements Runnable{
    //重写run()方法
    public void run(){
        System.out.println(Thread.currentThread().getName());
    }
}

输出结果为:

t1
t2

public void run()方法是JAVA中线程的执行体方法,所有线程的操作都是从run方法开始,有点类似于main()方法,即主线程。

java多核多线程

java可以实现多线程. 但是不能判断某个线程再哪个CPU上执行. java实现多线程有两种方法。

一种是继承Thread类

class MyThread extends Thread{
    @Override
    public void run(){
        //要执行的代码
    }
}

调用时,使用new MyThread().start()就可以了

另一种方法是实现Runnable接口

class MyThread implements Runnable{
    @Override
    public void run(){
        //要执行的代码
    }
}

调用方法:

new Thread(new MyThread()).start()

多线程回顾笔记总结

三种创建方式:

练习Thread,实现多线程同步下载图片

public class TestThread1 extends Thread {
    private String name; // 保存文件名
    // 创建一个构造器
    public TestThread1(String url, String name) {
        this.url = url;
        this.name = name;
    }
    public static void main(String[] args) {
        TestThread1 testThread1 = new TestThread1("url1","1.jpg");
        TestThread1 testThread2 = new TestThread1("url2","2.jpg");
        TestThread1 testThread3 = new TestThread1("url3","3.jpg");
        testThread1.start();
        testThread2.start();
        testThread3.start();
        /* 结果,说明线程不是按代码先后顺序执行的,是同时执行的
        下载了文件,名为:3.jpg
        下载了文件,名为:1.jpg
        下载了文件,名为:2.jpg
        */
    }
    // 下载图片的线程执行体
    @Override
    public void run() {
        WebDownLoader webDownLoader = new WebDownLoader();
        webDownLoader.downLoader(url,name);
        System.out.println("下载了文件,名为:"+name);
    }
}
// 下载器
class WebDownLoader {
    // 下载方法
    public void downLoader(String url,String name) {
        try {
            // copyURLToFile(),这个方法就是把网上的一个url变成一个文件
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downLoader方法出错");
        }
    }
}

创建线程方式2:实现Runnable接口,重写run()方法,执行线程需要丢入Runnable接口实现类,调用start()方法

public class TestThread2 implements Runnable {
    public static void main(String[] args) {
        // 创建Runnable 接口实现类对象
        TestThread2 testThread2 = new TestThread2();
        // 创建线程对象,通过线程对象来开启我们的线程,代理
        Thread thread = new Thread(testThread2);
        thread.start();
        //new Thread(testThread2).start(); // 简写方法
        for (int i = 0; i < 2000; i++) {
            System.out.println("我们在学习多线程---"+i);
        }
    }
    @Override
    public void run() {
        for (int i = 0; i < 2000; i++) {
            System.out.println("我们在玩 游戏 啦---"+i);
        }
    }
}

发现问题:多个线程操作同一个资源情况下,线程不安全,数据紊乱,出现重复数据

模拟龟兔赛跑

public class Race implements Runnable {
    // 胜利者
    private static String winner;
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            // 模拟比赛暂停
            if(Thread.currentThread().getName().equals("兔子") && i == 90){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 判断比赛是否结束
            boolean flag = gameOver(i);
            if(flag){
                break;
            }
            System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
        }
    }
    // 判断是否完成比赛
    private boolean gameOver(int steps){
        if(winner != null){
            // 已经有胜利者了
            return true;
        }
        if(steps == 100){
            winner = Thread.currentThread().getName();
            System.out.println("winner is " + winner);
            return true;
        }
        return false;
    }
    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race, "兔子").start();
        new Thread(race, "乌龟").start();
    }
}

关闭服务:

executorService.shutdownNow();

线程创建方式三:实现Callable接口

import java.util.concurrent.*;
public class TestCallable implements Callable<Boolean> {
    private String name; // 保存文件名
    // 创建一个构造器
    public TestCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable testThread1 = new TestCallable("url1","1.jpg");
        TestCallable testThread2 = new TestCallable("url2","2.jpg");
        TestCallable testThread3 = new TestCallable("url3","3.jpg");
        // 创建执行服务
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        // 提交执行
        Future<Boolean> submit1 = executorService.submit(testThread1);
        Future<Boolean> submit2 = executorService.submit(testThread2);
        Future<Boolean> submit3 = executorService.submit(testThread3);
        // 获取结果
        Boolean b1 = submit1.get();
        Boolean b2 = submit2.get();
        Boolean b3 = submit3.get();
        // 关闭服务
        executorService.shutdownNow();
        System.out.println(b1);
        System.out.println(b2);
        System.out.println(b3);
    }
    // 下载图片的线程执行体
    @Override
    public Boolean call() {
        WebDownLoader webDownLoader = new WebDownLoader();
        webDownLoader.downLoader(url,name);
        System.out.println("下载了文件,名为:"+name);
        return true;
    }
}
// 下载器
class WebDownLoader {
    // 下载方法
    public void downLoader(String url,String name) {
        try {
            // copyURLToFile(),这个方法就是把网上的一个url变成一个文件
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,downLoader方法出错");
        }
    }
}

静态代理模式总结:

  • 真实对象和代理对象都要实现同一个接口
  • 代理对象要代理真实角色

代理模式的好处:

  • 代理对象可以做很多真实对象做不了的事情
  • 真实对象专注做自己的事情就可以了,其他的事情交给代理公司来做

五大状态:

测试Stop

  1. 建议线程正常停止,利用次数,不建议死循环
  2. 建议使用标志位,设置一个标志位
  3. 不要使用stop(),destroy()方法,已过时
public class StopTest implements Runnable {
    // 1 设置一个标志位
    private boolean flag = true;
    public static void main(String[] args) {
        StopTest stopTest = new StopTest();
        // 开启线程
        new Thread(stopTest).start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main Thread is running..."+i);
            if (i == 900) {
                // 调用stop方法切换标志位停止线程
                stopTest.stop();
                System.out.println("Thread is stopped...");
            }
        }
    }
    // 设置一个公开的方法停止线程,转换标志位
    public void stop() {
        this.flag = false;
    }
    @Override
    public void run() {
        int i = 0;
        while (flag) {
            System.out.println("Thread is running..." + i++);
        }
    }
}

yield

jion

daemon

化妆的方法,互相持有对方的锁,就是需要拿到对方的资源

private void makeup() throws InterruptedException {
    if (choice == 0) {
        synchronized (lipstick) { // 获得口红的锁
            System.out.println(this.girlName+"获得口红的锁");
            Thread.sleep(1000);
        }
        synchronized (mirror) { // 一秒后想获得镜子的锁
            System.out.println(this.girlName+"获得镜子的锁");
        }
    } else {
        synchronized (mirror) { // 获得镜子的锁
            System.out.println(this.girlName+"获得镜子的锁");
            Thread.sleep(2000);
        }
        synchronized (lipstick) { // 一秒后想获得口红的锁
            System.out.println(this.girlName+"获得口红的锁");
        }
    }
}

这样synchronized块包裹着,就会导致程序卡死,只要不包裹着,就可以正常运行,如下:

死锁,多个线程互相抱着对方需要的资源,然后形成僵持

public class DeadLock {
    public static void main(String[] args) {
        Makeup bestGirl = new Makeup(0, "婕儿");
        Makeup betterGirl = new Makeup(1, "珂儿");
        bestGirl.start();
        betterGirl.start();
    }
}
// 口红
class Lipstick {
}
// 镜子
class Mirror {
}
// 化妆
class Makeup extends Thread {
    // 需要的资源只有一份,用static来保证只有一份
    static Mirror mirror = new Mirror();
    static Lipstick lipstick = new Lipstick();
    int choice; // 选择
    String girlName; // 使用化妆品的人
    public Makeup(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }
    @Override
    public void run() {
        // 化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    // 化妆的方法,互相持有对方的锁,就是需要拿到对方的资源
    private void makeup() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) { // 获得口红的锁
                System.out.println(this.girlName+"获得口红的锁");
                Thread.sleep(1000);
            }
            synchronized (mirror) { // 一秒后想获得镜子的锁
                System.out.println(this.girlName+"获得镜子的锁");
            }
        } else {
            synchronized (mirror) { // 获得镜子的锁
                System.out.println(this.girlName+"获得镜子的锁");
                Thread.sleep(2000);
            }
            synchronized (lipstick) { // 一秒后想获得口红的锁
                System.out.println(this.girlName+"获得口红的锁");
            }
        }
    }
}

上面列出了死锁的四个条件,我们只要想办法破其中任意一个,或多个条件就可以避免死锁发生

  1. 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件.
  2. Java提供了几个方法解决线程之间的通信问题
  3. 解决方式1 并发协作模型“生产者/消费者模式” —管程法 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
  4. 解决方式2

测试线程池

public class PoolTest {
    public static void main(String[] args) {
        // 创建服务,创建池子
        // newFixedThreadPool,参数为:线程池池子大小
        ExecutorService service = Executors.newFixedThreadPool(10);
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        // 2.关闭连接
        service.shutdown();
    }
}
class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

在Java 中多线程的实现方法有哪些,如何使用

Java多线程的创建及启动

Java中线程的创建常见有如三种基本形式

  1. 继承Thread类,重写该类的run()方法。
class MyThread extends Thread {
    private int i = 0;
    @Override
    public void run() {
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Thread myThread1 = new MyThread(); // 创建一个新的线程 myThread1 此线程进入新建状态
                Thread myThread2 = new MyThread(); // 创建一个新的线程 myThread2 此线程进入新建状态
                myThread1.start(); // 调用start()方法使得线程进入就绪状态
                myThread2.start(); // 调用start()方法使得线程进入就绪状态
            }
        }
    }
}

如上所示,继承Thread类,通过重写run()方法定义了一个新的线程类MyThread,其中run()方法的方法体代表了线程需要完成的任务,称之为线程执行体。当创建此线程类对象时一个新的线程得以创建,并进入到线程新建状态。通过调用线程对象引用的start()方法,使得该线程进入到就绪状态,此时此线程并不一定会马上得以执行,这取决于CPU调度时机。 2. 实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。

class MyRunnable implements Runnable {
    private int i = 0;
    @Override
    public void run() {
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Runnable myRunnable = new MyRunnable(); // 创建一个Runnable实现类的对象
                Thread thread1 = new Thread(myRunnable); // 将myRunnable作为Thread target创建新的线程
                Thread thread2 = new Thread(myRunnable);
                thread1.start(); // 调用start()方法使得线程进入就绪状态
                thread2.start();
            }
        }
    }
}

相信以上两种创建新线程的方式大家都很熟悉了,那么Thread和Runnable之间到底是什么关系呢?我们首先来看一下下面这个例子。

public class ThreadTest {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Runnable myRunnable = new MyRunnable();
                Thread thread = new MyThread(myRunnable);
                thread.start();
            }
        }
    }
}
class MyRunnable implements Runnable {
    private int i = 0;
    @Override
    public void run() {
        System.out.println("in MyRunnable run");
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}
class MyThread extends Thread {
    private int i = 0;
    public MyThread(Runnable runnable){
        super(runnable);
    }
    @Override
    public void run() {
        System.out.println("in MyThread run");
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }
}

同样的,与实现Runnable接口创建线程方式相似,不同的地方在于

Thread thread = new MyThread(myRunnable);

那么这种方式可以顺利创建出一个新的线程么?答案是肯定的。至于此时的线程执行体到底是MyRunnable接口中的run()方法还是MyThread类中的run()方法呢?通过输出我们知道线程执行体是MyThread类中的run()方法。其实原因很简单,因为Thread类本身也是实现了Runnable接口,而run()方法最先是在Runnable接口中定义的方法。

public interface Runnable {
    public abstract void run();
}

我们看一下Thread类中对Runnable接口中run()方法的实现:

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

也就是说,当执行到Thread类中的run()方法时,会首先判断target是否存在,存在则执行target中的run()方法,也就是实现了Runnable接口并重写了run()方法的类中的run()方法。但是上述给到的列子中,由于多态的存在,根本就没有执行到Thread类中的run()方法,而是直接先执行了运行时类型即MyThread类中的run()方法。 3. 使用Callable和Future接口创建线程。具体是创建Callable接口的实现类,并实现call()方法。并使用FutureTask类来包装Callable实现类的对象,且以此FutureTask对象作为Thread对象的target来创建线程。 看着好像有点复杂,直接来看一个例子就清晰了。

public class ThreadTest {
    public static void main(String[] args) {
        Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象
        FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); // 使用FutureTask来包装MyCallable对象
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            if (i == 30) {
                Thread thread = new Thread(ft); // FutureTask对象作为Thread对象的target创建新的线程
                thread.start(); // 线程进入到就绪状态
            }
        }
        System.out.println("主线程for循环执行完毕..");
        try {
            int sum = ft.get(); // 取得新创建的新线程中的call()方法返回的结果
            System.out.println("sum = " + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
class MyCallable implements Callable<Integer> {
    private int i = 0;
    // 与run()方法不同的是,call()方法具有返回值
    @Override
    public Integer call() {
        int sum = 0;
        for (; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
            sum += i;
        }
        return sum;
    }
}

首先,我们发现,在实现Callable接口中,此时不再是run()方法了,而是call()方法,此call()方法作为线程执行体,同时还具有返回值!在创建新的线程时,是通过FutureTask来包装MyCallable对象,同时作为了Thread对象的target。那么看下FutureTask类的定义:

public class FutureTask<V> implements RunnableFuture<V> {
    //....
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

于是,我们发现FutureTask类实际上是同时实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。通过Runnable特性,可以作为Thread对象的target,而Future特性,使得其可以取得新创建线程中的call()方法的返回值。 执行下此程序,我们发现sum = 4950永远都是最后输出的。而“主线程for循环执行完毕..”则很可能是在子线程循环中间输出。由CPU的线程调度机制,我们知道,“主线程for循环执行完毕..”的输出时机是没有任何问题的,那么为什么sum =4950会永远最后输出呢? 原因在于通过ft.get()方法获取子线程call()方法的返回值时,当子线程此方法还未执行完毕,ft.get()方法会一直阻塞,直到call()方法执行完毕才能取到返回值。 上述主要讲解了三种常见的线程创建方式,对于线程的启动而言,都是调用线程对象的start()方法,需要特别注意的是:不能对同一线程对象两次调用start()方法。 你好,本题已解答,如果满意请点右下角“采纳答案”。