Java多线程

什么是线程?

  • 线程(thread)是一个程序内部的一条执行路径。
  • 我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径。
  • main方法启动它是由一个主线程来执行,就是能够默认的线程我们一般把它叫做主线程,除此之外的线程一般叫子线程。main方法就是由一个所谓的主线程来调的。
  • main方法是由主线程负责调度的!
  • 主线程的默认名称就叫main

建议把主线程任务都放在子线程之后,不要把主线程任务放在子线程之前!

  • Java多线程_第1张图片
  •  程序中如果只有一条执行路径,那么这个程序就是单线程的程序。
  •  多线程就是多条线程在同时执行。
  •  多线程一定是并发和并行在同时推进。
  • 多线程的作用:多线程可以提高程序的运行效率,CPU可以在多个程序之间进行切换。
  • 什么是多线程:有了多线程,我们就可以让程序同时做多件事情。
  • 多线程的应用场景:只要你想让多个事情同时运行那么就一定会用到多线程。比如:软件中的耗时操作,所有的聊天软件,所有的服务器。
  •  多线程是指从软硬件上实现多条执行流程的技术。就是程序中由多条执行流程在同时执行。
  • Java多线程_第2张图片

 一. 多线程的创建

  • 实现多线程的方式都在这个Thread类里面,因为Thread类代表线程!

Java多线程_第3张图片

方式一:继承Thread类

Thread类:线程类

  • Java是通过java.lang.Thread类来代表线程的。
  • 按照面向对象的思想,Thread类应该提供了实现多线程的方式。

多线程的实现方式一:继承Thread类

  1. 定义一个子类MyThread继承线程类Thread,重写run()方法
  2. 创建MyThread类的对象
  3. 调用线程对象的start()方法启动线程(启动线程后会自动执行run方法)
  • 当run方法执行完,该线程结束,资源被回收(一旦结束不能重新启动)
  • run()方法属于线程的任务方法
  • 建议把子线程任务放在主线程之前!

继承Thread类实现多线程的优缺点:

  • 优点:编码简单
  • 缺点:存在单继承的局限性,线程类已经继承Thread类,无法 / 不能继承其他类,不利 / 便于扩展。

为什么不直接调用run方法,而是调用start方法启动线程?

  • 直接调用run方法会当成普通方法来执行,此时相当于还是单线程执行。
  • 只有调用start方法才是启动一个新的线程执行。

不要把主线程任务放在子线程之前,建议把主线程任务都放在子线程之后,为什么?

  • 如果把主线程任务放在子线程之前,这样主线程一直是先跑完的,相当于还是一个单线程的效果了。
package com.gch.d1_create;

/**
   目标:多线程的创建方式一:继承Thread类实现。
 */
public class ThreadDemo1 {
    public static void main(String[] args) {
        // 3.new一个新线程对象
        Thread t = new MyThread(); // 多态
        // 4.调用start方法启动线程(执行的还是run方法)
        t.start(); // start方法告诉操作系统这边开启了一个新线程,通知cpu以线程的方式来启动run方法
//        t.run(); // 它会认为这是个普通类,普通类调用普通方法,程序由上往下执行

        for (int i = 0; i < 5; i++) {
            System.out.println("主线程执行输出:" + i);
        }
    }
}

/**
   1.定义一个线程类继承Thread类
 */
class MyThread extends Thread{
    /**
       2.重写run方法,里面是定义线程以后要干啥
     */
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程执行输出:" + i);
        }
    }
}

方式二:实现Runnable接口

多线程的实现方案二:实现Runnable接口

  1. 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
  2. 创建MyRunnable线程任务对象
  3. 把MyRunnable任务对象交给Thread线程对象处理
  4. 调用线程对象的start()方法启动线程

Java多线程_第4张图片

package com.gch.d1_create;

/**
   目标:学会线程的创建方式二,理解它的优缺点
 */
public class ThreadDemo2 {
    public static void main(String[] args) {
        // 3.创建一个任务对象
        Runnable target = new MyRunnable();
        // 4.把任务对象交给Thread处理
        Thread t = new Thread(target);
//        Thread t = new Thread(target,"线程名");
        // 5.启动线程
        t.start(); // 开始出现两个线程
        // 两个线程同时执行,同时执行的话是有先后的

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程执行输出:" + i);
        }
    }
}

/**
 * 1.定义一个线程任务类 实现Runnable接口
 */
class MyRunnable implements Runnable {
    /**
       2.重写run方法,定义线程的执行任务
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程执行输出:" + i);
        }
    }
}

实现Runnable接口来实现多线程的优缺点:

  • 优点:线程任务类只是实现Runnable接口,它可以继续继承类和实现其他接口,扩展性强。
  • 缺点:编程多一层对象包装(把线程任务对象交给线程对象),如果线程有执行结果是不可以直接返回的(因为run方法返回值为void空)

方式二:还可以通过创建Runnable的匿名内部类对象来实现多线程

创建Runnable的匿名内部类对象来实现多线程:

  1. 创建Runnable的匿名内部类对象
  2. 交给Thread线程对象处理
  3. 调用线程对象的start()方法启动线程
package com.gch.d1_create;

/**
   目标:学会线程的创建方式二(匿名内部类方式实现,语法形式)
 */
public class ThreadDemo2Other {
    public static void main(String[] args) {
        // 1.创建Runnable的匿名内部类对象
        Runnable target = new Runnable() {
            // 2.重写run方法,定义线程的执行任务
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程1执行输出:" + i);
                }
            }
        };
        // 3.交给Thread处理
        Thread t = new Thread(target);
        // 4.调用线程对象的start方法启动线程
        t.start();

        // 1.还可以继续简化,把Runnable的匿名内部类对象作为Thread线程对象的入参
        Thread t2 = new Thread(new Runnable() {
            // 2.重写run方法,定义线程的执行任务
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程2输出:" + i);
                }
            }
        });
        // 3.调用线程对象的start方法启动线程
        t2.start();

        // 或者直接newThread类的对象然后直接调用start方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程3输出:" + i);
                }
            }
        }).start();

        // Lambda表达式进一步简化
        new Thread(() -> {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程输4出:" + i);
                }
            }).start();

        /**
           主线程
         */
        for (int i = 0; i < 10; i++) {
            System.out.println("主线程执行输出:" + i);
        }
    }
}

方式三:JDK5.0新增:实现Callable接口

1. 前两种线程创建方式都存在一个问题:

  • 它们重写run方法均不能直接返回结果
  • 继承Thread类、实现Runnable接口不适合需要返回线程执行结果的业务场景。

 2. 怎么解决这个问题?

  • JDK 5.0提供了Callable接口和FutureTask(未来任务对象)来实现。
  • 这种方式的优点是:可以得到线程执行的结果。

多线程的实现方式三:利用Callable接口、FutureTask(未来任务对象)实现

  1. 得到任务对象
  2. 定义类实现Callable接口,重写call方法,封装要做的事情
  3. 用FutureTask把Callable对象封装成线程任务对象
  4. 把线程任务对象交给Thread线程对象处理
  5. 调用Thread线程对象的start方法启动线程,执行任务
  6. 线程执行完毕后,通过FutureTask的get()方法去获取任务执行的结果。

Java多线程_第5张图片

package com.gch.d1_create;

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

/**
   目标:学会线程的创建方式三:实现Callable接口,结合FutureTask完成。
 */
public class ThreadDemo3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 3.创建Callable任务对象
        Callable call = new MyCallable(100);
        // 4.把Callable任务对象交给FutureTask对象作为线程任务对象
        // FutureTask:未来任务对象
        // FutureTask对象的作用1:是Runnable对象(实现了Runnable接口),可以交给Thread作为线程任务对象了
        // FutureTask对象的作用2:可以在线程执行完毕之后通过调用其get方法得到线程执行完成的结果
        FutureTask ft = new FutureTask<>(call);
        // 5.交给线程处理
        Thread t = new Thread(ft);
        // 6.启动线程
        t.start();

        // 如果call方法没有执行完毕,这里的代码会等待,直到线程跑完才提取结果。
        String rs = ft.get();
        System.out.println(rs); // 子线程执行的结果是:5050
    }
}

/**
 * 1.定义一个任务类,实现Callable接口  应该申明线程任务执行完毕后的结果的数据类型
 */
class MyCallable implements Callable {
    private int n;
    public MyCallable(int n){
        this.n = n;
    }

    /**
       2.重写call方法(任务方法)
     */
    @Override
    public String call() throws Exception {
        int sum = 0;
        for(int i = 1;i <= n;i++){
            sum += i;
        }
        return "子线程执行的结果是:" + sum;
    }
}

利用实现Callable接口和FutureTask未来任务对象来实现多线程的优缺点:

  • 优点:任务类只是实现接口,可以继续继承类和实现其他接口,扩展性强。而且可以在线程执行完毕后去获取线程执行的结果。
  • 缺点:编码复杂一点。

Java多线程_第6张图片

 二. Thread的常用方法

Java多线程_第7张图片

Thread常用API说明

  • THread常用方法:获取线程名称getName()、设置线程名称setName()、获取当前线程对象currentThread()。
  • Java多线程_第8张图片

Java多线程_第9张图片

Java多线程_第10张图片

Java多线程_第11张图片

package com.gch.d2_api;

public class MyThread extends Thread {
    /**
     * 调用父类的有参构造器
     * @param name:线程名
     */
    public MyThread(String name){
        // 为当前线程对象设置名称,送给父类的有参数构造器初始化名称
        super(name);
    }
    
    public MyThread() {
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "输出:" + i);
        }
    }
}
package com.gch.d2_api;

/**
   目标:线程的API
 */
public class ThreadDemo01 {
    // main方法是由主线程负责调度的
    public static void main(String[] args) {
        Thread t1 = new MyThread("Thread-0");
//        t1.setName("线程1");
        t1.start();
        System.out.println(t1.getName());

        Thread t2 = new MyThread("Thread-1");
//        t2.setName("线程2");
        t2.start();
        System.out.println(t2.getName());

        // 哪个线程执行它,它就得到哪个线程对象(当前线程对象)
        // 主线程的默认名称就叫main
        Thread m = Thread.currentThread();
//        m.setName("最牛的线程");
        System.out.println(m.getName()); // main


        for (int i = 0; i < 5; i++) {
            System.out.println(m.getName()+ "输出:" + i);
        }
    }
}

package com.gch.d2_api;

/**
   目标:线程的API
 */
public class ThreadDemo02 {
    // main方法是由主线程负责调度的
    public static void main(String[] args) throws Exception {
        for (int i = 1; i <= 5; i++) {
            System.out.println("主线程输出:" + i);
            if(i == 3){
                // 让当前线程进入休眠状态  单位:毫秒
                // 段子:项目经理让我加上这行代码,如果用户愿意交钱,我就注释掉
                Thread.sleep(3000);
            }
        }
    }
}

你可能感兴趣的:(Java,java,jvm,开发语言)