JAVA多线程

在Typora上写完之后,发布到简书上,很多样式都变了

一、线程与进程

    进程:进程是操作系统对一个正在运行的程序的一种抽象,因此可以说每个应用程序就是一个进程,它是操作系统进行资源分配和调度的一个独立单位。       

    线程:每个进程中可以拥有多个线程,运行在进程的上下文中,并享有同样的代码和全局数据.线程是CPU调度和分配的基本单位,它是比进程更小的独立运行体。

二、多线程创建方式

    Java线程创建方式主要有三种:
  1. 实现Runnable接口

    package cn.wuxiaoer.thread;
    
    public class ThreadDemo implements Runnable{
    
        public static void main(String[] args) {
            new Thread(new ThreadDemo(),"线程1").start();
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    
  2. 继承Thread类

    不推荐这种方法,主要是因为Java是单继承的,如果继承了Thread,那么该类就不能再继承其他的类。

    package cn.wuxiaoer.thread;
    
    public class ThreadDemo extends Thread{
        public static void main(String[] args) {
            new Thread(new ThreadDemo(),"线程1").start();
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    
  1. 实现Callable接口(这个是有返回值的)

       Callable接口实际上是属于Executor框架中的功能类,Callable接口与Runnable接口的功能类似,但提供了比Runnable更加强大的功能.
    

    实现Callable接口有以下好处:

    • Callable可以在任务结束的时候提供一个返回值,Runnable无法提供这个功能
    • Callable的call方法可以抛出异常,而Runnable的run方法不能抛出异常。
    package cn.wuxiaoer.thread;
    
    import java.util.concurrent.*;
    
    public class ThreadDemo implements Callable<String> {
        public static void main(String[] args) {
            ExecutorService threadPool=Executors.newSingleThreadExecutor();
            Future<String> future=threadPool.submit(new ThreadDemo());
            try {
                System.out.println(future.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }finally {
                threadPool.shutdown();
            }
        }
        
        @Override
        public String call() throws Exception {
            return "线程1";
        }
    }
    
     注意上面代码中的**ExecutorService**类和**Executors**类,这两个类都属于**JUC**中的类,我会在后面的文章中介绍,同时会介绍如何手动创建线程池,阿里巴巴的Java手册中也不建议使用上面的方法创建线程池,上面的例子只是为了演示如何创建线程。
    

    下图来至于《阿里巴巴Java开发手册终极版》

image-20210609144416370.png

三、线程状态

  1. 线程的状态

Jdk源码中线程的状态如下:

public enum State {
        NEW,//新创建
        RUNNABLE,//可运行
        BLOCKED,//被阻塞
        WAITING,//等待
        TIMED_WAITING,//计时等待
        TERMINATED;//被终止
    }
  1. 获取线程的状态:
package cn.wuxiaoer.thread;

public class ThreadDemo implements Runnable {
    public static void main(String[] args) {
       Thread thread=new Thread(new ThreadDemo());
        System.out.println(thread.getState());//NEW
        thread.start();
        System.out.println(thread.getState());//RUNNABLE
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(thread.getState());//TIMED_WAITING
    }

    @Override
    public void run() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  1. 这六个状态之间的关系如下:

    图片来自于《Java核心技术卷一》第14章

image-20210609151202194.png

四、线程优先级、守护线程、线程组

  1. 线程的优先级

    在Java程序中每个线程都有一个优先级,默认情况下,一个线程继承它的父线程的优先级。

    可以通过Thread的setPriority方法设置线程的优先级,一定在start之前设置。

    package cn.wuxiaoer.thread;
    
    public class ThreadDemo implements Runnable {
        public static void main(String[] args) {
           Thread thread=new Thread(new ThreadDemo());
           thread.setPriority(Thread.MAX_PRIORITY);
        }
        @Override
        public void run() {
        }
    }
    
     Thread中有三个个静态常量,我们可以将优先级设置为MIN_PRIORITY和MAX_PRIORITY之间的任意值,当设置完线程的优先级以后,每当线程调度有机会调度线程时,会优先选择优先级比较高的线程。
    
        /**
         * The minimum priority that a thread can have.
         */
        public final static int MIN_PRIORITY = 1;
    
       /**
         * The default priority that is assigned to a thread.
         */
        public final static int NORM_PRIORITY = 5;
    
        /**
         * The maximum priority that a thread can have.
         */
        public final static int MAX_PRIORITY = 10;
    
  2. 守护线程

    守护线程主要是为其它线程提供服务,比如说GC线程。

     可以通过Thread的setDaemon方法设置线程为守护线程。当只有守护线程时,虚拟机就退出了,所以下面的代码"守护线程完成!"永远也打印不出来。
    
    package cn.wuxiaoer.thread;
    
    public class ThreadDemo implements Runnable {
        public static void main(String[] args) {
           Thread thread=new Thread(new ThreadDemo());
           thread.setDaemon(true);
           thread.start();
           System.out.println("主线程完成!");
        }
    
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
                System.out.println("守护线程完成!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
  1. 未捕获异常处理器和线程组

    未捕获异常处理器

       在上面我们知道线程的run方法不能抛出任何受查异常,但是非受查异常会导致线程终止。这样就有可能导致一些问题的出现,比如异常的时候无法回收一些系统资源,或者没有关闭当前的连接等等。
    
     那么如何在线程终止之前来处理这些未捕获的run方法抛出的异常呢?
    
    • Thread中有一个叫做UncaughtExceptionHandler的处理器接口,通过实现这个接口创建一个异常处理类。
    • 通过使用setUncaughtExceptionHandler方法为线程安装一个默认的处理器。
    package cn.wuxiaoer.thread;
    
    public class UncaughtExceptionHandlerImpl implements Thread.UncaughtExceptionHandler {
        /**
         * @param t  当前抛出异常的线程
         * @param e  未捕获的异常
         */
        @Override
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println(t.getName()+"抛出了异常:"+e.getStackTrace());
        }
    }
    
    package cn.wuxiaoer.thread;
    
    public class ThreadDemo implements Runnable {
        public static void main(String[] args) {
            Thread thread=  new Thread(new ThreadDemo(),"线程1");
            thread.setUncaughtExceptionHandler(new UncaughtExceptionHandlerImpl());
            thread.start();
        }
        @Override
        public void run() {
            int i=10/0;
            System.out.println(i);
        }
    }
    

    如果不为线程设置一个处理器,那么该线程的处理器就是该线程的线程组(ThreadGroup)对象。

    private ThreadGroup group;    
    
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
            return uncaughtExceptionHandler != null ?
                uncaughtExceptionHandler : group;
    }
    

线程组

   线程组(ThreadGroup)是一个可以统一管理的线程集合。它是一个实现Thread的UncaughtExceptionHandler接口的类。默认情况下创建的所有线程都属于相同的线程组。也可以手动设置线程的线程组。(建议不要在自己的程序中使用线程组!)

如果当前线程的线程组为空,则使用父线程的线程组:

 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
      // ................
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
      //................
    }

手动设置线程组:

package cn.wuxiaoer.thread;

public class ThreadDemo implements Runnable {
    public static void main(String[] args) {
       ThreadGroup group=new ThreadGroup("group1");
       new Thread(group,new ThreadDemo()).start();
    }
    @Override
    public void run() {
    }
}

五、线程中断

没有可以强制线程终止的方法,可以使用线程的interrupt方法来请求终止线程。

  1. interrupt:向线程发送中断申请,可能会抛出InterruptedException异常。

  2. isInterrupted:判断当前线程(正在执行这个命令的线程)是否被中断,会将当前线程的中断状态设置为false。

  3. Thread.interrupted:判断当前线程是否被中断。

package cn.wuxiaoer.thread;

public class ThreadDemo implements Runnable {
    public static void main(String[] args) {
        Thread thread=new Thread(new ThreadDemo(),"线程1");
        thread.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //终止线程
        thread.interrupt();

    }
    @Override
    public void run() {
        //检测线程是否被终止
        while (!Thread.currentThread().isInterrupted()){
            System.out.println(Thread.currentThread().getName());
        }
        System.out.println("线程被终止了!");
    }
}

六、Sleep、join、yield

  1. Sleep与wait

    • Sleep属于Thread类,是指让线程“暂停被调度一段时间”,或者说叫“挂起”一段时间,整个sleep过程除了修改“挂起“状态之外,不会动任何其他的”资源“。这些资源包括任何持有的任何形式的锁。
    • wait属于Object类,当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
  2. join

    官方的说明:Waits for this thread to die.

    • Thread类中的join方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。
    package cn.wuxiaoer.thread;
    
    public class ThreadDemo implements Runnable {
        public static void main(String[] args) throws InterruptedException {
            Thread t1=new Thread(new ThreadDemo(),"线程1");
            t1.start();
            //在主线程中调用了线程t1的join方法,那么必须要等t1线程执行完。
            t1.join();
            Thread t2=new Thread(new ThreadDemo(),"线程2");
            t2.start();
        }
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
    
  1. yield

    • 使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
    • 调用了yield()方法后,线程依然处于RUNNABLE状态,线程不会进入堵塞状态。
    • 可以使用yield实现自旋锁
    package cn.wuxiaoer.thread;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class CharacterPrint implements Runnable {
        private int state = 1;
        private Lock lock = new ReentrantLock();
        @Override
        public void run() {
            for (int i = 1; i <= 3; i++) {
                createThread(i);
            }
        }
        private void createThread(final int i) {
            new Thread(()->{
                try {
                    while (state != i) {
                        Thread.yield();
                    };
                    lock.lock();
                    for (int j = 1; j <= 50; j++) {
                        System.out.println(Thread.currentThread().getName()+":"+j);
                    }
                    state = i+1;
                } finally {
                    lock.unlock();
                }
            }, "thread" + i).start();
    
        }
        public static void main(String[] args) {
            new CharacterPrint().run();
        }
    }
    

线程状态图,图片来自于《知乎》

image-20210609182000162.png

七、ThreadLocal、WeakReference

  1. 线程局部变量(ThreadLocal)

       ThreadLocal为每个使用该变量的线程分配一个**独立的变量副本**。所以每一个线程都可以独立地改变自己的副本,而**不会影响**其他线程所对应的副本。
    

    ThreadLocal主要有以下四个核心的方法:

    • get():返回此线程局部变量的当前线程副本中的值。
    • withInitial():创建一个局部变量,其初始值通过给定的Supplier生成。默认的是初始值是空。
    protected T initialValue() {
        return null;
    }
    
    • remove():移除此线程局部变量当前线程的值,移除之后,get方法将返回null
    • set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。
    package cn.wuxiaoer.thread;
    
    import java.util.concurrent.ConcurrentHashMap;
    
    public class ThreadDemo implements Runnable {
        private  static  final ThreadLocal<Integer> number=ThreadLocal.withInitial(()->{return 0;});
        public static void main(String[] args) throws InterruptedException {
            for (int i = 1; i <=3; i++) {
                new Thread(new ThreadDemo(),"线程-"+i).start();
            }
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"初始值:"+number.get());
            for (int i = 1; i <=10; i++) {
                number.set(i);
                System.out.println(Thread.currentThread().getName()+":"+number.get());
            }
        }
    }
    

    备注:想更深入的理解可以参考:https://blog.csdn.net/zhoudaxia/article/details/37397575

  2. java弱引用(WeakReference)

       当一个对象仅仅被weak reference指向, 而没有任何其他strong reference指向的时候, 如果GC运行, 那么这个对象就会被回收
    

    首先看一下垃圾回收对象的条件:

    • 没有任何引用指向它
    • GC被运行
    package cn.wuxiaoer.thread;
    
    public class WeakReferenceDemo {
        public static void main(String[] args) {
            //强引用,在垃圾回收执行后依然存在
            User user=new User();
            user.setName("new");
            System.gc();
            System.out.println(user.getName());
            //弱引用,在垃圾回收后就被回收
            java.lang.ref.WeakReference<User> weakReference = new java.lang.ref.WeakReference<>(new User());
            weakReference.get().setName("weakReference");
            System.gc();
            System.out.println(weakReference.get().getName());
        }
    }
    

    备注:深入理解可以参考:https://www.cnblogs.com/zjj1996/p/9140385.htmlhttps://www.jianshu.com/p/964fbc30151a

  3. ThreadLocal与WeakReference的关系

    可以参考:

    https://blog.csdn.net/cainiao1412/article/details/106280496

    https://www.cnblogs.com/zxporz/p/10900852.html

     上面只是WeakReference的一些简单使用和说明,至于ThreadLocal中为什么使用WeakReference,或者不使用有什么问题,我也没看明白。
    

八、线程同步

条件对象和锁是线程同步的强大工具。

  1. 为什么会有线程同步问题?

     当两个或者多个线程同时访问共享数据并且它们尝试同时更改它们时,就会发生竞争条件,当发生竞争条件时就可能会导致共享数据错误,而为了避免多线程引起的对共享数据的错误,就需要采用同步机制来解决这种问题。
    
  2. 线程同步的两个重要元素:条件对象和锁。

  3. 条件对象

     要用一个条件来管理那些已经获得了一个锁确不能做有用工作的线程,它就是条件对象。
    
  4.  在HotSpot JVM实现中,锁有个专门的名字:**对象监视器(Object Monitor)**。**锁主要有以下作用:**
    
  • 保护代码片段,任何时刻只能有一个线程执行被保护的代码。
  • 锁可以管理视图进入被保护代码段的线程。
  • 锁可以拥有一个或多个相关的条件对象。
  • 每个条件对象管理那些已经进入被保护的代码段但是还不能运行的线程。

Java中常见的锁:

  • synchronized
  • java.util.concurrent.lock.ReentrantLock
  • java.util.concurrent.lock.ReentrantReadWriteLock

锁的分类:

  • 可重入锁

    不可重入锁:只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待。

    可重入锁:不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一。设计了加锁次数,以在解锁的时候,可以确保所有加锁的过程都解锁了,其他线程才能访问。

  • 公平锁 / 非公平锁

    公平锁:多个线程按照申请锁的顺序来获取锁。可以在创建锁的时候设置为公平锁,但是一般不建议这么干。例如:Lock lock=new ReentrantLock(true);

    非公平锁:指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程先获得锁。如 synchronized、Lock等默认的都是非公平锁。

  • 独享锁 / 共享锁

    独享锁:指锁一次只能被一个线程所持有。synchronized、java.util.concurrent.locks.ReentrantLock 都是独享锁

    共享锁:指锁可被多个线程所持有。ReadWriteLock 返回的 ReadLock 就是共享锁

  • 悲观锁 / 乐观锁

    悲观锁:一律会对代码块进行加锁,如 synchronized、java.util.concurrent.locks.ReentrantLock

    乐观锁:默认不会进行并发修改,通常采用 CAS 算法不断尝试更新。

  • 自旋锁

    自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环占有、浪费 CPU 资源。

  1. synchronized

     synchronized关键字可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块,同时synchronized可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到(保证可见性,完全可以替代Volatile功能),它的的作用主要有三个:
    
  1. 原子性:是指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行;
  2. 可见性:保证共享变量的修改能够及时可见,其实是通过Java内存模型中的 “对一个变量unlock操作之前,必须要同步到主内存中;如果对一个变量进行lock操作,则将会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值” 来保证的;
  3. 有序性:有效解决重排序问题,即 “一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作”;

synchronized关键字最主要有以下3种应用方式:归根结底它上锁的资源只有两类:一个是对象,一个是

  • 修饰实例方法,作用于当前实例(this)加锁,进入同步代码前要获得当前实例的锁。

  • 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁。

  • 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

    如下图:

    image-20210615122114133.png

备注:深入了解synchronized可以参考:https://blog.csdn.net/qq_36934826/article/details/95978700

https://juejin.cn/post/6844903600334831629

  1. Lock

    Lock是java.util.concurrent.locks下的接口,它的代表实现类是ReentrantLock(可重入锁)。

    Lock接口中有以下方法:

    public interface Lock {
        // 获取锁,如果锁已被其他线程获取,则进行等待。
        void lock();
        //当通过这个方法去获取锁时,如果线程 正在等待获取锁,则这个线程能够 响应中断,即中断线程的等待状态。
        void lockInterruptibly() throws InterruptedException;
        // 它表示用来尝试获取锁,如果获取成功,则返回true;如果获取失败(即锁已被其他线程获取),则返回false,不会进行阻塞,带参数的方法表示拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,同时可以响应中断。
        boolean tryLock(); 
        boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
        // 释放锁  
        void unlock();
        // 返回绑定到此 Lock 实例的新 Condition 实例 
        Condition newCondition(); 
    }
    

    Lock与synchronized的区别:

    • Synchronized持有了锁,除非执行完或者遇到异常时,才能释放锁,无法收到释放锁。
    • Lock可以使用手动调用unlock()方法进行解锁。
    • Synchronized读和读也会发生冲突,无法区分共享锁和独占锁。
    • Lock可以使用ReadWriteLock解决读写锁的问题。
    • Synchronized会自动释放锁,但是Lock必须手动释放锁,并且lock()方法和unlock方法必须一一对应。

    Condition用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效.

    Condition接口包含以下方法:

    public interface Condition {
        
        //使用该方法将线程await后,调用thread.interrupt()不会报错
        void awaitUninterruptibly();
        //将当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态,在返回时,该方法返回了所剩毫微秒数的一个估计值,以等待所提供的 nanostimeout 值的时间,如果超时,则返回一个小于等于 0 的值。
        long awaitNanos(long nanosTimeout) throws InterruptedException;
        //使用该方法将线程await后,此时调用thread.interrupt()会报错.
        void await() throws InterruptedException;
        //使用该方法将线程await后,此时调用thread.interrupt()会报错,可指定时间单位
        boolean await(long time, TimeUnit unit) throws InterruptedException;
        //使用该方法将线程await后,此时调用thread.interrupt()会报错,指定截止时间
        boolean awaitUntil(Date deadline) throws InterruptedException;
        //对应Object的notify(),唤起一个线程
        void signal();
        //对应Object的notifyAll(),唤起所有的线程
        void signalAll();
    }
    

    注意:调用Condition的await()和signal()方法,都必须在lock保护之内。

    package cn.wuxiaoer.thread;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ThreadDemo {
        private static  int i=0;
        private  static  int sum=0;
        static  Lock lock=new ReentrantLock();
        static Condition condition=lock.newCondition();
        public static void main(String[] args) throws InterruptedException {
            new Thread(()->{
                add();
            },"线程1").start();
            new Thread(()->{
                add();
            },"线程2").start();
            new Thread(()->{
                minus();
            },"线程3").start();
        }
        private  static  void  minus(){
            lock.lock();
            try {
                while (true&sum<=10){
                    if (i<=0){
                        condition.awaitUninterruptibly();
                    }
                    i--;
                    sum++;
                    System.out.println(Thread.currentThread().getName()+"减少了1:"+i);
                   condition.signalAll();
                }
            }finally {
                lock.unlock();
            }
        }
        private  static  void  add(){
            lock.lock();
            try {
                while (true&sum<=10){
                    if (i>0){
                        condition.awaitUninterruptibly();
                    }
                    i++;
                    sum++;
                    System.out.println(Thread.currentThread().getName()+"增加了1:"+i);
                    condition.signalAll();
                }
            }finally {
                lock.unlock();
            }
        }
    }
    
  2. Volatile

    volatile通常被比喻成"轻量级的synchronized",volatile是一个变量修饰符,只能用来修饰变量。无法修饰方法及代码块,并且volatile只能保证有序性和可见性,不能保证原子性的。

    • 有序性:由于处理器优化和指令重排等,而volatile可以禁止指令重排优化,保障程序执行的顺序按照代码的先后顺序执行。

    • 可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

      常见使用场景:

      • 状态量标记
      • DCL懒汉式单例模式(双重检验锁)

九、生产者与消费者

生产者和消费者问题是典型的线程通信的问题。解决这类问题主要有两种方法:管程法和信号灯法。

线程通信可以使用synchronized + wait + notify,也可以使用Lock+ Condition的await+signal

  1. 管程法

     管程法的主要思想是生产者 : 负责生产数据,消费者 : 负责处理数据,但是消费者不能直接使用生产者的数据 , 他们之间有个 “ 缓冲区生产者将生产好的数据放入缓冲区 , 消费者从缓冲区拿出数据。
    
    package cn.wuxiaoer.thread;
    
    public class ThreadDemo {
        public static void main(String[] args) throws InterruptedException {
            SynContainer synContainer=new SynContainer();
            new Thread(()->{
                for (int i = 0; i < 100; i++) {
                    synContainer.push(new Message("消息标题-"+i,"消息内容"));
    
                }
            },"生产者线程").start();
    
            new Thread(()->{
                for (int i = 0; i < 100; i++) {
                    synContainer.pop();
    
                }
            },"消费者线程").start();
        }
    }
    class SynContainer{
       private Message[] messages=new Message[10];
       private  int num=-1;
       public synchronized void push(Message message){
           if (num>=messages.length-1){
               try {
                   this.wait();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
           num++;
           messages[num]=message;
           System.out.println("生产者生产了消息:"+message.getTitle());
           this.notifyAll();
       }
       public  synchronized  Message pop(){
           if (num<0){
               try {
                   this.wait();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
           Message message=messages[num];
           num--;
           this.notifyAll();
           System.out.println("消费者消费了消息:"+message.getTitle());
           return  message;
       }
    }
    class Message{
        private String title;
        private  String content;
        Message(String title,String content){
            this.title=title;
            this.content=content;
        }
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    
    }
    
  1. 信号灯法

    信号灯法利用标识控制线程等待和唤醒,类似于信号灯,红灯性绿灯停。

package cn.wuxiaoer.thread;

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Question question=new Question();
        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                question.ask("问题-"+i);
            }
        },"生产者线程").start();

        new Thread(()->{
            for (int i = 0; i < 100; i++) {
                question.answer("答案:"+i);

            }
        },"消费者线程").start();
    }
}
class Question{
    private  boolean flag=false;
    private String content;
    public  synchronized  void  ask(String content){
        if (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.content=content;
        flag=!flag;
        this.notifyAll();
        System.out.println("问出来了问题:"+content);
    }

    public  synchronized  void  answer(String answer){
        if ((!flag)||content==null){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        flag=!flag;
        this.notifyAll();
        System.out.println("回答了问题:"+content+"-"+answer);
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,922评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,591评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,546评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,467评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,553评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,580评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,588评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,334评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,780评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,092评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,270评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,925评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,573评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,194评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,437评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,154评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容

  • 进程和线程 进程 所有运行中的任务通常对应一个进程,当一个程序进入内存运行时,即变成一个进程.进程是处于运行过程中...
    胜浩_ae28阅读 5,099评论 0 23
  • 1.多线程之等待与通知机制1.1什么是等待通知机制?在生活中,如我们去饭店,服务员拿菜单给我们点菜,然后记录完告诉...
    学编程的小屁孩阅读 129评论 0 1
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,955评论 1 18
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,452评论 1 15
  • 目录介绍 5.0.0.1 线程池具有什么优点和缺点?为什么说开启大量的线程,会降低程序的性能,那么该如何做才能降低...
    杨充211阅读 410评论 0 3