• 多线程


    多线程

    1、介绍

    线程是同一进程内同时执行的多个代码段。宏观上并行,微观上串行,对于每块CPU来说,同一时刻,CPU只能执行同一条指令,但是对于多核系统来说,可以做到真正的并行。线程间可以共享内存,进程间不能共享内存。

    2、创建线程的方式

    创建线程的方式有两种,可以通过Thread类直接创建,也可以通过实现Runnable接口,传递给Thread构造函数来创建。后者可以实现多个线程执行相同的run方法,Thread方法的实现是调用Runnable的run方法。

    3、线程的常用方法

    3.1 start

    启动线程,调用该方法后,CPU才能够开始调度该线程,但不是不一定马上调度到,还需要看CPU具体的执行情况。

    Thread t = new Thread();
    t.start();
    

    3.2 run

    我们需要实现的方法,在方法中执行具体的业务逻辑代码。应用程序不需要调用该方法,而是CPU在调度该线程执行后,会自动调用该方法。

    3.3 yield

    暂时放弃cpu的抢占权,但是瞬间即逝,即该方法执行后又立即开始抢占CPU开始执行。因此是一个瞬间的动作。这通常是一个暗示,不能完全保证其达到目的。

    //当前线程放弃CPU抢占权
    Thread.yield();
    

    3.4 join

    等待指定的线程执行完成后,当前线程才能继续执行。因此也可以理解为将指定的线程执行过程加入到当前的线程中。

    Thread t = new Thread() ;
    //等待t执行完之后当前线程继续执行
    t.join() ;
    

    3.5 sleep

    休眠指定的时间片(毫秒值),就是让当前线程休眠一段时间,时间一到,也不一定就会立即继续执行,还需要等待CPU的调度执行时间。

    //当前线程休眠1s
    Thread.sleep(1000) ;
    

    3.6 守护线程

    守护线程是通常是为那些非守护线程提供服务的。如果一个进程中剩余的线程都是守护线程,则进程结束。

    Thread t = new Thread();
    //设置线程t为守护线程
    t.setDaemon(true) ;
    

    3.7 holdsLock

    判断当前线程是否持有指定的对象的锁,该方法是静态方法。

    //创建锁旗标
    Object lock = new Object();
    //判断当前线程是否持有lock的锁
    Thread.holdsLock(lock) ;
    

    4、线程安全问题

    线程安全问题是多线程编程中必然会遇到的问题,通常是由于多个线程并发访问共享变量,导致变量的内容不一致引发的安全问题。解决方式就是上锁,即使用synchronized关键字。java中任何对象都含有锁旗标,都可以作为锁出现,因此也相当于信号灯方式,同一时刻,只能有一个线程可以对该锁旗标上锁,其他线程会处于blocked状态,即阻塞状态。线程解锁后,其他线程可以进行抢夺,再进行上锁。锁操作过程中,需要确保的是线程是对同一个对象上锁。实现锁方式有两种,同步方法和同步代码块。

    //非静态方法是对当前对象上锁,即this对象
    public synchronized void m(){
    	...
    }
    
    //静态方法是以当前Class描述符为锁
    public static synchronized void m(){
    	...
    }
    
    //
    public void m(){
      	//同步代码块使用指定对象作为锁
    	synchronized(lock){
        	...
        }
    }
    

    5、生产消费问题

    同步解决了线程安全问题的同时,也带来了生产消费问题。所谓生产消费问题是生产者生产产品,消费者消费产品,生产的速率高于消费的速率,导致仓库最终会溢出,称这类现象为生产消费问题。解决方法就是使用等待唤醒机制放置仓库溢出。即生产者发现仓库已满,进进入等待队列,消费发现没有产品,也进入等待队列,两者均在有动作执行候,发送通知,通知等待队列中的线程继续开始执行。

    /**
      * 生产者生产过程,判断池中是否已满
      */
    public synchronized void put(Integer x){
    	while(pool.isFull()){
        	this.wait() ;
        }
      	pool.put(x) ;
      	this.notify() ;
    }
    
    /**
      * 消费者消费过程,判断池中是否已空
      */
    public synchronized void put(Integer x){
    	while(pool.isEmpty()){
        	this.wait() ;
        }
      	pool.remove() ;
      	this.notify() ;
    }
    

    6、死锁问题

    如果所有线程都进入等待队列,都等着别人发送通知,但是没有人能够发通知的时候,此时程序处于一种死锁状态。如下经过精心设计的程序就会导致死锁。程序最终的结果是生产者和消费者都进入等待队列。

    class PCDemo5{
      public static void main(String[] args){
        //使用java中集合类,List是列表。
        Pool pool = new Pool();
        Productor p1 = new Productor("生产者1",pool);
        p1.setName("p1");
        Consumer c1 = new Consumer("消费者",pool);
        c1.setName("c1");
        Consumer c2 = new Consumer("消费者",pool);
        c2.setName("c2");
        p1.start();
        c1.start();
        c2.start();
      }
    }
    
    //生产者
    class Productor extends Thread{
      static int i = 0 ;
      private String name ;
      private Pool pool ;
      public Productor(String name ,Pool pool){
        this.name = name ;
        this.pool = pool ;
      }
      public void run(){
        while(true){
          pool.add(i ++);
        }
      }
    }
    
    //消费者
    class Consumer extends Thread{
      private String name ;
      private Pool pool ;
      public Consumer(String name ,Pool pool){
        this.name = name ;
        this.pool = pool;
      }
      public void run(){
        while(true){
          pool.remove();
          //System.out.println("-: " + i);
        }
      }
    }
    
    class Pool{
      private java.util.List<Integer> list = new java.util.ArrayList<Integer>();
      //容器最大值
      private int MAX = 1 ;
      //添加元素
      public void add(int n){
        synchronized(this){
          try{
            String name = Thread.currentThread().getName();
            while(list.size() == MAX){
              System.out.println(name + ".wait()");
              this.wait();
            }
            list.add(n);
            System.out.println(name + " + : " + n);
            System.out.println(name + ".notify()");
            this.notify();
          }catch(Exception e){
            e.printStackTrace();
          }
        }
      }
      //删除元素
      public int remove(){
        synchronized(this){
          try{
            String name = Thread.currentThread().getName();
            while(list.size() == 0){
              System.out.println(name + ".wait()");
              this.wait();
            }
            int i = list.remove(0);
            System.out.println(name + " - : "  + i);
            System.out.println(name + ".notify()");
            this.notify();
            return i ;
          }
          catch(Exception e){
            e.printStackTrace();
          }
          return -1 ;
        }
      }
    }
    

    7、线程变换状态图

    xpc_java_pro_023

    8、思考题

    8.1熊吃蜂蜜问题

    两只熊,100只蜜蜂,蜜蜂每次生产的蜂蜜量是1,罐子的容量是30,熊在罐子的蜂蜜量达到20的时候,一次性将蜂蜜吃光。

    /**
     * 罐子类,容器
     */
    class Box{
      //最大量
      public static int MAX = 50 ;
    
      //当前蜂蜜量
      private int honeyNum = 0 ;
    
      //向罐子追加蜂蜜
      public synchronized void add(int n){
        while(honeyNum == MAX){
          try {
            this.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        honeyNum ++ ;
        this.notify();
      }
    
    /**
      * 消费行为
      */
      public synchronized int clearAll(){
        while(honeyNum < Bear.MIN){
          try {
            this.wait();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        int n = honeyNum ;
        honeyNum = 0 ;
        notify();
        return n ;
      }
    }
    
    /**
     * 熊
     */
    class Bear extends Thread {
      public static int MIN = 20 ;
      private String bearName ;
      private Box box ;
      public Bear(Box box, String bearName){
        this.box = box ;
        this.bearName = bearName ;
      }
    
      public void run() {
        for(;;){
          int n = box.clearAll();
          System.out.println(bearName + " : " + n);
        }
      }
    }
    
    /**
     * 蜜蜂
     */
    class Bee extends Thread {
      private String bname;
      private Box box ;
      public Bee(Box box, String bname) {
        this.box =box;
        this.bname = bname;
      }
    
      public void run() {
        int index = 1 ;
        for(;;){
          box.add(index);
          System.out.println(bname + " : " + index);
          index ++ ;
        }
      }
    }
    
    //测试类
    class App{
      public static void main(String[] args) {
        Box box = new Box() ;
        new Bear(box, "xxxxxx1").start();
        new Bear(box, "xxxxxx2").start();
        for(int i = 0 ; i < 30 ; i ++){
          new Bee(box , "B" + i).start();
        }
      }
    }
    

    8.2 和尚吃馒头问题

    有30个和尚,100个馒头,每个和尚最多吃4馒头,最少一个馒头,一次只能吃一个馒头。满足上述条件下,尽快把馒头吃了。

    /**
     * 派发馒头的类
     */
    class Boss{
      //剩余的馒头数
      public static int breadNum = 30 ;
    
      //未吃馒头的和尚数
      public static int uneatedMonks = 10 ;
    
      //获取馒头
      public synchronized int getBread(Monk monk){
        //不足最小值
        if(monk.eated < Monk.MIN){
          //取出最上方的馒头
          int tmp = breadNum ;
          breadNum -- ;
          if(monk.eated == 0){
            uneatedMonks -- ;
          }
          return tmp ;
    
        }
        if(monk.eated == Monk.MAX){
          return 0 ;
        }
        //判断是否有多余的馒头
        if(breadNum > (uneatedMonks * Monk.MIN)){
          int tmp = breadNum ;
          breadNum -- ;
          return tmp ;
        }
        return 0 ;
      }
    }
    
    /**
     * 和尚类
     */
    class Monk extends Thread{
      private String mname ;
      public static int MAX = 4 ;
      public static int MIN = 2 ;
    
      //已经吃了多少个
      private int eated  ;
      private String breadNumStr = "" ;
    
      private Boss boss ;
    
      public Monk(Boss boss , String mname){
        this.boss = boss ;
        this.mname = mname ;
      }
    
      public void run() {
        for(;;){
          int breadNo = boss.getBread(this) ;
          if(breadNo == 0){
            System.out.printf("%s吃了%d:(%s)
    " , mname , eated , breadNumStr);
            break ;
          }
          else{
            breadNumStr = breadNumStr + "," + breadNo ;
            eated ++ ;
          }
        }
      }
    }
    
    //测试类
    class App{
      public static void main(String[] args) {
        Boss boss = new Boss();
        for(int i = 0 ; i < 10 ; i ++){
          new Monk(boss , "tom" + i).start(); ;
        }
      }
    }
    
  • 相关阅读:
    PHP数字签名算法
    PHP日期相关类
    浏览器常见bug及解决办法
    PHPer整理的前端开发知识
    小程序之轮播图(2020.4.13更新)
    Android APK反编译 apktool使用教程
    秒懂-单列布局水平居中布局
    一句话搞定-phpStudy安装yaf扩展
    Git的简单安装
    人人都能读懂的css3 3d小demo
  • 原文地址:https://www.cnblogs.com/xupccc/p/9594441.html
Copyright © 2020-2023  润新知