Java小结4——多线程

发布于:2022-11-09 ⋅ 阅读:(580) ⋅ 点赞:(0)

目录

方法一 直接继承Thread类

方法二 实现Runnable接口

方式三 实现Callable接口

线程常用方法

线程安全

同步方法

Lock锁


多线程的三种实现方法:

父类为Thread类

Java是通过java.lang.Thread 类来代表线程的。

按照面向对象的思想,Thread类应该提供了实现多线程的方式。

方法一 直接继承Thread类

定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法

创建MyThread类的对象

调用线程对象的start()方法启动线程(启动后还是执行run方法的)

 案例:调start(),而不是run()。告诉CPU,马上要启动一条新线程,如果直接调run()则是普通类的方法的调用,不是多线程。

 小结

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

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

2、把主线程任务放在子线程之前了。

  • 这样主线程一直是先跑完的,相当于是一个单线程的效果了。

1.方式一是如何实现多线程的?

  • 继承Thread类
  • 重写run方法
  • 创建线程对象
  • 调用start()方法启动。

2.优缺点是什么?

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

方法二 实现Runnable接口

创建一个类,实现Runnable接口

 

 方式二优缺点:

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强

缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的。

小结

 匿名内部类写法:也是不可以返回结果

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

他们重写的run方法均不能直接返回结果

不适合需要返回线程执行结果的业务场景。

Android里可以用handler& message方式传递更新数据。

方式三 实现Callable接口

①得到任务对象

1.定义类实现Callable接口,重写call方法,封装要做的事情。

2.用FutureTask把Callable对象封装成线程任务对象

②把线程任务对象交给Thread处理。

③调用Thread的start方法启动线程,执行任务

④线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果。

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


        Callable<String> call2 = new MyCallable(200);
        FutureTask<String> f2 = new FutureTask<>(call2);
        Thread t2 = new Thread(f2);
        t2.start();

        try {
            // 如果f1任务没有执行完毕,这里的代码会等待,直到线程1跑完才提取结果。
            String rs1 = f1.get();
            System.out.println("第一个结果:" + rs1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        try {
            // 如果f2任务没有执行完毕,这里的代码会等待,直到线程2跑完才提取结果。
            String rs2 = f2.get();
            System.out.println("第二个结果:" + rs2);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

/**
    1、定义一个任务类 实现Callable接口  应该申明线程任务执行完毕后的结果的数据类型
 */
class MyCallable implements Callable<String>{
    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;
    }
}

方式三优缺点:

优点:

  • 线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强
  • 可以在线程执行完毕后去获取线程执行的结果

缺点:编程稍麻烦,还需要强制主线程等待

三种方式小结:

线程常用方法

区分线程:设置线程名称(有默认名称Thread-i,从0开始) setName(),获取线程名称getName()

拿到当前线程对象:Thread t = Thread.currentThread();

  • 此方法是Thread类的静态方法,可以直接使用Thread类调用。
  • 这个方法是在哪个线程执行中调用的,就会得到哪个线程对象。

 还可以通过构造器取名字

 写一个子类,设置有参构造器

 线程休眠方法

public static void sleep(long time)

让当前线程休眠指定的时间后再继续执行,单位为毫秒。

用法:

故意让项目卡,用户交钱再优化:注释掉(狗头)

小结

Thread类的一些构造器

实际开发几乎不会为线程专门取名字

线程安全

1.线程安全问题发生的原因是什么?

多个线程同时访问同一个共享资源且存在修改该资源的操作。

代码规范:设置了有参构造器,一般也要设置个无参构造器。

想在创建线程的时候顺便给线程取个名字,要在子类构造器里调用父类的构造器,super(name);

 

解决方法:线程同步

 

加锁把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。

 

这个锁对象可以是一个常量字符串,自带static final属性。

注意:

锁对象用任意唯一的对象好不好呢?

不好,会影响其他无关线程的执行

锁对象的规范要求:

  • 规范上:建议使用共享资源作为锁对象。
  • 对于实例方法建议使用this作为锁对象。
  • 对于静态方法建议使用字节码(类名.class)对象作为锁对象。

面向对象思维要在实战中强化,逐步深入了解并使用。

 

同步方法

 二者比较:

从性能上分析,同步代码块范围小一点,同步方法锁的范围更大,所以同步代码块的性能更好一点。

但是实际中同步方法使用更方便,可读性更好。

小结

Lock锁

功能更强大,有抢占锁,等待时间自动释放锁,,,,,

 在创建账户对象时,同时创建锁实例对象。

转存失败重新上传取消

使用非常方便:

 使用中, 一般放在try,catch后面的finally里,保证成功解锁

本文含有隐藏内容,请 开通VIP 后查看