Weixl 的个人博客

贼拉正经的个人博客

  menu
22 文章
0 浏览
0 当前访客
ღゝ◡╹)ノ❤️

多线程

多线程详解 java.Thread

  • 多任务 : 一个人可以同时做多个任务。比如 一边一边吃饭。
  • 程序 : 静态的概念,比如没有启动 QQ软件。当启动时就会是 进程
  • 进程 Process : 在操作系统中运行的程序就是进程。 比如 QQ,IDE等..
  • 线程 Thread: 一个进程中可以有多个线程。 比如 看视频中,可以同时听到声音,看图像,看弹幕等等.
  • 执行顺序 : 执行程序 --> 得到进程 -->真正执行的是线程
  • 注意点:一个进程中包含若干个线程,至少有一个线程来执行任务。线程是CPU调度和执行的单位。线程的执行顺序是由CPU来调度的。线程不一定立刻执行。要取决于 CPU的调度

7.png

核心概念

8.png

线程创建

Thread类

  1. 自定义线程类继承 Thread类
  2. 重写 run() 方法。编写线程执行体代码。
  3. 创建线程对象,调用 start()方法启动线程。
    public class Thread01 extends Thread {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("我在找工作-----");
                }
            }
            
        public static void main(String[] args) {
            //TODO 创建 Thread 类, 重写run方法。 执行start来开启线程
            //TODO main方法为主线程, 线程不一定立刻执行。要取决于 CPU的调度
            Thread01 thread01 = new Thread01();
            thread01.start();
    
            //TODO 主线程方法
            for (int i = 0; i < 100; i++) {
                System.out.println("我在学习-----");
            }
        }
    }
    

Runnable接口

  1. 实现Runnable接口
  2. 重写 run方法
  3. 创建线程 Thread 对象,调用start 启动线程。
    public class Thread02 implements Runnable {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println("我在找工作-----");
                }
            }
            
        public static void main(String[] args) {
            //TODO 类实现Runnable接口,重写run方法。通过Thread线程对象的 start启动线程
            Thread02 thread02 = new Thread02();
    
            //创建Thread 线程对象,传递构造参数。 start 启动
            new Thread(thread02).start();
    
            for (int i = 0; i < 100; i++) {
                System.out.println("我在学习-----");
            }
        }
    }
    

Thread和Runnable 区别

  1. Thread 不建议使用,避免OOP单继承局限性
  2. Runnable 推荐使用,避免单继承局限性,灵活方便,方便一个对象被多个线程调用。

静态代理

  • 静态代理模式 :
  • 目标对象 和 代理对象都要事先同一个接口
  • 代理对象要代理调用 目标对象的方法。 然后可以在代理对象中,做很多目标对象做不了的事情。 目标对象就专注做自己的事情
  • Thread 是 继承 Runnable 的。 Thread就相当于是 Runnable的代理对象。他们都有一个共同的方法 run。但是代理对象Thread 拥有自己的 start方法。来启动线程。

10.png

Lamda 表达式

  • Lamda表达式 是函数式编程的概念。
  • 避免匿名内部类定义过多。 让代码看起更简洁 。 去掉没有意义的代码,只留下核心的逻辑。
  • Functional interface 函数式接口。 一个接口中只有一个抽象的方法。才是函数式接口。
  • 因为Runnable中 只有一个run方法。 所以可以用 lamda表达式来实现一些 多线程任务代码。是通过内部类的方式,一步一步简化来的
    //TODO 第五种简化,lamda表达式
    love = (int a) -> {
        System.out.println("相爱"+ a);
    };
    
    love.love(1998);
    

线程状态

  • 线程有五大状态 :
    • 创建状态
    • 就绪状态
    • 阻塞状态
    • 运行状态
    • 死亡状态11.png

线程方法

线程停止方法

  • 不推荐使用 JDK 提供的 stop(),destroy()等停止线程的方法。
  • 推荐设置一个标志位,进行 flag = false。来让线程不再执行任务,但还是要让线程自己停下来。
    public class Thread06 implements Runnable{
         //TODO 推荐自己使用标志位,来让线程自己运行完,从而进行停止
         private Boolean flag = true;
    
        @Override
        public void run() {
            //flag为true时, 执行线程任务。否则不执行任务。让线程自己结束
            int i = 0;
            while (flag){
                System.out.println("线程正在运行++++" + i++);
            }
        }
    
        //线程停止方法
        void stop(){
            flag = false;
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread06 thread06 = new Thread06();
            new Thread(thread06).start();
    
            Thread.sleep(1000);
            for (int i = 0; i < 1000 ; i++) {
                if (i == 900){
                    //到90就停止 线程的任务。让线程自己运行完而终止
                    thread06.stop();
                    System.out.println("线程停止了");
                }
    
            }
        }
    }
    

线程休眠sleep

  • sleep(时间) : 指定当前线程 阻塞的毫秒数。
  • 每一个对象都有一个锁, sleep不会释放锁。
  • 模拟网络延时 : 放大问题的发生性。来检测代码。
  • 做倒计时。
    //TODO 模拟倒计时
        public static void main(String[] args) throws InterruptedException {
            //打印系统归档前
            Date date = new Date(System.currentTimeMillis());
            int num = 10;
            while (true){
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
                Thread.sleep(1000);
                //重新赋值
                date = new Date(System.currentTimeMillis());
                if (num-- <= 0){
                    break;
                }
            }
        }
    

线程礼让 yield

  • 让当前正在执行的线程暂停,但不阻塞。
  • 将线程从运行状态,转为就绪状态。 但是礼让不一定可以成功。

线程强制执行 join

  • join合并线程,待此线程执行完毕,其他线程才能继续执行。
    public class TestJoin implements Runnable{
            //测试 多线程的 join强制执行方法
            @Override
            public void run() {
                for (int i = 0; i < 500; i++) {
                    System.out.println("线程插队的vip来了,让一让");
                }
            }
            public static void main(String[] args) throws InterruptedException {
                TestJoin testJoin = new TestJoin();
                Thread thread = new Thread(testJoin);
                thread.start();;
                //主线程也运行一个方法。
                for (int i = 0; i < 100; i++) {
                    System.out.println("main线程" + i);
                    if (i == 2){
                        //启动强制执行。主线程的 剩下的变量会 在 testJoin线程执行完,在执行
                        thread.join();
                    }
                }
            }
        }
    

线程状态观测

  • Thread.getState ; 可以获取 线程状态的对象。
    //使用个线程对象 来 获取 状态信息对象
    Thread thread = new Thread();
    Thread.State state = thread.getState();
    //那么刚创建的话 state 就是会 /NEW 表示 新建状态。
    thread.start();
    在 state = thread.getState(); 输出的数据就会成为 就绪状态。

线程优先级

  • java提供一个 线程调度器 来监控 进入就绪状态的所有线程。
  • 线程调度器CPU,会按照优先级来决定 应该调度哪个线程执行。
    //优先级用 1-10表示。 数字越大,级别越高
    thread.setPriority(xx) : 设置优先级 , 在线程 start启动前执行
    thread.getPriority() : 获取线程的 优先级

守护线程 daemon

  • 线程分为 用户线程 和 守护线程
  • 例如 main 属于用户线程 , GC垃圾线程 属于 守护线程
  • 虚拟机不用管守护线程是否会停止,当用户线程停止,守护线程就会停止。
    thread.setDaemon(true); 设置为守护线程
    thread.start(); 线程启动



并发问题

  • 并发问题是指,在多个线程操作同一个资源数据时,线程是不安全。会出现数据 紊乱。
  • 例如卖票的问题:9.png

线程同步机制

  • 多个线程访问同一个对象,出现并发问题。就需要线程同步,线程同步其实就是一种等待机制。需要同时访问的线程会进入 对象的等待池 形成对队列。前面的线程执行完毕,下一个接着执行。

队列 和 锁

  • synchronized线程同步锁机制 需要 队列 + 锁。
  • 使用synchronized 锁的一些问题:性能差。
  • synchronized方法控制对 对象 的访问。为每一个对象上一把锁。每个 synchronized 方法都必须获得该方法的 对象 的锁。才能执行。否则线程阻塞。

同步方法 和 同步块

  • 同步方法 将 synchronized 关键字 添加在 方法中。
    @Override
    public synchronized void run() {
    //这里锁的对象是 this 自身类对象
  • 同步步 : synchronized(obj){} 是在方法中进行 锁别的对象。你可能进行增删的 对象不是自身类而是别的对象。就需要这个。
  • obj成为同步监听器。监听的是同步资源。
    synchronized (xxx别的对象,或者自身this) {}

死锁

  • 死锁是指 两个或两个以上的线程,在抢夺资源时互相一直等待的现象。 多个线程互相抱着对方需要的资源,形成僵持。
  • 某一个同步块同时拥有 “ 两个以上对象的锁 ” 时,可能发生 “死锁” 问题
  • 差生死锁的条件
    互斥条件:一个资源每次只能被一个进程使用。
    请求与保持条件 : 一个线程因请求资源阻塞时,对自己已获得的锁保持不放
    
    不剥夺条件 : 进程已经获得的资源,未使用完前,不能强行剥夺
    
    循环等待条件 : 若干进程之间形成一种头尾相接的循环等待资源的关系。
    

Lock锁

  • Lock锁 是JDK1.5之后。通过 显示定义 同步锁对象,来实现同步。
  • Lock锁。显示锁。 要手动开锁,关锁。
  • 使用ReentrantLock 可重复锁,来进行同步操作。
  • 是java的JUC并发包,下面的接口。
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        //进行卖票
        while (true){
            try {
                //加lock锁
                lock.lock();
                Thread.sleep(500);
                if (num <= 0){
                    break;
                }
                System.out.println(Thread.currentThread().getName() + ":" + num--);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                //释放锁 , 显示锁,需要自己手动 释放锁
                lock.unlock();
            }
        }
    }
    

线程协作通信

生产者和消费者问题

  • 这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。
  • 对于生产者,没有生产出产品之前,要通知消费者等待。生产好产品后,又需要马上通知消费者消费。
  • 对于消费者,在消费产品之后,要通知生产者已经结束了消费。让生产者再生产新的产品供消费。
  • 在生产者消费者问题中。仅有synchronized不够的。
    • synchronized 可阻止并发更新同一个共享资源,实现同步
    • 不能用来实现不同线程之间的消息传递(通信)
  • 解决办法:
  • this.wait() : 让线程等待 , 会释放锁, 必须使用在 同步机制 synchronized中
  • this.notifyAll : 唤醒所有线程

缓冲区

  • 定义一个缓存区的 中间件 。 相当于中介
  • 生产者 将产品 生产进缓存区中。
  • 消费者 需要消费产品,就从缓存区中消费。
  • 缓存区中有 wait 和 notifyAll 机制来进行通知 生产者生产 或 消费者 消费。
  • 查看代码更容易理解一点 : M:\每日作业3\优品购\youpingou_parent\demo01\src\main\java\com\czxy\TestPC.java

信号灯法

  • 通过一个 boolean 方法。 进行标识符的判断。 如果条件为 true 生产者就要生产。 如果 false 就让消费者消费。。
  • 通过boolean标识符,的切换,进行 生产者和消费者的 通信。

线程池

  • 提前创建好多个线程,放入线程池中。使用时直接获取。使用完再放回池中。可以避免频繁的创建销毁。
  • 使用 ExecutorService 和 Executors 来创建 线程池。12.png

解决多线程中无法使用mapper

  • 在springboot中,多线程的 Runnable 中不能 引入 service,mapper等对象。因为spring认为这样是不安全的。
  • 创建一个工具类:
    import org.springframework.beans.BeansException;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.ApplicationContextAware;
    
    /**
     * class name: AppBean <BR>
     * class description: please write your description <BR>
     * @version 1.0  2019年4月2日 上午10:03:10
     * @author Aisino)weihaohao
     */
    public class AppBean implements ApplicationContextAware {
    
        private static ApplicationContext applicationContext;
    
        /**
         * @Override
         * @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext) <BR>
         * Method name: setApplicationContext <BR>
         * Description: please write your description <BR>
         * Remark: <BR>
         * @param
         * @throws BeansException  <BR>
         */
        @Override
        public void setApplicationContext(ApplicationContext appContext) throws BeansException {
            applicationContext = appContext;
        }
    
        public static Object getBean(String name){
            return applicationContext.getBean(name);
        }
    
        public static ApplicationContext getApplicationContext() {
            return applicationContext;
        }
    }
    
    
  • 在config中注册:
    @Bean
    public AppBean appBean() {
       return new AppBean();
    }
    
  • 在多线程任务类中调用:
     //多线程专用
    //多线程任务类中 不能添加@component注解。
    private static ArticleMapper articleMapper2 = (ArticleMapper) AppBean.getBean("articleMapper");
    

总结

  • 创建线程方式: 1.继承Thread , 2.实现Runnable
  • sleep 和 wait 的区别 : sleep不会释放锁,wait会。
  • Thread是 静态代理了 Runnable的实现类的。对目标对象进行增强。
  • 动态代理指目标对象不确定。静态代理指目标对象确定。

龟兔赛跑案例

  • 兔子和乌龟进行赛跑。谁先跑完100步,谁就是胜利者。一个Runnable接口实现类。被两个线程调用。其中一个是兔子,设置让兔子中途休眠500毫秒。让乌龟胜利。
    public class Thread04 implements Runnable{
            //TODO 实现龟兔赛跑的多线程 案例
            //胜利者的名字
            private static String winner;
            
        @Override
        public void run() {
            //设置循环,兔子和乌龟,谁先跑完一百步,谁就是胜利者
            for (int i = 0; i <= 100; i++) {
    
                //给兔子进行一个休眠 让他在八十步的时候, 休眠500
                if (Thread.currentThread().getName().equals("兔子") && i == 80){
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (getGame(i)){
                    //true表示比赛结束了,有人已经跑到终点
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"--->跑了"+i+"步");
            }
        }
    
        //定义一个判断 比赛是否结束,谁是胜利者的方法
        boolean getGame(int i){
            if (winner != null){
                //已经有胜利者
                return true;
            }else if(i >= 100){
                //大于100步,表示比赛已经结束
                //赋值胜利者名称
                winner = Thread.currentThread().getName();
                System.out.println("比赛结束:胜利者--->"+winner);
                return true;
            }else{
                return false;
            }
        }
    
        public static void main(String[] args) {
            Thread04 thread04 = new Thread04();
            Thread04 thread05 = new Thread04();
    
            new Thread(thread04 , "兔子").start();
            new Thread(thread04 , "乌龟").start();
        }
    }
    

标题:多线程
作者:Weixl
地址:http://loveless.top/articles/2020/10/08/1602155459646.html