多线程的理解及应用

多线程相关的三组概念

程序和进程

程序可以用来运行的文件。(可执行文件)

​ 该文件可以运行,但是还没有运行。还没有运行的可执行文件(程序)

进程:可以用来运行的文件。(可执行文件)

​ 该文件可以运行,但是还没有运行。还没有运行的可执行文件(程序)

进程和线程

进程:正在执行的程序。

线程:进程中的一条独立的执行单元。

开发部门:前端 后端 测试 UI

任务:完成一个项目的研发 【进程】

  • UI 【线程】
  • 前端 【线程】
  • 后端 【线程】
  • 测试 【线程】

进程和线程的关系

  • 进程是由线程组成的
  • 一个进程中最少需要一条线程
  • 进程之间的资源不共享,每条进程互不影响
  • 一个进程中的每一条线程资源共享

并行和并发

并行:在某一个时刻,多个进程(线程)同时在执行。

进程或者线程想要执行,需要cpu,在某一个特定的时刻,一个cpu只能执行一个任务

  • 如果计算机想要实现3个进程的并行,需要什么条件?

​ 需要3个cpu

  • 如果需要同时去做四件事情: 吃饭 睡觉 打游戏 学习

​ 至少需要四个人

并发:在某一个时刻,多个进程(线程)同时启动,同时启动的多个任务会被cpu交替执行。

在某一个时刻,将四个任务分发给同一个人去做:吃饭 睡觉 打游戏 学习

一个人可以在某个时间段,去完成四件事情

计算机使用并发原理,提升了计算机的效率还是降低了?

对于系统而言是提升了运行的效率

多线程的第一种继承方式

方式:继承方式

步骤

  • 自定义一个线程类型
  • 继承Thread类型
  • 重写父类中的run方法表示一个具体的任务
  • 在主方法中创建一个线程类对象
  • 启动线程

注意事项

  • 新线程的启动借助主线程
  • 一旦新线程启动之后,两条线程之间并发原理执行,一个cpu在两条线程之间切换执行,去谁那就执行谁。
  • 在线程中制定任务,在run方法中制定
  • 启动新线程,使用start方法启动
  • 一旦新线程启动之后,两条线程之间互不影响,主线程结束,新线程继续执行,线程结束主线程也不受影响。

在这里插入图片描述

代码

public class Thread_class {
    public static void main(String[] args) {
        //创建一条线程对象
        A a = new A();
        //启动新线程
       a.start();
       // a.run();
        //主线程执行的
        for (int i = 0; i < 100; i++) {
            System.out.println("主线程------");
        }
    }
}
//定义类型继承Thread类型--该线程变成新线程
class  A extends  Thread{
    //重写父类run()方法
    //新线程所执行的任务
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("-----新线程");
        }
    }
}
//交替执行

多线程的第二种继承方式

方式:实现方式

步骤

  • 定义一个类型,表示一个任务类
  • 实现Runnable接口
  • 重写接口中的run方法表示需要执行的任务
  • 在方法中创建一个任务类对象,表示一个具体任务
  • 创建一个Thread类的线程对象,将任务对象提交给线程对象
  • 启动线程

代码

public class Thread_class02 {
    public static void main(String[] args) {
        //new一个任务类B 的对象
        B a = new B();
        //new一个线程对象 把任务对象提交给该线程
        Thread th = new Thread(a);
        //线程对象启动
        th.start();//---与主线程并发执行(交替)
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程------");
        }

    }
}
//定义一个类实现Runnable接口---接口本身不是线程,用来定义任务类的接口
class B implements Runnable{
    //重写run方法,表示该线程需要执行的任务
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("------新线程");
        }
    }
}

继承方式和实现方式的区别

复杂程序

继承方式更加简单:只需要定义一个线程类,创建线程对象启动

实现方式更加复杂:不仅需要定义一个任务类,需要创建任务对象,还需要创建一个线程对象,最后将任务提交给线程,再启动。

灵活性方面

继承方式灵活性差:将执行的任务定义再线程类中,以后该线程类的对象就只能执行定义好的任务,不能执行其他任务。

实现方式灵活性强:因为将线程和任务分开定义,创建的任务对象可以提交给不同的线程去执行;创建的线程对象也可以接收不同的任务执行。

类型的扩展性方面

继承方式不能再继承其他父类,扩展性差

实现方式可以继续继承其他父类,扩展性强

多线程的第三种继承方式

方式:实现Callable接口

步骤

  • 定义一个类实现Callable接口
  • 在类中重写call()方法
  • 创建自定义类的对象
  • 创建Future的实现类FutureTask对象,把自定义类对象作为构造方法的参数传递
  • 创建Thread类的对象,把FutureTask对象作为构造方法的参数传递
  • 启动线程
  • 再调用get方法,就可以获取线程结束之后的结果。

代码

public class Thread_class03 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建一个任务对象
        C c = new C();
        //将任务对象进行包装,用FutureTask类型接收任务对象
        FutureTask<String> ft = new FutureTask<String>(c);
        //创建线程对象
        Thread thread = new Thread(ft);
        //启动线程
        thread.start();
        //用FutureTask对象接收返回值
        String s = ft.get();
        System.out.println(s);
    }
}
//定义一个类型,实现任务接口
class C implements Callable{
    //重写接口中的call()方法,表示新线程所需要执行的代码
    //注意: 改方法是具有返回值的,方法结束后给定一个返回值
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("----------新线程执行了");
        }
        return "线程结束";
    }
}

匿名内部类实现多线程

public static void main(String[] args) throws ExecutionException, InterruptedException {
    //匿名直接创建线程类
    Thread thread1=new Thread(){
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("线程2执行"+i+"次");
            }
        }
    };

    //匿名创建接口Runnable

    Runnable r = new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("线程1执行"+i+"次");
            }
        }
    };
    Thread thread2 = new Thread(r);


    //匿名创建Callable<>接口 --可返回值
    Callable<String> c = new Callable<String>() {
        @Override
        public String call() throws Exception {
            for (int i = 0; i < 100; i++) {
                System.out.println("线程3执行"+i+"次");
            }
            return "线程3执行完了------";
        }
    };
    FutureTask<String> ft = new FutureTask<>(c);
    Thread thread3 = new Thread(ft);
    thread1.start();
    thread2.start();
    thread3.start();
    System.out.println(ft.get());
}

Thread类

获取线程名称

getName() :获取线程名称

特点:如果定义一个线程对象之后,没有给该线程对象起名,会有一个默认名称:

Thread-x:x从0开始依次递增

Thread thread = new Thread(ft);
thread.getName();

设置线程名称

  • setName(String name) :给线程对象设置名称
Thread thread = new Thread(ft);
thread.setName("001");
  • Thread(String name) :通过构造给线程命名
Thread th = new Thread("001");
  • Thread(Runnable target, String name) :在接收一个任务的同时给线程命名
Thread th2 = new Thread(r,"001");

注意

  • 如果使用匿名内部类创建子类的线程对象,可以直接通过构造命名

  • 如果单独定义子类,那么子类中需要自己定义有参构造给name命名

获取线程对象

方法currentThread()

注意

  • 该方法是静态方法,使用类名直接调用

  • 哪一个线程执行该方法,返回的是该线程的对象

public class Thread_class02 {
    public static void main(String[] args) {
        //new一个任务类B 的对象
        B a = new B();
        //new一个线程对象 把任务对象提交给该线程
        Thread th1 = new Thread(a,"001");
        Thread th2 = new Thread(a,"002");
        th1.start();
        th2.start();
    }
}
class B implements Runnable{
    @Override
    public void run() {
        System.out.println("任务");
        //获取线程对象
        Thread t =Thread.currentThread();
        System.out.println("执行该任务的线程是"+t.getName());
    }
}

案例:

1、获取main线程的线程名称

public static void main(String[] args) {
    System.out.println(Thread.currentThread().getName());
}

2、获取垃圾回收线程名称

public class GetFinalize {
    public static void main(String[] args) {
        new D();
        //强制回收指令
        System.gc();
    }
}
class D{
    @Override
    protected void finalize() throws Throwable {
        System.out.println("垃圾要被回收了");
        Thread t =Thread.currentThread();
        System.out.println("回收垃圾的线程的名字是:"+t.getName());
    }
}

线程休眠

  • Thread.sleep(long time):让执行这段代码的线程休息一会

注意

  • 该方法是静态方法,参数是一个毫秒值,表示线程休眠的时间
  • 哪一个线程执行该方法,就让哪一个线程休息
  • 如果在一个普通方法中使用sleep,可以选择声明异常也可以选择处理异常
  • 如果在一个重写的方法中使用sleep,只能处理异常不能声明异常

​ 父类或者接口中的方法没有异常的声明,在重写时不能声明

  • 将来调用sleep方法的线程,不一定是执行该方法的线程
  • 使用interrupt强制唤醒某一个线程,一旦线程的休眠被唤醒之后,就会抛出线程 中断异常。
public class Xiumian_Huanxing {
    public static void main(String[] args) throws InterruptedException {
        E e = new E();
        e.start();
        System.out.println(e.getName()+"预计在3秒中后唤醒"+e.getName());
        //此线程休眠3秒
        Thread.sleep(1000*3);
        //唤醒001线程
        e.interrupt();
    }
}
class E extends Thread{
    @Override
    public void run() {
        Thread t = new Thread();
        t.setName("001线程");
        System.out.println(t.getName()+"要开始执行任务了!,休眠1分钟!");
        try {
            Thread.sleep(1000*60);
        } catch (InterruptedException e) {
            System.out.println("休眠被打断了");
        }
        System.out.println("休眠结束了.");
    }
}

守护线程(后台线程)

概念:保护其他线程能够正常运行的线程就是守护线程。

方法

setDaemon(boolean on) :将当前线程对象设置为指定线程,参数为true为守护线程否则为非守护线程。

isDaemon():判断当前对象是否是一个守护线程

t1.setDaemon(true);
System.out.println(t1.isDaemon());

特点

  • 自己创建的线程对象,都默认为非守护线程(用户线程)
  • 如果被守护的线程结束了,那么守护线程也跟着结束了
  • 如果程序中有多条用户线程及一条守护线程,只要用户线程还在运行,守护线程也会继续运行,并不会随着其中一条用户线程的结束而结束。
public static void main(String[] args) {

    Thread t1 = new Thread(){
        @Override
        public void run() {
            while (true){
                System.out.println("------------------------------------守护任务中");
            }
        }
    };
    //将 t1 线程 转为守护线程
    t1.setDaemon(true);

    t1.start();
    Runnable r = new Runnable() {
        @Override
        public void run() {
            while (true){
                System.out.println("我是保护对象!我还没挂");
            }
        }
    };
    Thread t2 = new Thread(r);
    t2.start();

    for (int i = 0; i < 200; i++) {
        System.out.println("我也是保护对象,我是主线程,我会挂!!!!");
    }

}

设置线程的优先级

线程的优先级

定义的每一条线程都有一个默认的优先级,默认的优先级都是相同的5,如果线程的优 先级都相同,那么每次被cpu执行的几率都是相同。

设置优先级的方法setPriority(int newPriority) :设置线程优先级

  • 10:最高

  • 1:最低

静态常量

static int MAX_PRIORITY 10线程可以拥有的最大优先级。

static int MIN_PRIORITY 1线程可以拥有的最小优先级。

static int NORM_PRIORITY 5分配给线程的默认优先级。

public static void main(String[] args) {
    Runnable r =  new Runnable(){
        @Override
        public void run() {
            System.out.println("方法");
        }
    };
    Thread t = new Thread(r,"001");
    Thread t1 = new Thread(r,"002");
    //将线程1设置为最高优先级,被执行的几率最高
    t.setPriority(Thread.MAX_PRIORITY);
    //将线程2设置为最低优先级,被执行的的几率最低
    t1.setPriority(Thread.MIN_PRIORITY);
    t.start();
    t1.start();
}

线程安全问题

问题的概述:

当多个线程执行同一个任务时,多个线程再使用并发原理执行同一个任务,就容易出现线程安全问题。

同步代码块

同步代码块:使用同步代码块括起来的代码,可以保证代码的完整性和一致性、原子性

格式:

synchronized(锁对象){
  需要保证完整性的代码。
}

原理分析

如果代码使用同步代码块同步了。

线程想要执行同步的代码,需要先获取锁对象,获取到锁对象之后,才可以执行代码, 如果在执行的过程中,代码没有执行完,锁对象不会被释放。如果在执行的时候,cpu 执行其他线程了,其他线程想要执行代码,也需要获取锁对象,这时不能获取到锁对象 (对象被线程1拿着呢,没有执行不会释放),cpu就无法执行线程2代码,只能在获 取线程1继续执行,执行完之后,才会释放。释放锁之后,两个线程开始互相争抢,谁 先抢到,谁就先执行代码。

如果想要保证两段都互不影响,应该让两个线程使用同一个锁对象

注意:保证代码同步,使用同一个锁,类.class是最保险的

public static void main(String[] args) {
        Runnable r =new Runnable(){
            int money = 0;
            @Override
            public  void run() {
            //同步代码块
            synchronized (Synch.class){
                for (int i = 1;i  <=10000;i++){
                    money = money +1;
                }
                System.out.println(Thread.currentThread().getName()+"存了"+money);
            }
            }
        };

        Thread t1 = new Thread(r,"001");
        Thread t2 = new Thread(r,"002");
        t1.start();
        t2.start();
    }

同步方法

概 念:如果一个方法中的所有内容都需要保持同步,可以使用同步方法来代替同步代码 块以达到简化代码的操作。

格式

修饰符  synchronized 返回值类型 方法名称(){

}     

注意

如果方法是一个非静态方法,那么锁对象默认为 this

如果方法是静态方法,锁对象默认为类.class

public static void main(String[] args) {
    Runnable r =new Runnable(){
        int money = 0;
        @Override
        //同步方法
        public synchronized void run() {

            for (int i = 1;i  <=10000;i++){
                money = money +1;
            }
            System.out.println(Thread.currentThread().getName()+"存了"+money);
        }
    };

    Thread t1 = new Thread(r,"001");
    Thread t2 = new Thread(r,"002");
    t1.start();
    t2.start();
}

称(){

}


**注意**:

如果方法是一个非静态方法,那么锁对象默认为 this

如果方法是静态方法,锁对象默认为类.class

```java
public static void main(String[] args) {
    Runnable r =new Runnable(){
        int money = 0;
        @Override
        //同步方法
        public synchronized void run() {

            for (int i = 1;i  <=10000;i++){
                money = money +1;
            }
            System.out.println(Thread.currentThread().getName()+"存了"+money);
        }
    };

    Thread t1 = new Thread(r,"001");
    Thread t2 = new Thread(r,"002");
    t1.start();
    t2.start();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

皮卡丘不断更

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值