• 多线程


    1) 概念

    计算机中的一个程序运行时至少要有一个进程,一个进程中至少包含一个线程,线程是程序中的一个独立流程

    示例:

       假设一家餐厅,一个厨师C,一个服务员F

    顾客A和顾客B去餐厅吃饭:

       A先进:F-A点菜  3个菜  F-C   炒菜

       B进:F-B点菜  3个菜  F-C     炒菜

      如果是两个服务员F1  F2,此时AB就餐时F1-AF2-BF1F2就餐整个流程相互不影响,只是公用一个厨师炒菜

      假设AB同时就餐,此时C就同时得到了两份菜单:

    处理一:先把A3个菜全部抄完,然后再炒B3个菜,出现等待状态严重

    处理二:A-B菜交替进行 效率相对较高,AB两位认为是同时就餐

    类比:

    餐厅:一个应用程序

    厨师:CPU(中央处理器)  按照时间片运行 抢占式调度执行(不是按顺序执行,谁抢到谁执行)

    线程:F1  F2(两个流程独立运行,相互不影响)

    线程并发:线程并发是一种表象,只是因为cpu的运算时间非常快,所以我们认为线程是并发执行的

    线程开发:在一个程序中产生多个线程,让这些线程彼此独立完成特定的功能

    问题:如何在java中去产生多个线程

    2) 线程开发 -------(重写run()方法)

    ① 继承Thread

    ② 实现Runnable接口(没有start()方法),所以要借助它的子类thread实现  

    先创建一个Runnable线程对象扔给thread构造方法构建一个t2线程出来,得到thread中的start方法

     

      注意:

               创建一个线程后需要调用start()方法启动线程,线程启动后由JVM获取cpu时间片调用run()方法执行。

               线程中的run()是不能手动调用,如果手动调用即无线程概念,只是普通方法的调用

               线程的结果是无规则的,只能从线程运行的结果反推线程执行过程

             主线程:main  

    获取当前类的对象方法:

    Thread.currentThread().getName()

    3) 线程的状态

    ① 基本状态

    ① 阻塞状态 sleep()

    当运行中的线程需要等待外部资源或调用线程sleep()时,当前线程进入阻塞状态,当阻塞结束后当前线程进入可运行状态

              阻塞状态的线程不会释放掉当前对象的对象锁

            

            业务需求:父母给你1000块生活费,每次给100块,给10次

               对于父母:每次给你100块,给10次

               对于你:每次接收100块,接10次

               父母开始给钱

                   给第1100块

                   给第2100块

                   …

                   给第10100块

               父母给钱结束 

              问题:在一个线程中需要等到另一个线程全部执行完后才继续往后执行

            ③ join()方法  ----一定发生

               调用当前线程的join()方法,则会一直等待当前线程执行完

               join(int time):只等待指定时间,时间一到,放弃等待继续执行

            ④ yield()方法  -让行  ----可能会发生

               yield()方法不会进入到阻塞状态,而是处于可运行状态,当线程调用此方法后,当前线程释放掉系统资源,由系统再次调用执行,此时,当前线程继续抢占资源,有可能继续抢占到资源继、续执行。

            线程一旦终止会继续往后执行,不会重头执行

               sleepyield方法的区别
    asleep会产生异常,yield不会产生异常;

              bsleep会进入阻塞状态,yield不会

    c、当一个线程调用sleep方法,线程会等sleep时间结束之后会进入可运行或就绪状态,而一个线程调用yield方法后会立即重新抢占cpu时间片继续执行

     

            ⑤ 线程优先级

               线程的优先级表示线程的运行顺序,优先级高就代表先运行

               线程的优先级从1-10,级别越高,优先级越高,默认优先级为5

               线程的优先级越高并不代表优先执行,线程的执行时通过cpu的调度执行(做开发的时 候通过代码来控制线程执行顺序)

    4) 线程安全问题

    线程不安全的原因:多个线程同时访问同一个临界资源,如果破坏了操作的原子性,则可能会造成数据不一致,这种情况我们叫做线程不安全。举例,stack中两个线程同时对stack进行操作

    临界资源:多个线程共同访问的同一个对象

    原子操作:线程中若干行代码不能分开执行,若不同步执行则会发生线程不安全现象

    银行转账:

    这一系列的操作叫做原子操作,临界资源为银行账户

    存钱:插卡---密码---放钱---输入转入账户---修改账户余额---确认成功---取卡---成功

    取钱:插卡---密码---取钱---确认成功---取卡---成功

            解决方法:线程同步--前后有关系

     同步:Synchronized       位置:  package lgs.hm.practice.test.thread.stack;

       A同步代码块   某一时刻只允许一个线程进入加了锁的同步代码块

      this的原因:当一个线程进入到同步快执行的时候有当前对象,那么这个线程就获得了当前对象的对象锁 ,synchronize同步块必须要拿到对象锁,对象锁只有一把,所以有多个同步块的时候必须要拿到对象锁才能执行,没有对象锁的进入到锁池状态(当前对象的锁池),就要等其它同步块运行完释放掉对象锁才能调用。

     B、同步方法

           锁池状态

              当一个线程遇到synchronized同步关键字时,则当前线程立即获取到当前对象的对象锁,开始同步执行,此时,若其他线程同样遇到当前对象的synchronized关键字时,因对象锁只有一把,而进入同步块一定需要拿到对象锁,否则不能进入同步块,所以其他线程得不到对象锁时会进入到锁池状态。当对象锁被释放后,锁池状态中会随机选取一个线程获取到对象锁,从而进入到可运行状态。  如果当前线程执行完释放掉对象锁后,让他在锁池状态中休息不参与线程之间对对象锁的的获取,就在synchronized之外睡眠一段时间

      口述线程的状态及各种状态的特点

    5) 线程的死锁

     ① 概念

    synchronized(a){

      int I = 10;

      synchronized(b){

    }

    }

    synchronized(b){

      int I = 10;

      synchronized(a){

    }

    }

        实例:

    ② 解决死锁方法:Object中提供的方法:wait()、notify()notifyAll()

         wait():当一个对象调用其wait()方法后,当前线程会释放掉当前对象的对象锁,当前线程进入到当前对象的等待池中。wait()方法一定是在synchronized同步块中调用。

         notifyAll():当一个对象调用其notifyAll()方法后,会在等待池中唤醒需要当前对象锁的所有线程,此时被唤醒的线程会进入到锁池状态等待抢锁,当线程抢到了当前对象锁后进入可运行状态。

    notify():和notifyAll一样,只是唤醒的是等待池中随机的一个线程。

    6) 线程状态图

     

    7) 生产者和消费者问题

    产品类
    public class Product {
        //定义产品的唯一ID
        int id;
        //定义构造方法初始化产品id
        public Product(int id) {
            this.id=id;
            // TODO Auto-generated constructor stub
        }
    }
    
    仓库
    public class Repertory {
        //定义一个集合类用于存放产品.规定仓库的最大容量为10.
        public LinkedList<Product> store=new LinkedList<Product>();
        public LinkedList<Product> getStore() {
            return store;
        }
        public void setStore(LinkedList<Product> store) {
            this.store = store;
        }
        /* 生产者方法
         * push()方法用于存放产品.
         * 参数含义:第一个是产品对象
         * 第二个是线程名称,用来显示是谁生产的产品.
         * 使用synchronized关键字修饰方法的目的:
         * 最多只能有一个线程同时访问该方法.
         * 主要是为了防止多个线程访问该方法的时候,将参数数据进行的覆盖,从而发生出错.
         */
        public synchronized void push(Product p,String threadName)
        {
            /* 仓库容量最大值为10,当容量等于10的时候进入等待状态.等待其他线程唤醒
             * 唤醒后继续循环,等到仓库的存量小于10时,跳出循环继续向下执行准备生产产品.
             */
            while(store.size()==10){
                try {
                    //打印日志
                    System.out.println(threadName+"报告:仓库已满--->进入等待状态--->呼叫老大过来消费");
                    //因为仓库容量已满,无法继续生产,进入等待状态,等待其他线程唤醒.
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //唤醒所有等待线程
            this.notifyAll();
            //将产品添加到仓库中.
            store.addLast(p);
            //打印生产日志
            System.out.println(threadName+"生产了:"+p.id+"号产品"+" "+"当前库存来:"+store.size());
            try {
                //为了方便观察结果,每次生产完后等待0.1秒.
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
             
        }
        /* 消费者方法
         * pop()方法用于存放产品.
         * 参数含义:线程名称,用来显示是谁生产的产品.
         * 使用synchronized关键字修饰方法的目的:
         * 最多只能有一个线程同时访问该方法.
         * 主要是为了防止多个线程访问该方法的时候,将参数数据进行的覆盖,从而发生出错.
         */
        public synchronized void pop(String threadName){
            /* 当仓库没有存货时,消费者需要进行等待.等待其他线程来唤醒
             * 唤醒后继续循环,等到仓库的存量大于0时,跳出循环继续向下执行准备消费产品.
             */
            while(store.size()==0)
            {
                try {
                    //打印日志
                    System.out.println(threadName+"下命令:仓库已空--->进入等待状态--->命令小弟赶快生产");
                    //因为仓库容量已空,无法继续消费,进入等待状态,等待其他线程唤醒.
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //唤醒所有等待线程
            this.notifyAll();
            //store.removeFirst()方法将产品从仓库中移出.
            //打印日志
            System.out.println(threadName+"消费了:"+store.removeFirst().id+"号产品"+" "+"当前库存来:"+store.size());
            try {
                //为了方便观察结果,每次生产完后等待1秒.
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    生产者
    public class Producer implements Runnable {
        //定义一个静态变量来记录产品号数.确保每一个产品的唯一性.
        public static  Integer count=0;
        //定义仓库
        Repertory repertory=null;
        //构造方法初始化repertory(仓库)
        public Producer(Repertory repertory) {
            this.repertory=repertory;
        }
        /* run()方法因为该方法中存在非原子性操作count++;
         * 当多个线程同时访问时会发生count++的多次操作,导致出错
         * 为该方法添加同步错做,确保每一次只能有一个生产者进入该模块。
         * 这样就能保证count++这个操作的安全性.
         */
        @Override
        public void run() {
                while (true) {     
                    synchronized(Producer.class){
                        count++;
                        Product product=new Product(count);
                        repertory.push(product,Thread.currentThread().getName());
                    }
                                 
                 
            }
             
        }
    }
    
    消费者
    
    public class Consumer implements Runnable {
        //定义仓库
        Repertory repertory=null;
        //构造方法初始化repertory(仓库)
        public Consumer(Repertory repertory) {
            this.repertory=repertory;
        }
        //实现run()方法,并将当前的线程名称传入.
        @Override
        public  void run() {
            while(true){
                repertory.pop(Thread.currentThread().getName());
            }
        }
     
    }
    
    .测试类
    
    public class TestDemo {
      public static void main(String[] args) {
        //定义一个仓库,消费者和生产者都使用这一个仓库
        Repertory repertory=new Repertory();
        //定义三个生产者(p1,p2,p3)
        Producer p1=new Producer(repertory);
        Producer p2=new Producer(repertory);
        Producer p3=new Producer(repertory);
        //定义两个消费者(c1,c2)
        Consumer c1=new Consumer(repertory);
        Consumer c2=new Consumer(repertory);
        //定义5个线程(t1,t2,t3,t4,t5)
        Thread t1=new Thread(p1,"张飞");
        Thread t2=new Thread(p2,"赵云");
        Thread t3=new Thread(p3,"关羽");
        Thread t4=new Thread(c1,"刘备");
        Thread t5=new Thread(c2,"曹操");
        //因为关羽跟赵云的生产积极性高,所以把他们的线程优先级调高一点
        t2.setPriority(10);
        t3.setPriority(10);
        //启动线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
      }
    }
    
    //转自https://www.cnblogs.com/hckblogs/p/7858545.html#blogTitle
  • 相关阅读:
    PHP实现无限极分类
    html2canvas生成并下载图片
    一次线上问题引发的过程回顾和思考,以更换两台服务器结束
    Intellij IDEA启动项目报Command line is too long. Shorten command line for XXXApplication or also for
    mq 消费消息 与发送消息传参问题
    idea 创建不了 java 文件
    Java switch 中如何使用枚举?
    Collections排序
    在idea 设置 git 的用户名
    mongodb添加字段和创建自增主键
  • 原文地址:https://www.cnblogs.com/dulute/p/11313310.html
Copyright © 2020-2023  润新知