wait、notify、notifyAll

2019-02-13   网络   thread   thread  
  • content

总结java线程基础知识

  • wait、notify、notifyAll

一. 示例

1. 方法介绍

void notify() Wakes up a single thread that is waiting on this object’s monitor. 译:唤醒在此对象监视器上等待的单个线程

void notifyAll() Wakes up all threads that are waiting on this object’s monitor. 译:唤醒在此对象监视器上等待的所有线程

void wait( ) Causes the current thread to wait until another thread invokes the notify() method or the notifyAll( ) method for this object. 译:导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法

void wait(long timeout) Causes the current thread to wait until either another thread invokes the notify( ) method or the notifyAll( ) method for this object, or a specified amount of time has > elapsed. 译:导致当前的线程等待,直到其他线程调用此对象的notify() 方法或 notifyAll() 方法,或者指定的时间过完。

void wait(long timeout, int nanos) Causes the current thread to wait until another thread invokes the notify( ) method or the notifyAll( ) method for this object, or some other thread interrupts the current > thread, or a certain amount of real time has elapsed. 译:导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法,或者其他线程打断了当前线程,或者指定的时间过完。

2. 简单示例

public class WaitAndNotify {
    public static void main(String[] args) {
        Object co = new Object();
        System.out.println(co);

        for (int i = 0; i < 5; i++) {
            MyThread t = new MyThread("Thread" + i, co);
            t.start();
        }

        try {
            TimeUnit.SECONDS.sleep(2);
            System.out.println("-----Main Thread notify-----");
            synchronized (co) {
                co.notify();
            }

            TimeUnit.SECONDS.sleep(2);
            System.out.println("Main Thread is end.");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class MyThread extends Thread {
        private String name;
        private Object co;

        public MyThread(String name, Object o) {
            this.name = name;
            this.co = o;
        }

        @Override
        public void run() {
            System.out.println(name + " is waiting.");
            try {
                synchronized (co) {
                    co.wait();
                }
                System.out.println(name + " has been notified.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
运行结果:
java.lang.Object@1540e19d
Thread1 is waiting.
Thread2 is waiting.
Thread0 is waiting.
Thread3 is waiting.
Thread4 is waiting.
-----Main Thread notify-----
Thread1 has been notified.
Main Thread is end.

将其中的那个notify换成notifyAll,运行结果:
Thread0 is waiting.
Thread1 is waiting.
Thread2 is waiting.
Thread3 is waiting.
Thread4 is waiting.
-----Main Thread notifyAll-----
Thread4 has been notified.
Thread2 has been notified.
Thread1 has been notified.
Thread3 has been notified.
Thread0 has been notified.
Main Thread is end.

运行环境jdk8,结论:
notify唤醒一个等待的线程;notifyAll唤醒所有等待的线程。

3. 生产者-消费者问题

什么是生产者-消费者问题?

producer_consumer.jpg

假设有一个公共的容量有限的池子,有两种人,一种是生产者,另一种是消费者。需要满足如下条件: 1、生产者产生资源往池子里添加,前提是池子没有满,如果池子满了,则生产者暂停生产,直到自己的生成能放下池子。 2、消费者消耗池子里的资源,前提是池子的资源不为空,否则消费者暂停消耗,进入等待直到池子里有资源数满足自己的需求。

public interface AbstractStorage {
    void consume(int num);
    void produce(int num);
}
import java.util.LinkedList;

/**
 *  生产者和消费者的问题
 *  wait、notify/notifyAll() 实现
 */
public class Storage1 implements AbstractStorage {
    //仓库最大容量
    private final int MAX_SIZE = 100;
    //仓库存储的载体
    private LinkedList list = new LinkedList();

    //生产产品
    public void produce(int num){
        //同步
        synchronized (list){
            //仓库剩余的容量不足以存放即将要生产的数量,暂停生产
            while(list.size()+num > MAX_SIZE){
                System.out.println("【要生产的产品数量】:" + num + "\t【库存量】:"
                        + list.size() + "\t暂时不能执行生产任务!");

                try {
                    //条件不满足,生产阻塞
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            for(int i=0;i<num;i++){
                list.add(new Object());
            }

            System.out.println("【已经生产产品数】:" + num + "\t【现仓储量为】:" + list.size());

            list.notifyAll();
        }
    }

    //消费产品
    public void consume(int num){
        synchronized (list){

            //不满足消费条件
            while(num > list.size()){
                System.out.println("【要消费的产品数量】:" + num + "\t【库存量】:"
                        + list.size() + "\t暂时不能执行消费任务!");

                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //消费条件满足,开始消费
            for(int i=0;i<num;i++){
                list.remove();
            }

            System.out.println("【已经消费产品数】:" + num + "\t【现仓储量为】:" + list.size());

            list.notifyAll();
        }
    }
}
public class Producer extends Thread{
    //每次生产的数量
    private int num ;

    //所属的仓库
    public AbstractStorage abstractStorage;

    public Producer(AbstractStorage abstractStorage){
        this.abstractStorage = abstractStorage;
    }

    public void setNum(int num){
        this.num = num;
    }

    // 线程run函数
    @Override
    public void run()
    {
        produce(num);
    }

    // 调用仓库Storage的生产函数
    public void produce(int num)
    {
        abstractStorage.produce(num);
    }
}
public class Consumer extends Thread{
    // 每次消费的产品数量
    private int num;

    // 所在放置的仓库
    private AbstractStorage abstractStorage1;

    // 构造函数,设置仓库
    public Consumer(AbstractStorage abstractStorage1)
    {
        this.abstractStorage1 = abstractStorage1;
    }

    // 线程run函数
    public void run()
    {
        consume(num);
    }

    // 调用仓库Storage的生产函数
    public void consume(int num)
    {
        abstractStorage1.consume(num);
    }

    public void setNum(int num){
        this.num = num;
    }
}
public class Test{
    public static void main(String[] args) {
        // 仓库对象
        AbstractStorage abstractStorage = new Storage1();

        // 生产者对象
        Producer p1 = new Producer(abstractStorage);
        Producer p2 = new Producer(abstractStorage);
        Producer p3 = new Producer(abstractStorage);
        Producer p4 = new Producer(abstractStorage);
        Producer p5 = new Producer(abstractStorage);
        Producer p6 = new Producer(abstractStorage);
        Producer p7 = new Producer(abstractStorage);

        // 消费者对象
        Consumer c1 = new Consumer(abstractStorage);
        Consumer c2 = new Consumer(abstractStorage);
        Consumer c3 = new Consumer(abstractStorage);

        // 设置生产者产品生产数量
        p1.setNum(10);
        p2.setNum(10);
        p3.setNum(10);
        p4.setNum(10);
        p5.setNum(10);
        p6.setNum(10);
        p7.setNum(80);

        // 设置消费者产品消费数量
        c1.setNum(50);
        c2.setNum(20);
        c3.setNum(30);

        // 线程开始执行
        c1.start();
        c2.start();
        c3.start();

        p1.start();
        p2.start();
        p3.start();
        p4.start();
        p5.start();
        p6.start();
        p7.start();
    }
}

更简洁的生产者-消费者代码

import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;

/**
* Simple Java program to demonstrate How to use wait, notify and notifyAll()
* method in Java by solving producer consumer problem.
*
* @author Javin Paul
*/
public class ProducerConsumerInJava {
    public static void main(String args[]) {
        System.out.println("How to use wait and notify method in Java");
        System.out.println("Solving Producer Consumper Problem");
        Queue<Integer> buffer = new LinkedList<>();
        int maxSize = 10;
        Thread producer = new Producer(buffer, maxSize, "PRODUCER");
        Thread consumer = new Consumer(buffer, maxSize, "CONSUMER");
        producer.start(); 
        consumer.start(); 
    }
}

/**
* Producer Thread will keep producing values for Consumer
* to consumer. It will use wait() method when Queue is full
* and use notify() method to send notification to Consumer
* Thread.
*
* @author WINDOWS 8
*
*/
class Producer extends Thread
{ 
    private Queue<Integer> queue;
    private int maxSize;
    public Producer(Queue<Integer> queue, int maxSize, String name){
        super(name); 
        this.queue = queue; 
        this.maxSize = maxSize;
    }

    @Override 
    public void run()
    {
        while (true){
            synchronized (queue) {
                while (queue.size() == maxSize) {
                    try {
                        System.out .println("Queue is full, " + "Producer thread waiting for " + "consumer to take something from queue");
                        queue.wait();
                    } catch (Exception ex) {
                        ex.printStackTrace(); 
                    }
                }
                Random random = new Random();
                int i = random.nextInt();
                System.out.println("Producing value : " + i); 
                queue.add(i); 
                queue.notifyAll();
            }
        }
    }
}

/**
* Consumer Thread will consumer values form shared queue.
* It will also use wait() method to wait if queue is
* empty. It will also use notify method to send
* notification to producer thread after consuming values
* from queue.
*
* @author WINDOWS 8
*
*/
class Consumer extends Thread {
    private Queue<Integer> queue;
    private int maxSize;
    public Consumer(Queue<Integer> queue, int maxSize, String name){
        super(name);
        this.queue = queue;
        this.maxSize = maxSize;
    }
    @Override public void run() {
        while (true) {
            synchronized (queue) {
                while (queue.isEmpty()) {
                    System.out.println("Queue is empty," + "Consumer thread is waiting" + " for producer thread to put something in queue");
                    try {
                        queue.wait();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
                System.out.println("Consuming value : " + queue.remove()); queue.notifyAll();
            }
        }
    }
}

问题

1. 为什么wait、notify必须在持有锁的情况下才能执行?

a. 如果在未持有对象锁的情况下调用object.wait()/notify(),直接会报错,JDK已经做好保护。

b. 判断条件与wait()方法分为两个步骤,在不加锁的情况下,假设thread1在执行到两个步骤中间的时候thread2执行了条件设置和nofity()方法,那thread1的wait就错过了notify通知。

通过互斥锁来保证wait()/notify()之间的先后顺序,才能保证wait不会错过notify,从而导致wait线程一直挂着。

2. 为什么在Object中提供wait、nodify方法?为什么不是在Thread中提供?

a. wait()方法告诉当前线程释放监视器(monitor)并将线程放入等待队列,直到其它线程进入相同的监视器(monitor)并调用nodify()

b. notify()唤醒在这个对象的监视器(monitor)上等待的单个线程

c. 这两个方法是线程之间通信的方式,都是monitor级别的方法,monitor是关联到Object的而不是Thread的(所有对象都有监视器monitor)

d. 如果仅仅在Thread中提供这两种方法,那一个线程必须知道其它线程的状态,其它线程在等待哪些资源,这样才能(调用thread2.nofity())通知它们去获取这些资源,然而在java里面线程之间是无法互相知道对方状态的

参考

wait和notify的理解与使用

java中的notify和notifyAll有什么区别?

Java多线程学习之wait、notify/notifyAll 详解

java中的notify和notifyAll有什么区别?

为什么wait,notify和notifyAll要与synchronized一起使用?

Why must wait() always be in synchronized block

How can the wait() and notify() methods be called on Objects that are not threads?

线程基础知识总结

2019-02-12   网络   thread   thread  
  • content

总结java线程基础知识

  • 创建线程的三种方式
  • Thread方法介绍

一. Java语言创建线程的三种方式

1. 通过实现 Runnable 接口

推荐这种方式

class RunnableDemo implements Runnable {
   private Thread t;
   private String threadName;
   
   RunnableDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // 让线程睡眠一会
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}
 
public class TestThread {
 
   public static void main(String args[]) {
      RunnableDemo R1 = new RunnableDemo( "Thread-1");
      R1.start();
  
      RunnableDemo R2 = new RunnableDemo( "Thread-2");
      R2.start();
   }
}

2. 通过继承 Thread 类本身

不推荐这种方式,原因是继承Thread类就无法继承其它类,而实现Runnable接口之后,还可以继承其它类

class ThreadDemo extends Thread {
   private Thread t;
   private String threadName;
   
   ThreadDemo( String name) {
      threadName = name;
      System.out.println("Creating " +  threadName );
   }
   
   public void run() {
      System.out.println("Running " +  threadName );
      try {
         for(int i = 4; i > 0; i--) {
            System.out.println("Thread: " + threadName + ", " + i);
            // 让线程睡眠一会
            Thread.sleep(50);
         }
      }catch (InterruptedException e) {
         System.out.println("Thread " +  threadName + " interrupted.");
      }
      System.out.println("Thread " +  threadName + " exiting.");
   }
   
   public void start () {
      System.out.println("Starting " +  threadName );
      if (t == null) {
         t = new Thread (this, threadName);
         t.start ();
      }
   }
}
 
public class TestThread {
 
   public static void main(String args[]) {
      ThreadDemo T1 = new ThreadDemo( "Thread-1");
      T1.start();
  
      ThreadDemo T2 = new ThreadDemo( "Thread-2");
      T2.start();
   }
}

3. 通过 Callable 配合 Future 或者 FutureTask 创建线程

a) Runnable/Thread的问题 通过实现Runnable接口或者继承Thread类这两种方式来启动线程都有个问题,就是无法获取执行结果,实现Callable接口可以解决这个问题

b) Callable 一般配合ExecutorService来使用,通过泛型参数来定义返回的数据类型,ExecutorService提供的submit方法可以执行Callable实例(任务)

<T> Future<T> submit(Callable<T> task);

c) Future返回的Future对象提供了一些有用的方法,其中包含获取执行结果的get方法

  • cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论mayInterruptIfRunning为true还是false,肯定返回true
  • isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true
  • isDone方法表示任务是否已经完成,若任务完成,则返回true
  • get()方法用来获取执行结果,该方法会产生阻塞,直到结果返回
  • get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就抛出异常

d) FutureTask

public class FutureTask<V> implements RunnableFuture<V>{}
public interface RunnableFuture<V> extends Runnable, Future<V> {}

实现Runnable无法返回执行结果,实现Callable无法直接通过Thread包装执行(只能提交给ExecutorService执行) 而FutureTask兼顾两者优点 FutureTask.jpg

FutureTask futureTask = new FutureTask(new Callable<String>() {  
           @Override  
           public String call() throws Exception {  
               long b = new Date().getTime();  
               System.out.println("call begin " + b);  
               StringBuilder sb = new StringBuilder();  
               for (int i = 0; i < 100000; i++) {  
                   sb.append(i).append(",");  
               }  
               System.out.println("call end " + (new Date().getTime() - b));  
               return sb.toString();  
           }  
       }) );  
// 使用futureTask创建一个线程  
new Thread(futureTask).start(); 
//Callable+Future方式获取执行结果
import java.util.concurrent.*;
public class Test {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Task task = new Task();
        Future<Integer> future = executorService.submit(task);
        executorService.shutdown();
  
        System.out.println("主线程在执行任务...");
        try {
            Thread.sleep(2000);
        } catch(InterruptedException ex) {
            ex.printStackTrace();
        }
   
        try {
            System.out.println("task运行结果:"+future.get());
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        } catch (ExecutionException ex) {
            ex.printStackTrace();
        }  
        System.out.println("所有任务执行完毕");
    }
}
class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println("子线程在执行任务...");
        //模拟任务耗时
        Thread.sleep(5000);
        return 1000;
    }
}
//Callable+FutureTask方式获取执行结果
import java.util.concurrent.*;
public class Test {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Task task = new Task();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
        executorService.submit(futureTask);
        executorService.shutdown();
  
        System.out.println("主线程在执行任务...");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
   
        try {
            System.out.println("task运行结果:"+futureTask.get());
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        } catch (ExecutionException ex) {
            ex.printStackTrace();
        }
   
        System.out.println("所有任务执行完毕");
    }
}
class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        System.out.println("子线程在执行任务...");
        //模拟任务耗时
        Thread.sleep(5000);
        return 1000;
    }
}

二. Thread方法介绍

1. join()

作用: 让父线程等待执行该方法的线程结束之后才能继续运行

底层调用的是wait方法

// 父线程
public class Parent extends Thread {
    public void run() {
        Child child = new Child();
        child.start();
        child.join();
        // ...
    }
}
// 子线程
public class Child extends Thread {
    public void run() {
        // ...
    }
}

//在 Parent 调用 child.join() 后,child 子线程正常运行,Parent 父线程会等待 child 子线程结束后再继续运行

2. yield()

Java线程中的Thread.yield( )方法,译为线程让步。顾名思义,就是说当一个线程使用了这个方法之后,它就会把自己CPU执行的时间让掉,让自己或者其它的线程运行,注意是让自己或者其他线程运行,并不是单纯的让给其他线程。

yield()的作用是让步。它能让当前线程由“运行状态”进入到“就绪状态”,从而让其它具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其它具有相同优先级的线程就一定能获得执行权;也有可能是当前线程又进入到“运行状态”继续运行!

举个例子:一帮朋友在排队上公交车,轮到Yield的时候,他突然说:我不想先上去了,咱们大家来竞赛上公交车。然后人就一块冲向公交车,有可能是其他人先上车了,也有可能是Yield先上车了。

但是线程是有优先级的,优先级越高的人,就一定能第一个上车吗?这是不一定的,优先级高的人仅仅只是第一个上车的概率大了一点而已,最终第一个上车的,也有可能是优先级最低的人。并且所谓的优先级执行,是在大量执行次数中才能体现出来的。

三. 线程状态

Java线程共有5中状态,分别为:新建(new)、就绪(runnable)、运行(running)、堵塞(blocked)、死亡(dead)。

  1. 新建状态:当创建一个线程时,此线程进入新建状态,但此时还未启动。
  2. 就绪状态:创建好线程后调用线程的start()方法,该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权。等待期间线程处于就绪状态
  3. 运行状态:当线程获得cpu的使用权后,线程进入运行状态,开始执行run()方法。
  4. 阻塞状态:线程在运行过程中可能由于各种原因放弃了cpu 使用权,暂时停止运行,即进入阻塞状态。阻塞的情况分为:等待阻塞、同步阻塞、其他阻塞等三种情况。
  5. 死亡状态:线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。

注:

  1. 上图运行中、就绪状态要调换一下位置
  2. 超时等待翻译为定时等待比较好理解

四. 守护线程

概念

在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)

只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。

注意点

(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会抛出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (2) 在Daemon线程中产生的新线程也是Daemon的。 (3) 不要认为所有的应用都可以分配给Daemon来进行服务,比如读写操作或者计算逻辑。因为你不可能知道在所有的User完成之前,Daemon是否已经完成了预期的服务任务。一旦User退出了,可能大量数据还没有来得及读入或写出,计算任务也可能多次运行结果不一样。这对程序是毁灭性的。造成这个结果理由就是,一旦所有User Thread离开了,虚拟机也就退出运行了。

问题

如何正确终止线程?

  1. 使用共享变量,要用volatile关键字,保证可见性,能够及时终止。
  2. 使用interrupt()和isInterrupted()配合使用。

参考

Java 多线程编程

在Java中使用Callable、Future进行并行编程

Future和FutureTask的区别

Java 浅析 Thread.join()

Thread中yield方法

第一篇博客

2018-11-01   刚子   生活   生活  

前言

测试一下第一篇博客

课程目录

课程涵盖各种前后端,大数据教程,最新语言等。都是一个个完整的项目,一系列的教程。


11 / 11