Java Thread(多线程)

单线程和多线程的对比

小明放学回家后要做三件事:上厕所、玩手机、睡觉

单线程状态的小明:先上厕所、再玩手机、最后睡觉
多线程状态的小明:一边上厕所一边玩手机、最后睡觉

  • 利:多线程可以提高程序运行效率和资源的利用率
  • 弊:多线程会比较消耗资源,效率比较低(线程切换浪费时间),而且处理不好的话会造成线程死锁

单线程也有自己的优势:不会出现抢占资源出现死锁的现象

多线程的实现方法

继承Thread

public class ThreadDemo extends Thread {
    //无参构造函数
    public ThreadDemo() {

    }

    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //输出
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                // 睡眠
                sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        //实例化对象
        ThreadDemo test = new ThreadDemo();
        
        //开启线程
        new Thread(test, "A").start();
        new Thread(test, "B").start();

    }
}

实现Runnable

public class ThreadDemo implements Runnable {

    //无参构造函数
    public ThreadDemo() {
    }


    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //输出
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                // 睡眠
                Thread.currentThread().sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        //实例化对象
        ThreadDemo threadDemo = new ThreadDemo();

        //开启线程
        new Thread(threadDemo, "A").start();
        new Thread(threadDemo, "B").start();
    }
}

两种实现方式的区别

继承 Thread类,不适合多个线程共享资源。
实现Runnable 接口,方便实现资源的共享。

Java是单继承、多实现的模式,继承Runnable实现多线程会更灵活一点。

start方法和run方法的关系

单线程:调用run方法
多线程:调用start方法,加入队列,交替执行

案例一:你想去玩迪士尼的某个项目,你开始排队的时候,相当于调用了start方法,当排队轮到你玩的时候,相当于调用了run方法。

案例二:你投简历相当于调用start方法,你被公司录取了相当于执行run方法。

CPU会从已经开启了start方法的线程队列中挑选某个线程执行run方法。

调用start方法只是代表某个线程已经就绪,不一定会执行。不调用start方法一定不会执行。

投简历不一定会有offer,不投简历一定没有offer!

多进程的优先级

985、211毕业生投简历,拿到offer的概率肯定要比一本高,这就是优先级。
人分三六九等,线程也是如此。

public class ThreadDemo implements Runnable {

    //无参构造函数
    public ThreadDemo() {
    }

    //重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            //输出
            System.out.println(Thread.currentThread().getName() + ":" + i);

        }
    }

    public static void main(String[] args) {
        //实例化对象
        ThreadDemo threadDemo = new ThreadDemo();

        //创建线程
        Thread thread = new Thread(threadDemo);
        Thread thread1 = new Thread(threadDemo);

        /*
        先执行优先级高的线程
            最小优先级
            Thread.MIN_PRIORITY
            中等优先级
            Thread.NORM_PRIORITY
            最大优先级
            Thread.MAX_PRIORITY
         */

        //设置优先级
        thread.setPriority(Thread.MAX_PRIORITY);
        //设置优先级
        thread1.setPriority(Thread.MIN_PRIORITY);


        //开启线程
        thread.start();
        thread1.start();
    }
}

资源竞争和线程同步

假如你银行卡里有500元,你和你夫人在不同的分行同时取500元,你俩都能取出500元吗?
你们要是能操作成功,银行早就破产了!

你和你夫人相当于两个线程,你的账户相当于资源。
两个线程同时对一个资源进行操作,肯定会出问题。那么银行怎么没出问题?
因为银行实现了:队列+锁
银行达到的效果:一个资源在同一时间只能有一个线程访问
例如:你的账户在取钱,你夫人没法对你的账户取钱,只有当取钱结束了,你夫人才能取钱。

队列

排队产生的队列可以实现:同一时间只有一个线程访问

锁可以确保某个线程使用资源的时候其他的线程无法进入

你在公共厕所排队,等排到你的时候,你刚坐到马桶上,想排泄的时候,好家伙!排在你后面的人进来了!
怎么解决这种情况?用锁把门锁上,你不开锁,排在你后面的人就永远进不来。

synchronized翻译过来是同步的意思,可以看成锁。

1、同步方法

	//synchronized  指本对象,也就是this
    private synchronized void method() {
		需要修改的代码
    }

2、同步代码块

//可以设定为任何对象
synchronized(xxx.class){
需要修改的代码
}

lock是锁的意思,相当于synchronized的升级版。

两者的不同点:

  • lock需要手动解锁,synchronized是自动解锁
  • lock是类,synchronized是关键字
  • lock适合大量代码同步、synchronized适合少量代码同步
  • 当某个线程使用synchronized锁后被挂起、睡眠等情况时,所有线程都会等待。lock锁针对这种情况,会结束锁,让其他进程继续使用
    例如:
    synchronized锁时,你进入公共厕所,把门锁上,然后你睡着了,导致后面所有排队的人都在等待。
    lock锁时,你进入公共厕所,把门锁上,然后你睡着了,过了一会管理员发现不对劲,过来把门打开,把你从马桶上扔出去,后面排队的人继续使用。

两者的相同点:

  • 都可以实现上锁的效果
//初始化
Lock lock =new ReentrantLock();
		//上锁
		lock.lock();
		try {
		需要修改的代码
		} finally {
			//解锁
			lock.unlock();
		}

死锁

锁是把双刃剑,解决资源竞争问题的同时也带来了死锁问题

还记得上面说的排队上厕所吗?当排到你的时候,你坐到马桶上的时候,把门上锁了,你不开锁,别人就进不来。
当你排泄完了,发现自己没带纸!恰好门外面的小明有纸。
我:麻烦借我点纸。
小明:你把锁打开,我把纸给你。
我:你把纸给我,我擦完才能开锁。
小明:你把锁打开,我把纸给你。
我:你把纸给我,我擦完才能开锁。

产生了死锁,结果队列后面的所有人都没法上厕所。

死锁:两个线程都在等待对方先完成,造成程序的停滞,一般程序的死锁都是在程序运行时出现的。

一个死锁Demo

public class DeadLock extends Thread {
    /*

    有一家饭店,先结账后吃饭,这是店规,不能改变。
    小明是个死心眼的人,先吃饭后结账是他的原则,不能改变。
    某天,小明去了这家饭店吃饭,就构成了死锁条件
     */
    //定义饭和钱
    public static String meal = "饭";
    public static String money = "钱";


    public static void main(String[] args) {
        //实例化对象
        Restaurant restaurant = new Restaurant();
        XiaoMing xiaoMing = new XiaoMing();

        //开启多线程
        xiaoMing.start();
        restaurant.start();

    }


}

/*
小明
 */
class XiaoMing extends Thread {
    @Override
    public void run() {
        synchronized (DeadLock.money) {
            System.out.println("小明:我要先吃饭,才能给你钱");
            try {
                //沉睡
                sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (DeadLock.meal) {
                System.out.println("小明:吃饭中。。。");
            }
        }
    }
}

/*
饭店
 */
class Restaurant extends Thread {
    @Override
    public void run() {
        synchronized (DeadLock.meal) {
            System.out.println("餐馆:你必须先结账,才能吃饭");
            try {
                //沉睡
                sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (DeadLock.money) {
                System.out.println("餐馆收钱中。。。");
            }
        }
    }
}

线程的五种状态

线程一般具有5种状态:创建、就绪、运行、阻塞、终止

创建状态

在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时它已经有了相应的内存空间和其他资源,但还处于不可运行状态。

Thread thread=new Thread();

就绪状态

新建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。

运行状态

当就绪状态被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run() 方法。

阻塞状态

一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入/输出操作,会让 CPU 暂时中止自己的执行,进入阻塞状态。
在可执行状态下,如果调用sleep(),suspend(),wait() 等方法,线程都将进入阻塞状态,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态

死亡状态

线程调用 stop() 方法时或 run() 方法执行结束后,即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。

线程通信

sleep

让当前正在执行的线程休眠,不施放对象锁。

线程的状态:运行----阻塞—就绪(睡眠完成)

例如:你在公共厕所排队,终于排到你了,你坐上马桶,把门锁上,好家伙,你睡着了,后面的人一直等啊等,等你睡醒了把锁打开,后面的人才能正常使用马桶。

yield

暂停当前正在执行的线程,并执行其他线程,不施放对象锁。

线程的状态:运行----就绪

例如:接上面的公共厕所例子,排到你了,你坐上马桶,把门锁上,发现自己没有排泄的欲望,但是又不想下次花时间排队,好家伙!然后你就把门从外面锁上,去别处玩了,后面排队的人都要一直等你把锁打开才能使用。

wait

当前线程暂停执行,释放对象锁

线程的状态:运行----堵塞

例如:排到你了,你坐上马桶,把门锁上,正准备排泄,门外校长在敲门,说让你赶紧出来,他拉肚子,然后你把锁打开,你出去,校长进来。

notify

随机唤醒一个调用wait() 方法而阻塞的线程

线程的状态:堵塞----就绪

例如:接上面,校长排泄完了,把锁打开,发现你和小明都是把门锁上后被领导换出去的,然后校长开始随机点豆豆,点到谁,就把谁换进来,继续排泄。

notifyAll

唤醒所有调用wait() 方法而阻塞的线程

线程的状态:堵塞----就绪

例如:接上面,校长排泄完了,把锁打开,发现你和小明都是把门锁上后被领导换出去的,然后校长对你俩说,谁先跑到我面前(竞争),就把谁换进来,继续排泄。

join

依次执行任务

线程的状态:运行----堵塞

例如:在线程B中调用了线程A的Join()方法,只有线程A执行完毕后,才会继续执行线程B。

在这里插入图片描述

用户线程和守护进程

用户线程

例如:main线程

用户线程执行完毕,JVM才可以关闭。

守护线程

例如:gc线程(垃圾收集器)

setDeamon方法参数为true把线程设置为守护线程

在这里插入图片描述

JVM关闭时不用考虑守护线程有没有执行完毕。

快问快答:Java 程序每次运行至少启动几个线程?
答:至少启动两个线程,每当使用 Java 命令执行一个类时,实际上都会启动一个 JVM,每一个JVM实际上就是在操作系统中启动一个线程,Java 本身具备了垃圾的收集机制。所以在 Java 运行时至少会启动两个线程,一个是 main 线程,另外一个是垃圾收集线程。

生产者消费者问题

有一个仓库、把货入库的工人(生产者)、把货出库的工人(消费者)。

生产者

public class Producer extends Thread {

    //生产的数量
    private int num;
    //仓库
    private Storage storage;

    //构造韩函数
    public Producer(int num, Storage storage) {

        this.num = num;

        this.storage = storage;

    }

    //生产
    public void produce() {

        this.storage.put(this.num);

    }


    @Override
    public void run() {

        // TODO Auto-generated method stub
        while (true) {
            //生产
            this.produce();
            try {
                //睡眠
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者

public class Consumer extends Thread {
    //消费的数量
    private int num;
    //仓库
    private Storage storage;

    //构造函数
    public Consumer(int num, Storage storage) {

        this.num = num;

        this.storage = storage;

    }

    //消费
    public void consume() {

        this.storage.get(this.num);

    }


    @Override
    public void run() {

        // TODO Auto-generated method stub
        while (true) {
            //消费
            this.consume();
            try {
                //睡眠
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

仓库

package com.ives.thread;

/*
   @about file:write here
   @author:Ives
   @time:2021/1/10 上午12:34
 */

import java.util.ArrayList;

import java.util.List;


public class Storage {

    // 仓库最大容量
    private static final int MAX = 100;

    // 当前仓库容量
    private List<Object> nowNumber = new ArrayList<>(MAX);// 库存余量


    //入库
    public void put(int num) {

        synchronized (nowNumber) {

            //仓库满了,不能生产
            while (MAX - nowNumber.size() < num) {

                System.out.println(String.format("仓库总容量:%d,当前仓库容量:%d,此次入库量:%d,仓库空余量不足,请等待。。。", MAX, nowNumber.size(), num));

                try {
                    //等待
                    nowNumber.wait();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

            System.out.println(String.format("仓库总容量:%d,当前仓库容量:%d,此次入库量:%d,产品正在入库请等待。。。", MAX, nowNumber.size(), num));

            //更新仓库的数量
            for (int i = 0; i < num; i++) {

                nowNumber.add(new Object());

            }

            System.out.println("入库操作完毕!");

            //唤醒其他线程
            nowNumber.notifyAll();

        }

    }

    //出库
    public void get(int num) {

        synchronized (nowNumber) {

            //仓库的货不够
            while (nowNumber.size() < num) {

                System.out.println(String.format("仓库总容量:%d,当前仓库容量:%d,出库量:%d,仓库余量不足,请等待。。。", MAX, nowNumber.size(), num));

                try {
                    //等待
                    nowNumber.wait();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

            System.out.println(String.format("仓库总容量:%d,当前仓库容量:%d,出库量:%d,产品正在出库请等待。。。", MAX, nowNumber.size(), num));

            //更新仓库数量
            for (int i = 0; i < num; i++) {

                nowNumber.remove(0);

            }

            System.out.println("出库操作完毕!");

            //唤醒其他进程
            nowNumber.notifyAll();

        }

    }
}



主函数

public class ThreadMain {
    public static void main(String[] args) {
        //实例化仓库
        Storage storage = new Storage();

        //实例化生产者
        Producer producer = new Producer(20, storage);

        //实例化消费者
        Consumer consumer = new Consumer(80, storage);

        //开启线程
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值