多线程01

本次课程内容

  • 什么是多线程 为什么要使用多线程
  • 多线程的两种实现方式
  • 线程的常用方式
  • 线程安全问题产生的原因
  • 使用线程解决安全问题
  • 线程的死锁

详细内容

什么是多线程 为什么使用多线程

什么情况下需要使用多线程 :

能否提高资源的利用率

多线程在java 底层怎么实现的

多线程在Java虚拟机底层是靠时间片的快速轮询来实现的
就是在jvm虚拟机底层 实际上你可以认为只有一个 进程在工作。只不过时间片切换的比较快,给你造成一种假象
好像是多个线程同时工作的

总结:

线程是进程的进一步划分,线程是为了解决 进程开销大,内存不能共享的问题
线程是程序级别的 要不要 使用多线程 判别的标准:能否提供资源的利用率
多线程是怎么实现的? 通过时间片快速轮询 来实现,实际上在底层 (jvm底层)不可能多个线程同时工作
cpu核心 进程 线程 (线程 跟 cpu 核心 没有直接的关联 逻辑上的进程)

多线程创建的两种方式

使用继承Thread类的方式

线程 :Thread

举例 :模拟三个窗口卖票 每个窗口买自己的票,这三个窗口同时卖

package com.aaa.thread;

/**
 * @Author Lst
 * @Date 2020/12/29 13:08
 * @Version 1.0
 * 多线程实现的第一种方式
 * 继承 Thread类
 */
public class Test01 extends Thread {
    // 窗口名称
    private String name;
    // 每个线程 模拟一个窗口,每个窗口都有票的资源
    private Integer tickets = 3;

    public Test01(String name) {
        this.name = name;
    }

    /**
     * 执行线程的方法
     */
    @Override
    public void run() {
        for (Integer i = 0; i < tickets; i++) {
            System.out.println(name+"卖出了第 "+(i+1)+" 张票");
        }
        super.run();
    }

    public static void main(String[] args) {
        // 创建一个窗口的对象
        Test01 test01 = new Test01("窗口1");
        Test01 test02 = new Test01("窗口2");
        Test01 test03 = new Test01("窗口3");
        // 启动上面的三个线程, 开始卖票
        // 启动线程只能而且只有一个方法就是线程类的start方法
        test01.start();
        test02.start();
        test03.start();

    }

}

使用实现Runnable接口的方式

举例 :模拟三个窗口卖票 每个窗口买自己的票,这三个窗口同时卖

package com.aaa.thread;

/**
 * @Author Lst
 * @Date 2020/12/29 13:50
 * @Version 1.0
 * 多线程创建的第二种方式
 * 使用 Runnable接口
 */
public class Test02 implements Runnable {

    // 窗口名称
    private String name;
    // 每个线程 模拟一个窗口,每个窗口都有票的资源
    private Integer tickets = 3;

    public Test02(String name) {
        this.name = name;
    }

    public void run() {
        for (Integer i = 0; i < tickets; i++) {
            System.out.println(name+"卖出了第 "+(i+1)+" 张票");
        }
    }

    public static void main(String[] args) {
        // 创建多个窗口 需要先创建Runnable接口的实现类对象
        Test02 test01 = new Test02("窗口1");
        Thread thread01 = new Thread(test01);
        Thread thread02 = new Thread(new Test02("窗口2"));
        Thread thread03 = new Thread(new Test02("窗口3"));
        // 启用线程
        thread01.start();
        thread02.start();
        thread03.start();



    }

}

两种实现方式 优缺点 : 第一种方式继承Thread类的方式使用起来跟简单,但是实际开发中一般还是推荐使用第二种方式,实现Runnable接口的方式 原因

  1. java 是单继承,一个类如果继承了 Thread类就没办法在继承其他类的
  2. 使用Runnable接口的方式可以方便的实现多个线程操作同一个资源。

举例:刚才需求都是每个窗口卖自己的票,但是实际生活中,一般都是多个窗口买同一批票。

package com.aaa.thread;

/**
 * @Author Lst
 * @Date 2020/12/29 14:09
 * @Version 1.0
 * 多个线程共享同一资源
 * 多个窗口卖同一批票
 */
public class Test03 implements Runnable {
    // 票的资源
    private Integer tickets = 3;


    public void run() {
        for (Integer i = 0; i < 3; i++) {
            if (tickets > 0) {
                // 获取当前正在执行的线程名称
                Thread thread = Thread.currentThread();
                // 在获取线程名称
                String name = thread.getName();
                System.out.println(name+" 卖出了第"+(tickets)+"张票");
                tickets--;
            }
        }
    }

    public static void main(String[] args) {
        // 先创建票的资源
        Test03 test03 = new Test03();
        // 创建多个窗口(线程)让这多个窗口都操作test03这一个资源
        Thread thread01 = new Thread(test03,"窗口1");
        Thread thread02 = new Thread(test03,"窗口2");
        Thread thread03 = new Thread(test03,"窗口3");
        // 启动三个线程
        thread01.start();
        thread02.start();
        thread03.start();
    }
}

现在已经实现了多个窗口卖同一票,但是有可能多个窗口买同一张票的问题

线程的常用的方法

方法名说明
Thread.currentThread()获取当前正在运行的线程
getName()获取线程的名称
Thread.sleep(min)让当前正在运行的线程进入休眠状态,参数为休眠时间,单位是毫秒
setPriority(int)设置线程运行的优先级,优先级是一个整数 最小值 1,最大值是 10,默认值是 5优先级高的线程 有大的几率可能先执行但是不保证。设置线程优先级必须在线程启动之前设置才有效
yield()线程礼让的方法,对一个线程调用 yield()方法,当前获取时间片的线程,会放弃执行权,然后所有的线程再重新竞争一次时间片,谁获取到时间片谁执行。但是注意放弃时间片的线程,有可能再次抢到时间片
wait()Object类中提供的线程方法,让当前线程处于等待状态
分析线程安全问题产生的原因
解决线程安全问题,还可以使用java并发编程中 ReentrantLock类,
// 声明可重入锁对象
private ReentrantLock reentrantlock = new ReentrantLock();
// 为代码加锁
reentrantLock.lock();
// 为代码解锁
reentrantLock.unlock();
// 如果出异常的  不解锁 就死锁了 为了能让出现异常也能 最终 执行 这个解锁代码 使用 try{}catch(){}finally{}

死锁

解决线程同步问题的时候,如果出现了嵌套的不同步代码块,有可能会出现死锁的问题。

生活中的死锁的例子 : 爸爸对儿子说,如果给我成绩单 ,我就给你玩具。儿子对爸爸说,如果给我玩具,我就给你成绩单。

Father

package com.aaa.deadlock;

/**
 * @Author Lst
 * @Date 2021/1/1 14:30
 * @Version 1.0
 * 父亲的类
 */
public class Father {
    /**
     * 爸爸发言了
     */
    public void say() {
        System.out.println("给我成绩单,就给你玩具");
    }


    /**
     * 爸爸得到成绩单的方法
     */
    public void get() {
        System.out.println("爸爸得到了成绩单");
    }
}

Son

package com.aaa.deadlock;

/**
 * @Author Lst
 * @Date 2021/1/1 14:28
 * @Version 1.0
 * 创建儿子的类
 */
public class Son {
    /**
     * 儿子发言了
     */
    public void say() {
        System.out.println("给我玩具,就给你成绩单");
    }


    /**
     * 儿子得到玩具的方法
     */
    public void get() {
        System.out.println("儿子得到了玩具");
    }
}

TestDead

package com.aaa.deadlock;

/**
 * @Author Lst
 * @Date 2021/1/1 14:32
 * @Version 1.0
 * 测试死锁的代码
 */
public class TestDead implements Runnable {
    private Son son;
    private Father father;
    private boolean flag;

    public TestDead(Son son, Father father, boolean flag) {
        this.son = son;
        this.father = father;
        this.flag = flag;
    }

    public void run() {
        if (flag) {
            synchronized (father) {
                son.say();
                synchronized (son) {
                    son.get();
                }
            }
        }else {
            synchronized (son) {
                father.say();
                synchronized (father) {
                    father.get();
                }
            }
        }
    }

    public static void main(String[] args) {
        // 创建儿子和父亲的对象
        Father father = new Father();
        Son son = new Son();
        // 创建测试死锁的对象
        TestDead dead01 = new TestDead(son,father,true);
        TestDead dead02 = new TestDead(son,father,false);
        Thread thread01 = new Thread(dead01);
        Thread thread02 = new Thread(dead02);
        // 启动上面的两个线程
        thread01.start();
        thread02.start();

    }
}

线程的声明周期

线程的声明周期 五中状态

  1. ​ 创建状态 new 方法 会让线程对象处于创建状态
  2. ​ 就绪状态 调用 线程类的 strat()方法 启用线程
  3. ​ 执行状态 获取到时间片 会让线程进入运行状态
  4. ​ 死亡状态(消亡)线程运行结束
  5. ​ 阻塞状态 sleep() , wait() 方法 都会让线程进入阻塞状态 解除后进入 就绪状态

线程总结

  • 什么时候使用 多线程 标准 :能否提高资源利用率

    现在在jvm底层怎么运行的? 通过时间片的快熟切换来运行的,一个时刻只有一个线程在运行。

  • 线程的实现的两种方法 继承 Thread类,实现Runnable接口

    推荐使用Runnable接口的方式,原因两个 : 1 java 单继承 2 实现 Runnable接口 可以方便的实现多线程对单资源的共享

  • 线程安全问题产生的原因 : 多个线程修改同一资源

  • 解决线程安全问题 : 三个方式 同步代码块,同步方法,重入锁

    同步的代码 越少越好 因为同步也是 耗费资源的

  • 线程的死锁 : 死锁产生的原因 同步代码块的嵌套使用,有可能出现线程的问题

  • 线程的声明周期 :创建状态 就绪状态 运行状态 堵塞状态 死亡状态

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值