zoukankan      html  css  js  c++  java
  • 多线程

    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
  • 相关阅读:
    关系型数据库与非关系型数据库
    项目技术点总结
    小程序项目开发总结
    小程序的生命周期
    ES6中的class 与prototype
    js中的深拷贝与浅拷贝
    DOM的构建与优化
    ES6中promise总结
    react服务端渲染
    vue服务端渲染
  • 原文地址:https://www.cnblogs.com/dulute/p/11313310.html
Copyright © 2011-2022 走看看