Java基础理论之多线程

本文深入探讨Java中的多线程概念,包括线程创建、线程安全、线程状态以及线程池的使用。介绍了Thread类、Runnable接口、线程同步方法,并通过实例展示了线程池的优势和常见实现,帮助开发者理解并掌握Java多线程编程。

多线程

对于大数据量的操作,建议使用开启线程的方式进行完成,避免了程序占用时间,多线程会提高执行效率但也伴随相应的线程安全的风险。微服务同一代码部署到多个服务上也会出现线程安全问题,此篇仅介绍非分布式的线程管理。

引入线程

  • 并行:指两个或多个事件在同一时刻发生(同时发生)。
  • 并发:指两个或多个事件在同一个时间段内发生。

注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
  • 线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

进程与线程的区别

  • 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
  • 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。

注意:下面内容为了解知识点

1:因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。

2:Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。

3:由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。

线程调度:

计算机通常只有一个CPU时,在任意时刻只能执行一条计算机指令,每一个进程只有获得CPU的使用权才能执行指令。所谓多进程并发运行,从宏观上看,其实是各个进程轮流获得CPU的使用权,分别执行各自的任务。那么,在可运行池中,会有多个线程处于就绪状态等到CPU,JVM就负责了线程的调度。JVM采用的是抢占式调度,没有采用分时调度,因此可以能造成多线程执行结果的的随机性。

多线程原理

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。

多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈。

Thread类

简介

Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。

所属包

java.lang 
Class Thread
java.lang.Object 
java.lang.Thread 

构造方法

  • public Thread():分配一个新的线程对象。
  • public Thread(String name):分配一个指定名字的新的线程对象。
  • public Thread(Runnable target):分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

常用方法:

public String getName():获取当前线程名称。
public void setName(String name) 将此线程的名称更改为等于参数 name 。
public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void run():此线程要执行的任务在此处定义代码。
public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
public static Thread currentThread() :返回对当前正在执行的线程对象的引用。
public int getPriority() 返回此线程的优先级
public void setPriority(int newPriority) 更改此线程的优先级 

void setDaemon(boolean on) 将此线程标记为 daemon线程或用户线程  当其他线程执行完毕后守护线程将结束 无论是否执行完毕

run方法和start方法的区别:

run:封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程

star:启动线程,然后有Jvm调用此线程的run方法

Runnable

所属包

java.lang 
Interface Runnable

简介:

通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

tips:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。

创建线程

通过Thread类 创建线程

方式一 通过继承Thread 重写run 方法 实现线程

Java中通过继承Thread类来创建启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

方式二 通过创建Runnable对象实现 实现run方法 实现线程

1.定义Runnable对象 (1.匿名内部类,2.继承接口 ,3.lambda表达式 实现接口)
2.实现run方法
3.定义Tread类 构造方法传入Runnable对象
4.调用start()启用该线程

例子:

 public  static  void testThread(){
	
        // 方式1
        Thread thread = new ThreadOne();
        thread.start();
        new Thread(()->System.out.println("lambda 创建线程")).run();
        // 方式2
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~");
                System.out.println("测试runnable");
            }
        };

        Thread thread1 = new Thread(runnable);
        thread1.start();
        // 方式3
        Runnable runnable1 =()->{
            System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~");
            System.out.println("测试runnable lambda");
        };

        Thread thread2 = new Thread(runnable1);
       thread2.start();

       // 方式4
        Thread thread3 = new Thread(new RunnableOne());
        thread3.start();

    }
    static class ThreadOne extends  Thread{
        @Override
        public void run() {
            System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~");
            for (int i = 0; i < 1; i++) {
                System.out.println(i);
            }
        }
    }

    static class RunnableOne implements Runnable{
        @Override
        public void run() {
            System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~");
            System.out.println("测试runnable 继承接口");
        }
    }

守护线程

void setDaemon(boolean on) 将此线程标记为 daemon线程或用户线程  当其他线程执行完毕后守护线程将结束 无论是否执行完毕

变量副本

当共享数据改变的时候 是先改变当前线程的变量副本 然后在去改变共享数据, 可能存在改变了共享数据 其他线程仍使用变量副本中变量没有更新变量的问题
此时需要在共享数据上 添加  volatile 关键字 (不能保证数据的原子性) 也可以在需要使用共享数据的地方加入同步代码块 同步代码块在运行时会清空变量副本

 乐观锁 悲观锁

 ​​​​​​​

 并发工具类

多个线程执行完毕后 再执行之后代码 

CountDownLatch:

所属包 方法:

包
java.util.concurrent Class CountDownLatch

构造
public CountDownLatch(int count)构造一个以给定计数 CountDownLatch CountDownLatch。 

oid await() 导致当前线程等到锁存器计数到零,除非线程是 interrupted 。  
boolean await(long timeout, TimeUnit unit) 使当前线程等待直到锁存器计数到零为止,除非线程为 interrupted或指定的等待时间过去。  
void countDown() 减少锁存器的计数,如果计数达到零,释放所有等待的线程。  
long getCount() 返回当前计数。  
String toString() 返回一个标识此锁存器的字符串及其状态。 

使用:

1.创建计数器对象 ; 2.将计数器对象传入run方法 执行 等待或计数  3.线程执行完指定次数后执行await后的代码

semaphore

控制访问特定资源场景

所属包 方法:

包
java.lang.Object java.util.concurrent.Semaphore 
构造:
Semaphore(int permits) 
创建一个 Semaphore与给定数量的许可证和非公平公平设置。  
Semaphore(int permits, boolean fair) 
创建一个 Semaphore与给定数量的许可证和给定的公平设置。 

方法  
void acquire() 从该信号量获取许可证,阻止直到可用,或线程为 interrupted 。  
void acquire(int permits) 从该信号量获取给定数量的许可证,阻止直到所有可用,否则线程为 interrupted 。  

void release() 释放许可证,将其返回到信号量。  
void release(int permits) 释放给定数量的许可证,将其返回到信号量。  

 1. 获取Semaphore 对象 2.获取通行证 3.执行代码4.归还通行证

线程池创建线程

概述

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:

合理利用线程池能够带来三个好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

所属包

java.util.concurrent 
Class Executors

启动线程池

Executors类中有个创建线程池的方法如下:

  • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)

获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:

Runnable 

  • public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行

    Future接口:用来记录线程任务执行完毕后产生的结果。

使用线程池中线程对象的步骤:

Callable:

  • <T> Future<T> submit(Callable<T> task) : 获取线程池中的某一个线程对象,并执行.

    Future : 表示计算的结果.

  • V get() : 获取计算完成的结果。

常用线程池创建

newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

这种类型的线程池特点是:
(1)工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程(2)将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
(3)在使用CachedThreadPool时,一定要注意控制任务的数量,否则,由于大量线程同时运行,很有会造成系统瘫痪。

newFixedThreadPool

创建一个指定工作线程数量的线程池(初始为0,最大为指定数)。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。

FixedThreadPool是一个典型且优秀的线程池,它具有线程池提高程序效率和节省创建线程时所耗的开销的优点。但是,在线程池空闲时,即线程池中没有可运行任务时,它不会释放工作线程,还会占用一定的系统资源。

ThreadPoolExecutor

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 
创建一个新 ThreadPoolExecutor给定的初始参数。 

参数:
核心线程数量,最大线程数量,空闲线程最大存活时间,
时间单位:java.util.concurrent Enum TimeUnit ;
任务队列:java.util.concurrent Class ArrayBlockingQueue<E>;
创建线程工厂: Executors :static ThreadFactory defaultThreadFactory() 返回用于创建新线程的默认线程工厂。 
任务的拒绝策略:java.util.concurrent Class ThreadPoolExecutor.AbortPolicy 丢弃任务抛出异常
java.util.concurrent Class ThreadPoolExecutor.CallerRunsPolicy   绕过池子直接执行run犯法
java.util.concurrent Class ThreadPoolExecutor.DiscardOldestPolicy  丢弃任务不抛异常
java.util.concurrent Class ThreadPoolExecutor.DiscardPolicy  抛弃队列中等待最久的 任务加入队列



ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,60,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());

newSingleThreadExecutor

创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。

newScheduleThreadPool

创建一个定长的线程池,而且支持定时的以及周期性的任务执行,支持定时及周期性任务执行。

线程池使用案例

public static  void practiceExcutor(){
        // 无限大小的线程池   使用 runnable 接口
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
//        usePool(cachedThreadPool);
        // 创建一个指定大小的线程池        // 使用callable 接口接收future 的返回值
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
//        usePool(fixedThreadPool);
        //创建一个单线程的线程池
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        usePool(singleThreadExecutor);
        // 创建一个延迟执行的线程池
        ScheduledExecutorService  scheduledThreadPool = Executors.newScheduledThreadPool(2);
//        usePool(scheduledThreadPool);


    }



    /**
     * 使用线程池
     */
    private static void usePool(ExecutorService executor) {
        ArrayList<Future<String>> futures = new ArrayList<>();
        for(int i = 0; i < 3; i++){
                final int index = i;

            if (executor instanceof ScheduledExecutorService ){
                try {
                    Thread.sleep(500L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ((ScheduledExecutorService) executor).schedule(new Runnable(){
                        public void run(){
                            System.out.println(index+Thread.currentThread().getName());
                            System.out.println("延迟3秒");
                        }
                    }, 3, TimeUnit.SECONDS);

            } else {
                executor.execute(()->System.out.println(Thread.currentThread().getName()+"   execute 方式启动"));
                executor.submit(()->System.out.println(Thread.currentThread().getName()+"    submit  方式启动"));
                futures.add(executor.submit(new Callable<String>(){

                    @Override
                    public String call() throws Exception {
                            return index+Thread.currentThread().getName()+"    callable方式启动";
                    }
                }));

            }

        }
        futures.forEach(a->{
            try {
                System.out.println(a.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
    }

线程安全问题

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
Java中提供了同步机制(synchronized)来解决。

线程同步

有三种方式完成同步操作:

  1. 同步代码块。(锁是共享数据)
  2. 同步方法。(锁是this ,同步静态方法锁是 类名.class)
  3. 锁机制。

同步代码块

  • 同步代码块synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:

synchronized(同步锁){
     需要同步操作的代码
}

同步锁:

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

  1. 锁对象 可以是任意类型。

  2. 多个线程对象 要使用同一把锁。

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

同步方法

  • 同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

格式:

public synchronized void method(){
   	可能会产生线程安全问题的代码
}

同步锁是谁?

​ 对于非static方法,同步锁就是this。

​ 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

Lock锁

java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

ReentrantLock

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

  • public void lock() :加同步锁。
  • public void unlock():释放同步锁。

示例代码

 public static void practiceLock() {

        Student lock = new Student(1, "同步");
        // 同步代码块 内部锁
        RunnableZero runnableOne = new RunnableZero();
        new Thread(runnableOne, "同步代码块1").start();
        new Thread(runnableOne, "同步代码块2").start();
        // 测试 外部锁  联系两个Runnable
        RunnableOne runnableOne1 = new RunnableOne(lock);
        RunnableOne runnableOne2 = new RunnableOne(lock);
        new Thread(runnableOne1, "同步代码块3").start();
        new Thread(runnableOne2, "同步代码块4").start();
        // 同步方法
        RunnableTwo runnableTwo = new RunnableTwo();
        new Thread(runnableTwo, "同步方法1").start();
        new Thread(runnableTwo, "同步方法2").start();
        // lock锁
        RunnableThree runnableThree = new RunnableThree();
        new Thread(runnableThree, "lock锁 锁1").start();
        new Thread(runnableThree, "lock锁 锁2").start();

    }

    /**
     * 使用类中变量作为同步代码块的锁
     */
    static class RunnableZero implements Runnable {
        int[] intArray = {1, 2, 3};

        @Override
        public void run() {

            synchronized (intArray) {
                for (int i : intArray) {
                    System.out.println(Thread.currentThread().getName());
                    System.out.println(i);
                   /* try {
                        Thread.sleep(100L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }*/
                }
            }

        }
    }

    /**
     * 使用 外部变量作为同步代码块的锁   多个Runnable 共用同一变量
     * @param <T>
     */
    static class RunnableOne<T> implements Runnable {
        int[] intArray = {1, 2, 3};
        Student student;

        public RunnableOne(Student student) {
            this.student = student;
        }

        @Override
        public void run() {
            synchronized (student /*intArray*/) {
                for (int i : intArray) {
                    System.out.println(Thread.currentThread().getName());
                    System.out.println(i);
                   /* try {
                        Thread.sleep(100L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }*/
                }
            }
        }
    }

    /**
     * 同步方法的使用
     * @param <T>
     */
    static class RunnableTwo<T> implements Runnable {
        int[] intArray = {1, 2, 3};


        @Override
        public void run() {
            printArray(intArray);


        }

        /**
         * 同步方法 使用 synchronized修饰方法
         * @param intArray
         */
        public synchronized static void printArray(int[] intArray) {
            for (int i : intArray) {
                System.out.println(Thread.currentThread().getName());
                System.out.println(i);
               /* try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }*/
            }
        }
    }

    /**
     * 使用lock锁  注意try catch finally使用 , 注意lock的实现子类
     * @param <T>
     */
    static class RunnableThree<T> implements Runnable {
        int[] intArray = {1, 2, 3};
        Lock lock = new ReentrantLock();

        @Override
        public void run() {
            lock.lock();
            try {
                for (int i : intArray) {
                    System.out.println(Thread.currentThread().getName());
                    System.out.println(i);
                   /* try {
                        Thread.sleep(100L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }*/
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        }
    }


线程状态

线程状态概述

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态:

这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析

线程状态导致状态发生条件
NEW(新建)线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。


我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable(可运行)状态与非运行状态之间的转换问题。

线程的等待唤醒

睡眠sleep方法

我们看到状态中有一个状态叫做计时等待,可以通过Thread类的方法来进行演示.

public static void sleep(long time) 让当前线程进入到睡眠状态,到毫秒后自动醒来继续执行

public class Test{
  public static void main(String[] args){
    for(int i = 1;i<=5;i++){
      	Thread.sleep(1000);
        System.out.println(i)   
    } 
  }
}

这时我们发现主线程执行到sleep方法会休眠1秒后再继续执行。

3.3 等待和唤醒

Object类的方法

public void wait() : 让当前线程进入到等待状态 此方法必须锁对象调用.

public class Demo1_wait {
    public static void main(String[] args) throws InterruptedException {
	   // 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.
        new Thread(() -> {
            try {

                System.out.println("begin wait ....");
                synchronized ("") {
                    "".wait();
                }
                System.out.println("over");
            } catch (Exception e) {
            }
        }).start();
    }

public void notify() : 唤醒当前锁对象上等待状态的线程 此方法必须锁对象调用.

public class Demo2_notify {
    public static void main(String[] args) throws InterruptedException {
	   // 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.
        new Thread(() -> {
            try {

                System.out.println("begin wait ....");
                synchronized ("") {
                    "".wait();
                }
                System.out.println("over");
            } catch (Exception e) {
            }
        }).start();

        //步骤2:  加入如下代码后, 3秒后,会执行notify方法, 唤醒wait中线程.
        Thread.sleep(3000);
        new Thread(() -> {
            try {
                synchronized ("") {
                    System.out.println("唤醒");
                    "".notify();
                }
            } catch (Exception e) {
            }
        }).start();
    }
}

阻塞队列:

 BlockingQueue:

所属包 与常用方法

java.util.concurrent 
Interface BlockingQueue<E>

void put(E e) 将指定的元素插入到此队列中,等待空格可用。 
E take() 检索并删除此队列的头,如有必要,等待元素可用。 

已知实现类: 
ArrayBlockingQueue , DelayQueue , LinkedBlockingDeque , LinkedBlockingQueue , LinkedTransferQueue , PriorityBlockingQueue , SynchronousQueue 

1 为容器数量
 ArrayBlockingQueue<String> abq = new ArrayBlockingQueue<>(1);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值