Java基础_多线程

什么是多线程

  • 线程: 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
  • 进程: 进程是程序的基本执行实体.
  • 多线程
    • 作用: 提高效率
    • 应用场景: 软件中的耗时操作、所有的聊天软件、所有的服务器

并发, 并行

  • 并发: 在同一时刻,有多个指令在单个CPU上交替执行
  • 并行: 在同一时刻,有多个指令在多个CPU上同时执行

多线程的实现方式

继承Thread类

// TheadDemo
public class ThreadDemo01 {
    public static void main(String[] args) {
        /**
         * 多线程的第一种启动方式:
         * 1. 定义一个类继承Thread
         * 2. 重写run方法
         * 3. 创建子类的对象, 并启动线程
         */
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("thread1");
        t2.setName("thread2");

        // 开启线程
        t1.start();
        t2.start();
    }
}

// MyThread
public class MyThread extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "Hello Thread!");
        }
    }
}

实现Runnable接口

// Demo
public class ThreadDemo {
    public static void main(String[] args) {
        /**
         * 多线程的第二种启动方式:
         * 1. 定义一个类实现Runnable接口
         * 2. 重写run方法
         * 3. 创建自己的类的对象
         * 4. 创建一个Thread类的对象, 并启动线程
         */

        // 创建MyRunnable的对象
        // 表示多线程要执行的任务
        MyRunnable myRun = new MyRunnable();

        // 创建线程对象
        Thread t1 = new Thread(myRun);
        Thread t2 = new Thread(myRun);

        t1.setName("thread1");
        t2.setName("thread2");

        // 开启线程
        t1.start();
        t2.start();
    }
}


// MyRunnable
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            // 获取当前线程的对象
            Thread t = Thread.currentThread();
            System.out.println(t.getName() + "Hello Runnable!");
        }
    }
}

实现Callable接口和Future接口

// Demo
public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        /**
         * 多线程的第三种实现方式
         * 特点: 可以获取到多线程运行的结果
         *
         * 1. 创建一个类MyCallable实现Callable接口
         * 2. 重写call (有返回值, 表示多线程运行的结果)
         *
         * 3. 创建MyCallable的对象 (表示多线程要执行的任务)
         * 4. 创建FutureTask的对象 (管理多线程运行的结果)
         * 5. 创建Thread类的对象, 并启动
         */

        // 创建MyCallable的对象
        Callable myC = new MyCallable();
        // 创建FutureTask的对象
        FutureTask<Integer> myF = new FutureTask<>(myC);
        // 创建Thread类的对象
        Thread t1 = new Thread(myF);
        t1.start();

        // 获取结果
        Integer res = myF.get();
        System.out.println(res);
    }
}

// Impl
public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        // 1~100求合
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

实现方式对比

优点缺点
继承Thread类编程比较简单, 可以直接使用Thread类中的方法可扩展性较差, 不能再继承其他的类
实现Runnable接口扩展性强, 实现该接口的同时还可以继承其他的类编程相对复杂, 不能直接使用Thread类中的方法
实现Callable接口同实现Runnable接口同实现Runnable接口
  • 继承Thread类和实现Runnable接口没有返回值, 实现Callable接口可以有返回值

常见的成员方法

常见方法

// Demo
public class demo {
    public static void main(String[] args) throws InterruptedException {
        /**
         * String getName()                 返回此线程的名称
         * void setName(String name)        设置线程的名字(构造方法也可以设置名字)
         * // 线程有默认的名字
         * // 格式: Thread-x(x是序号, 从0开始)
         *
         * static Thread currentThread()    获取当前线程的对象
         * // JVM虚拟机启动后, 会自动启动多条线程,
         * // 其中有一条就叫做main线程, 它回去调用main方法
         *
         * static void sleep(long time)     让线程休眠指定的时间, 单位为毫秒
         */

        // MyThread t1 = new MyThread("飞机");
        // MyThread t2 = new MyThread("坦克");
        //
        // t1.start();
        // t2.start();

        Thread t = Thread.currentThread();
        System.out.println(t.getName()); // main

        System.out.println("111");
        Thread.sleep(3000);
        System.out.println("222");
    }
}

// Thread
public class MyThread extends Thread{

    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"@"+i);
        }
    }
}

进程的优先级

// Demo
public class Demo {
    public static void main(String[] args) {
        /**
         * setPriority(int newPriority)   设置线程的优先级
         * final int getPriority()        获取线程的优先级
         */

        MyRunnable mr = new MyRunnable();
        Thread t1 = new Thread(mr, "飞机");
        Thread t2 = new Thread(mr, "坦克");

        // 线程默认的优先级是5
        System.out.println(t1.getPriority());
        System.out.println(t2.getPriority());
        System.out.println(Thread.currentThread().getPriority());

        // 线程最大优先级是10, 最小是1
        t2.setPriority(10);
        t1.setPriority(1);

        // 优先级指定的是线程抢占到cpu的概率
        t1.start();
        t2.start();
    }
}

// runnable
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"@"+i);
        }
    }
}

守护线程

// Demo
public class Demo {
    public static void main(String[] args) {
        /**
         * // 设置为守护线程
         * final void setDaemon(boolean on)
         * // 当其他的非守护线程执行完毕之后, 守护线程会陆续结束
         */

        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();

        t1.setName("女神");
        t2.setName("备胎");

        t2.setDaemon(true);

        t1.start();
        t2.start();
    }
}

// MyThread1
public class MyThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName()+"@"+i);
        }
    }
}

// MyThread2
public class MyThread2 extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"@"+i);
        }
    }
}

礼让线程

// Demo
public class Demo {
    public static void main(String[] args) {
        /**
         * // 出让线程/礼让线程
         * public static void yield
         * 解释: 当前占据cpu的线程执行该方法后,
         *      会让出cpu的执行权, 然后飞机和坦克会再次抢占cpu
         * 效果: 可以让线程的执行比较均衡
         */

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("飞机");
        t2.setName("坦克");

        t1.start();
        t2.start();
    }
}

// MyThread
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"@"+i);
            // 出让当前cpu的执行权
            Thread.yield();
        }
    }
}

插入线程

// Demo
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        /**
         * // 插入线程/插队线程
         * public final void join()
         */

        MyThread t1 = new MyThread();
        t1.setName("土豆");
        t1.start();

        // 表示把t这个线程, 插入到当前线程之前
        // t: 土豆
        // 当前线程: main线程
        t1.join();

        for (int i = 0; i < 10; i++) {
            System.out.println("main线程"+i);
        }
    }
}

// MyThread
public class MyThread extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+"@"+i);
        }
    }
}

线程安全

线程的生命周期

售票模拟

同步代码块

// Demo
public class Demo {
    public static void main(String[] args) {
        /**
         * 3个窗口模拟售卖100张票
         */

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

// MyThread
public class MyThread extends Thread{

    // 表示这个类所有的对象, 都共享ticket
    static int ticket = 0;

    // 锁对象, 一定要是唯一的
    // static Object obj = new Object();

    @Override
    public void run() {
        while(true)
        {
            // 同步代码块
            synchronized (MyThread.class){
                if(ticket >= 100){
                    break;
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                ++ticket;
                System.out.println(getName()+"正在卖第"+ticket+"张票");
            }
        }
    }
}

同步方法

// Demo
public class Demo {
    public static void main(String[] args) {
        /**
         * 3个窗口模拟售卖100张票
         */

        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        Thread t3 = new Thread(mr);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

// MyRunnable
public class MyRunnable implements Runnable{

    // 只有一个Runnable对象, 不用static
    int ticket = 0;

    @Override
    public void run() {
        while(true)
        {
            if (method()) break;
        }
    }

    private synchronized boolean method() {
        if(ticket >= 1000){
            return true;
        }
        Thread t = Thread.currentThread();
        ++ticket;
        System.out.println(t.getName()+"正在卖第"+ticket+"张票");
        return false;
    }
}
  • 总结
    • 同步方法的锁对象不能自己指定, 非静态方法是this, 静态方法是当前类的字节码文件对象
    • StringBuilder是线程不安全的, StringBuffer是线程安全的(它的方法是有添加synchronized关键字)

lock锁

// Demo
public class Demo {
    public static void main(String[] args) {
        /**
         * 3个窗口模拟售卖100张票
         */

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

// MyThread
public class MyThread extends Thread{

    // 表示这个类所有的对象, 都共享ticket
    static int ticket = 0;

    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true)
        {
            lock.lock();

            try {
                if(ticket >= 1000){
                    break;
                }
                Thread.sleep(1);
                ++ticket;
                System.out.println(getName()+"正在卖第"+ticket+"张票");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }

        }
    }
}
  • 总结
    • 如果简单地把解锁写在尾部, 则会导致某个线程break循环之后, 直接跳出循环而没有解锁, 导致程序不会停止

死锁

// Demo
public class Demo {
    public static void main(String[] args) {
        /**
         * 死锁
         */

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.setName("线程A");
        t2.setName("线程B");

        t1.start();
        t2.start();
    }
}

// MyThread
public class MyThread extends Thread{

    static Object objA = new Object();
    static Object objB = new Object();

    @Override
    public void run() {
        while(true)
        {
            if(getName().equals("线程A")){
                synchronized (objA){
                    System.out.println("线程A拿到了A锁, 准备去拿B锁");
                    synchronized (objB){
                        System.out.println("线程A拿到了B锁, 顺利执行完了一轮");
                    }
                }
            }else if(getName().equals("线程B")){

                synchronized (objB){
                    System.out.println("线程B拿到了B锁, 准备去拿A锁");
                    synchronized (objA){
                        System.out.println("线程B拿到了A锁, 顺利执行完了一轮");
                    }
                }
            }
        }
    }
}
  • 总结
    • 不要嵌套锁

生产者和消费者

waitAndNotify

// Demo
public class Demo {
    public static void main(String[] args) {
        /**
         * 实现生产者和消费者(等待唤醒机制)
         * 实现线程轮流交替执行
         */

        Cook c = new Cook();
        Foodie f = new Foodie();

        c.start();
        f.start();
    }
}

// Desk
public class Desk {

    /**
     * 控制生产者和消费者的执行
     */

    // 是否有面条  0: 没有面条,  1: 有面条
    public static int foodflag = 0;
    // 总个数
    public static int count = 10;
    // 锁对象
    public static Object lock = new Object();
}

// Cook
public class Cook extends Thread {

    @Override
    public void run() {
        while(true)
        {
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    // 判断桌子上是否有食物
                    if(Desk.foodflag == 1){
                        // 如果有, 就等待
                        System.out.println("还有, 不做");
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else{
                        // 如果没有, 就制作食物
                        System.out.println("厨师做了一碗面条");
                        // 修改桌子上的食物状态
                        Desk.foodflag = 1;
                        // 叫醒等待的消费者开吃
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

// Foodie
public class Foodie extends Thread{
    
    @Override
    public void run() {
        while(true)
        {
            synchronized (Desk.lock){
                if(Desk.count == 0){
                    break;
                }else{
                    // 先判断桌子上是否有面条
                    if(Desk.foodflag == 0){
                        // 如果没有, 就等待
                        System.out.println("没得吃");
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else{
                        // 把吃的总数-1
                        Desk.count -= 1;
                        // 如果有, 就开吃
                        System.out.println("吃货正在吃面条, 还能吃"+Desk.count+"碗");
                        // 吃完之后, 唤醒厨师继续做
                        Desk.lock.notifyAll();
                        // 修改桌子的状态
                        Desk.foodflag = 0;
                    }
                }
            }
        }
    }
}

putAndTake

// Demo
public class Demo {
    public static void main(String[] args) {
        /**
         * 阻塞队列实现生产者和消费者(等待唤醒机制)
         */

        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

        Cook c = new Cook(queue);
        Foodie f = new Foodie(queue);

        c.start();
        f.start();
    }
}

// Cook
public class Cook extends Thread {

    ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while(true)
        {
            try {
                queue.put("面条");
                System.out.println("厨师放了一碗面条");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

// Foodie
public class Foodie extends Thread{

    ArrayBlockingQueue<String> queue;

    public Foodie(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while(true)
        {
            try {
                String take = queue.take();
                System.out.println("吃货拿了一碗"+take);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
  • 总结
    • 输出语句在锁外面, 导致打印顺序会有问题

线程的六种状态

  • 新建状态 (New) -> 创建线程对象
  • 就绪状态 (Runnable) -> start方法
  • 阻塞状态 (Blocked) -> 无法获得锁对象
  • 等待状态 (Waiting) -> wait方法
  • 计时等待 (Timed_Waiting) -> sleep方法
  • 结束状态 (Terminated) -> 全部代码运行完毕
  • 运行状态: 该状态交给操作系统管理, 没有定义

来源

黑马程序员. 阿玮Java零基础

Gitee地址

https://gitee.com/yu-ba-ba-ba/awJava

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Y_cen

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

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

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

打赏作者

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

抵扣说明:

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

余额充值