多线程

多线程

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)\r\n" , 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(); ;
    }
  }
}
posted @ 2018-09-05 20:54  大道至简(老徐)  阅读(273)  评论(0编辑  收藏  举报