阶段一/常用工具类/Java多线程

目录

什么是线程

创建线程

Thread类和Runnable接口介绍

Thread

Runnable接口

Callable接口

线程的状态

生命周期

线程同步


什么是线程

  • 进程是指可执行程序并存放在计算机存储器的一个指令序列,它是一个动态执行的过程。
  • 线程是比进程还要小的运行单位,一个进程包含多个线程,它被包含在进程之中,是进程中的实际运作单位。
  • 线程可以看作一个子程序
  • CPU使用时间片轮转的工作方式,可以让多个程序轮流占用CPU,达到同时运行的效果

创建线程

在 Java 中,创建线程有以下 3 种方式:

  1. 继承 Thread 类,重写 run() 方法,该方法代表线程要执行的任务;
  2. 实现 Runnable 接口,实现 run() 方法,该方法代表线程要执行的任务;
  3. 实现 Callable 接口,实现 call() 方法,call() 方法作为线程的执行体,具有返回值,并且可以对异常进行声明和抛出。

Thread类和Runnable接口介绍

Thread

  • 定义 Thread 类的子类,重写 run() 方法,run() 方法的方法体就代表了线程要完成的任务;
  • Thread类实现了Runnable接口
  • 创建 Thread 子类的实例,即创建线程对象;
  • 调用线程对象的 start 方法来启动该线程,一个线程只能启动一次,执行线程时会调用run方法
  • 线程获得CPU的使用权是随机的,不同线程,执行顺序是随机的。
// Thread类实现了Runnable接口
class MyThread extends Thread{
    public MyThread(String name){
        super(name);
    }
    @Override
    public void run() {
        for(int i = 1; i <= 5; i++){
            System.out.println(getName() + "该线程正在执行第" + i + "次");
        }
    }
}
/**
 * 线程获得CPU的使用权是随机的,所以下面代码的运行会有很多种情况
 */
public class ThreadTest {
    public static void main(String[] args) {
        MyThread mt1 = new MyThread("线程1");
        MyThread mt2 = new MyThread("线程2");
        mt1.start();
        mt2.start();
    }
}

输出:线程获得CPU的使用权是随机的,所以运行会有很多种情况

线程2该线程正在执行第1次
线程1该线程正在执行第1次
线程2该线程正在执行第2次
线程1该线程正在执行第2次
线程1该线程正在执行第3次
线程1该线程正在执行第4次
线程2该线程正在执行第3次
线程1该线程正在执行第5次
线程2该线程正在执行第4次
线程2该线程正在执行第5次

Runnable接口

通过实现 Runnable 接口的方案来创建线程,要优于继承 Thread 类的方案,主要有以下原因:

  1. Java 不支持多继承,所有的类都只允许继承一个父类,但可以实现多个接口。如果继承了 Thread 类就无法继承其它类,这不利于扩展;
  2. 继承 Thread 类通常只重写 run() 方法,其他方法一般不会重写。继承整个 Thread 类成本过高,开销过大。

用Runnable接口创建线程的主要工作如下

  1. 声明实现Runnable接口的类
  2. 在实现类内实现run()方法
  3. 创建实现类的对象
  4. 通过实现类的对象创建线程类的对象
  5. 调用start()方法启动线程
class PrintRunnable implements Runnable{
    @Override
    public void run() {
        // 获取当前运行的线程的线程名,可以通过Thread的静态方法currentThread
        int i = 1;
        while(i <= 5){
            System.out.println(Thread.currentThread().getName() + "正在运行第" + (i++) + "次");
        }
    }
}

public class Test {
    public static void main(String[] args){
        // 创建 Runnable 实现类的实例,
        PrintRunnable pr1 = new PrintRunnable();
        // 用该实例并以此实例作为 Thread 的 target 来创建 Thread 对象
        Thread t1 = new Thread(pr1); // 启动线程只能通过Thread及其子类去启动
        PrintRunnable pr2 = new PrintRunnable();
        Thread t2 = new Thread(pr1);
        // 通过Thread的对象调用start方法启动线程
        t1.start();
        t2.start(); //当多线程访问同一资源,会发生争抢,造成线程安全问题。
    }
}

输出:与下例不同,这边的i是方法的局部变量,所以每个线程会精确的运行5次,只不过打印顺序会是随机的。

Thread-1正在运行第1次
Thread-0正在运行第1次
Thread-1正在运行第2次
Thread-0正在运行第2次
Thread-1正在运行第3次
Thread-1正在运行第4次
Thread-1正在运行第5次
Thread-0正在运行第3次
Thread-0正在运行第4次
Thread-0正在运行第5次
class PrintRunnable implements Runnable{
    int i = 1;
    @Override
    public void run() {
        // 获取当前运行的线程的线程名,可以通过Thread的静态方法currentThread
        while(i <= 5){
            System.out.println(Thread.currentThread().getName() + "正在运行第" + (i++) + "次");
        }
    }
}

public class Test {
    public static void main(String[] args){
        // 创建 Runnable 实现类的实例,
        PrintRunnable pr1 = new PrintRunnable();
        // 用该实例并以此实例作为 Thread 的 target 来创建 Thread 对象
        Thread t1 = new Thread(pr1); // 启动线程只能通过Thread及其子类去启动
        Thread t2 = new Thread(pr1);
        // 通过Thread的对象调用start方法启动线程
        t1.start();
        t2.start();
    }
}

 输出结果:貌似有点线程不安全,

因为一个线程只能启动一次,通过Thread实现线程时,线程和线程所要执行的任务是捆绑在一起的。也就使得一个任务只能启动一个线程,不同的线程执行的任务是不相同的,所以没有必要,也不能让两个线程共享彼此任务中的资源。

    一个任务可以启动多个线程​,通过Runnable方式实现的线程,实际是开辟一个线程,将任务传递进去,由此线程执行。可以实例化多个 Thread对象,将同一任务传递进去,也就是一个任务可以启动多个线程来执行它。这些线程执行的是同一个任务,所以他们的资源是共享,在本例中他们共享数据i,因为i是pr1的成员属性,t1和t2的运行均会改变pr1的成员属性i。

    ​可以理解为,Thread实现线程是不同的对象来运行run()方法,不同对象,自然无法共享变量。而Runnable方式实现的线程是同一个对象运行run()方法,所以可以实现共享变量。

Thread-0正在运行第1次
Thread-1正在运行第1次
Thread-0正在运行第2次
Thread-0正在运行第3次
Thread-1正在运行第5次
Thread-0正在运行第4次

Callable接口

使用Callable接口创建多线程的步骤:

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

线程的状态

  • 新建(New)
  • 可运行(Runnable)
  • 正在运行状态(Running)
  • 阻塞(Blocked)
  • 终止(Dead)

生命周期

 

线程同步

1、多线程运行问题

  • 各个线程是通过竞争CPU时间而获得运行机会的
  • 各线程什么时候得到CPU时间,占用多久,是不可预测的
  • 一个正在运行着的线程在什么地方被暂停是不确定的

2、线程同步关键字synchronized,使用方式:

  • 修饰一个代码块,被修饰的代码块称为同步代码块,作用范围是大括号{}括起来的代码;
  • 修饰一个方法,被修饰的方法称为同步方法,其作用范围是整个方法;
  • 修饰一个静态方法,作用范围是整个静态方法;
  • 修饰一个类,作用范围是synchronized后面括号括起来的部分。

3、synchronized关键字作用

  • synchronized关键字保证共享对象在同一时刻只能被一个线程访问
  • 当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后,其他线程才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,其他线程才能执行并锁定该对象。 
  • 但是由于线程的执行顺序是随机的,所以两个线程的执行顺序也是随机的

4、线程间通信

  • wait()方法:中断方法的执行,使线程等待
  • notify()方法:唤醒处于等待的某一个线程,使其结束等待
  • notifyAll()方法:唤醒所有处于等待的线程,使它们结束等待
    • notifyAll方法是唤醒所有线程,本线程不可能把自己唤醒,如果能执行到唤醒这一步就证明自己是在运行的状态。
    • notify()方法是随机唤醒一个线程,notifyAll()方法是唤醒所有线程,一般用notifyAll()
  • wait通常和notifyAll配合使用,只使用wait不唤醒,有可能造成死锁

参考:慕课网-Java工程师

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值