JAVA多线程——(一)如何实现多线程

一、背景介绍

  最近开始了人生的新征程,开始转java开发了,之前也看过一些关于JAVA多线程的一些资料和文档,但是没有系统的进行过整理和总结。借此机会对java多线程以及常见问题进行系统的整理和总结。在这一小节中,主要目的是整理一下多线程的实现方式。在此之前,首先复习一下线程的生命周期,有利于后期对于并发场景的理解

二、线程的生命周期

  在介绍线程的生命周期前,先简要的总结一下什么是线程、为什么要使用线程。

  1、什么是线程?

    在计算机中,一个应用程序是一个进程,一个进程可以并发多个线程,线程是CPU的最小调度单位、

2、为什么要使用线程

    • 将串行执行转换为并行执行
    • 提高系统并发能力
    • 提高系统吞吐率,充分利用CPU资源  

  

  在介绍完什么是线程后,接下来介绍一下线程的生命周期。如下图所示,线程包含有5个状态:新建状态、就绪状态、运行状态、死亡状态、阻塞状态。

其中分别为:

  • 新建状态:

    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:

    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:

    如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:

    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

    死亡状态:
    • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

    • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

    • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

 

一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

  

三、多线程的实现方式

Java 提供了三种创建线程的方法:

    • 通过实现 Runnable 接口;
    • 通过继承 Thread 类本身;
    • 通过 Callable 和 Future 创建线程。

1、通过继承Thread类本身

  继承thread类,并重写run方法,调用start()方法开始执行。Thread内部也实现了Runnable接口

 

 package thread;

public class MyThreadDemo1 extends Thread{
    private String name;
    private Integer age;

    public MyThreadDemo1(String name, Integer age){
        this.name = name;
        this.age = age;
    }

    public void run(){
        try {
            for(int i = 4; i > 0; i--) {
                System.out.println("Thread: " + name + ", " + i);
                // 让线程睡眠一会
                Thread.sleep(50);
            }
        }catch (InterruptedException e) {
            System.out.println("Thread " +  name + " interrupted.");
        }
        System.out.println("Thread " +  name + " exiting.");
    }


    public static void main(String[] args) {
        MyThreadDemo1 thread1 = new MyThreadDemo1("thread11111", 11);
        thread1.start();

        MyThreadDemo1 thread2 = new MyThreadDemo1("thread222222", 22);
        thread2.start();

    }

}

 

 运行结果:

Thread: thread222222, 4
Thread: thread11111, 4
Thread: thread222222, 3
Thread: thread11111, 3
Thread: thread222222, 2
Thread: thread11111, 2
Thread: thread222222, 1
Thread: thread11111, 1
Thread thread222222 exiting.
Thread thread11111 exiting.

 

 

2、通过实现 Runnable 接口

  由于有的类已经继承自某一个类,同事java中限制了只能单继承,所以可以通过实现Runnable接口的方式实现多线程。

  通过重写run方法,.创建线程对象,调用start()方法启动线程

  

package thread;

public class MyThreadDemo2 implements Runnable{
    private String name;
    private Integer age;

    public MyThreadDemo2(String name, Integer age){
        this.name = name;
        this.age = age;
    }

    public void run(){
        try {
            for(int i = 4; i > 0; i--) {
                System.out.println("Thread: " + name + ", " + i);
                // 让线程睡眠一会
                Thread.sleep(50);
            }
        }catch (InterruptedException e) {
            System.out.println("Thread " +  name + " interrupted.");
        }
        System.out.println("Thread " +  name + " exiting.");
    }


    public static void main(String[] args) {
        MyThreadDemo2 mythread1 = new MyThreadDemo2("thread11111", 11);
        Thread thread1 = new Thread(mythread1);

        MyThreadDemo2 mythread2 = new MyThreadDemo2("thread222222", 22);
        Thread thread2 = new Thread(mythread1);
        thread1.start();
        thread2.start();

    }

}

 

   运行结果:

Thread: thread11111, 4
Thread: thread11111, 4
Thread: thread11111, 3
Thread: thread11111, 3
Thread: thread11111, 2
Thread: thread11111, 2
Thread: thread11111, 1
Thread: thread11111, 1
Thread thread11111 exiting.
Thread thread11111 exiting.

 

 

3、通过 Callable 和 Future 创建线程

  Callable箱规Runnable接口,可以获取线程的返回值。实现方式为:

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

  重点关注项:实现后面的Callable<Long>,其中Long为线程返回值得类型,需和创建FutureTask<Long> future1 = new FutureTask<>(mythread1)的类型,以及call() 函数返回值的类型保持一致

 

 

 

package thread;

import org.apache.commons.logging.Log;

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

public class MyThreadDemo3 implements Callable<Long> {
    private String name;
    private Integer age;

    public MyThreadDemo3(String name, Integer age){
        this.name = name;
        this.age = age;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThreadDemo3 mythread1 = new MyThreadDemo3("thread11111", 11);
        FutureTask<Long> future1 = new FutureTask<>(mythread1);
        Thread thread1 = new Thread(future1);

        MyThreadDemo3 mythread2 = new MyThreadDemo3("thread222222", 22);
        FutureTask<Long> future2 = new FutureTask<>(mythread2);

        Thread thread2 = new Thread(future2);
        thread1.start();
        thread2.start();
        System.out.println("thread1 result:" + future1.get() + "\n");
        System.out.println("thread2 result:" + future2.get() + "\n");


    }

    @Override
    public Long call() throws Exception {
        try {
            for(int i = 4; i > 0; i--) {
                System.out.println("Thread: " + name + ", " + i);
                // 让线程睡眠一会
                Thread.sleep(50);
            }
        }catch (InterruptedException e) {
            System.out.println("Thread " +  name + " interrupted.");
        }
        System.out.println("Thread " +  name + " exiting.");
        return System.currentTimeMillis();
    }
}

 

   运行结果:

Thread: thread222222, 4
Thread: thread11111, 4
Thread: thread11111, 3
Thread: thread222222, 3
Thread: thread11111, 2
Thread: thread222222, 2
Thread: thread11111, 1
Thread: thread222222, 1
Thread thread11111 exiting.
thread1 result:1651053928927

Thread thread222222 exiting.
thread2 result:1651053928934

 

四、多线程总结

  多线程可以提升系统的并发能力、CPU利用率,但是多线程也引入了一些新的问题:多线程如何通信? 多线程如何保证数据一致性?多线程是不是回死锁?滥用多线程会有什么影响?(线程上下文频繁切换,消耗系统资源)。这些问题,会在后面的文章中一一介绍。

 

 

 

  


 

posted on 2022-04-27 18:13  1450811640  阅读(234)  评论(0编辑  收藏  举报