java路线-----多线程
2021/7/25 20:39:40
本文主要是介绍java路线-----多线程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
线程与进程
进程是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
线程是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程
线程调度
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核心而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使用率更高。
线程让出 cpu 的情况:
- 当前运行线程主动放弃 CPU,JVM 暂时放弃 CPU 操作(基于时间片轮转调度的 JVM 操作系统不会让线程永久放弃 CPU,或者说放弃本次时间片的执行权),例如调用 yield()方法。
- 当前运行线程因为某些原因进入阻塞状态,例如阻塞在 I/O 上。
- 当前运行线程结束,即运行完 run()方法里面的任务。
同步与异步
同步:排队执行 , 效率低但是安全.
异步:同时执行 , 效率高但是数据不安全.并发与并行
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)
实现多线程
1 继承Thread类
Thread 类本质上是实现了 Runnable 接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过 Thread 类的 start()实例方法。start()方法是一个 native 方法,它将启动一个新线程,并执行 run()方法。
public class MyThread extends Thread { public void run() { System.out.println("MyThread.run()"); } } MyThread myThread1 = new MyThread(); myThread1.start();
案例
上面案例的执行顺序是这样的:
每个线程都有自己的栈空间,共用一块堆内存。由一个线程调用的方法那么这个方法也会执行在这个线程里边。
2 实现Runnable接口
如果自己的类已经 extends 另一个类,就无法直接 extends Thread,此时,可以实现一个Runnable 接口。
打开API也可以看到很多关于Thread类的方法(关于API的下载关注公众号:小小李童鞋回复JavaAPI获取)
设置和获取线程名称
可以用对象调用它的setName方法设置名称。
线程的休眠可以调用它的sleep方法
每隔一秒执行一次循环
线程阻塞
其实就是所有比较消耗时间的操作,比如说读取文件,接收用户输入数据等
线程中断
当主线程执行完毕,就调用子线程的中断方法,子线程捕获到异常然后return掉,子线程就被杀死了。
守护线程
定义:守护线程–也称“服务线程”,他是后台线程,它有一个特性,即为用户线程 提供 公共服务,在没有用户线程可服务时会自动离开。
2. 优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。
3. 设置:通过 setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为守护线程的方式是在 线程对象创建 之前 用线程对象的 setDaemon 方法。
4. 在 Daemon 线程中产生的新线程也是 Daemon 的。
5. 线程则是 JVM 级别的,以 Tomcat 为例,如果你在 Web 应用中启动一个线程,这个线程的生命周期并不会和 Web 应用程序保持同步。也就是说,即使你停止了 Web 应用,这个线程依旧是活跃的。
6. example: 垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是 JVM 上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。
7. 生命周期:守护线程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。当 JVM 中所有的线程都是守护线程的时候,JVM 就可以退出了;如果还有一个或以上的非守护线程则 JVM 不会退出
子线程和上边的继承Runnable接口一样,这里改动就是将子线程设置为守护线程。
3 线程安全问题
举例:售票窗口
当new了三个线程同时进行卖票操作就出现了线程不安全问题
线程安全问题解决方案
1同步代码块-------synchronized
通过同步代码块改进后的代码
三个线程看同一把锁,线程安全得到了解决,但是效率比较低了
反例,如果每个线程看自己的锁,那么依然是线程不安全的
2 同步方法
通过给方法设置关键字synchronized来保证线程安全,改进后的代码,把上边的if里边的语句单独抽出来为一个方法
如果这里创建三个对象那么这个程序就错误了,因为是三个对象分别卖他们的十张票,
所以,切记我们用的是同一个对象
3显示锁–Lock解决线程不安全问题
上面两个都是隐式锁,自己完成锁定解锁过程。
显示锁需要手动解锁,锁上,这个过程
4公平锁和非公平锁
显示锁有一个参数:
5线程死锁
两个线程互相调用对方,导致双方都被锁上,看下边这个案例:
package thread; public class Demo { public static void main(String[] args) { //线程死锁 Culprit c = new Culprit(); Police p = new Police(); new MyThread(c,p).start(); c.say(p); } static class MyThread extends Thread{ private Culprit c; private Police p; MyThread(Culprit c,Police p){ this.c = c; this.p = p; } @Override public void run() { p.say(c); } } static class Culprit{ public synchronized void say(Police p){ System.out.println("罪犯:你放了我,我放了人质"); p.fun(); } public synchronized void fun(){ System.out.println("罪犯被放了,罪犯也放了人质"); } } static class Police{ public synchronized void say(Culprit c){ System.out.println("警察:你放了人质,我放了你"); c.fun(); } public synchronized void fun(){ System.out.println("警察救了人质,但是罪犯跑了"); } } }
6生产者与消费者问题
两个线程在合作的时候,如果不加锁机制,可能会发生数据错乱,看下边这个例子,其中name 和taste就发生了错误,因为在线程休眠的期间另一个线程可能就完成了操作。
另一个问题,加了这个锁机制,因为只是给它做饭的步骤那里加的,会出现一个问题,厨师可能一直在那里做饭,或者服务员一直在那里端饭,理想状态是一人一次
最终代码改进。设置一个flag,为true表示厨师可以做饭,当做完饭的时候改为false,并唤醒当前沉睡的线程自己进入休眠,服务员里的get方法也一样。
package thread; public class Demo12 { public static void main(String[] args) { //多线程通信 生产者与消费者问题 Food f = new Food(); new Cook(f).start(); new Waiter(f).start(); } //厨师 static class Cook extends Thread{ private Food f; public Cook(Food f) { this.f = f; } @Override public void run() { for (int i = 0; i < 100; i++) { if(i%2==0){ f.setNameAndTaste("老干妈小米粥","香辣味"); }else { f.setNameAndTaste("煎饼果子","甜辣味"); } } } } //服务员 static class Waiter extends Thread{ private Food f; public Waiter(Food f) { this.f = f; } @Override public void run() { for (int i = 0; i < 100; i++) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } f.get(); } } } //食物 static class Food{ private String name; private String taste; //true表示可以生产 boolean flag = true; public synchronized void setNameAndTaste(String name,String taste){ if(flag){ this.name = name; try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } this.taste = taste; flag = false; this.notifyAll(); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void get(){ if(!flag){ System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste); flag = true; this.notifyAll(); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
线程的六种状态
关于线程的六种状态详细介绍可以看这篇文章
线程生命周期(状态)
当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,它要经过新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5 种状态。尤其是当线程启动以后,它不可能一直"霸占"着 CPU 独自运行,所以 CPU 需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换
1. 新建状态(NEW)
当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值
2. 就绪状态(RUNNABLE)
当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行。
3. 运行状态(RUNNING):
如果处于就绪状态的线程获得了 CPU,开始执行 run()方法的线程执行体,则该线程处于运行状态。
4. 阻塞状态(BLOCKED):
阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
4.1等待阻塞(o.wait->等待对列):
运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue)中。
4.2同步阻塞(lock->锁池)
运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。
4.3其他阻塞(sleep/join)
运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O处理完毕时,线程重新转入可运行(runnable)状态。
5. 线程死亡(DEAD)
线程会以下面三种方式结束,结束后就是死亡状态。
正常结束
- run()或 call()方法执行完成,线程正常结束。
异常结束 - 线程抛出一个未捕获的 Exception 或 Error。
调用 stop - 直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用
6终止线程 4 种方式
1. 正常运行结束
程序运行结束,线程自动结束。
2. 使用退出标志退出线程
一般 run()方法执行完,线程就会正常结束,然而,常常有些线程是伺服线程。它们需要长时间的运行,只有在外部某些条件满足的情况下,才能关闭这些线程。使用一个变量来控制循环,例如:
最直接的方法就是设一个 boolean 类型的标志,并通过设置这个标志为 true 或 false 来控制 while
循环是否退出,代码示例:
public class ThreadSafe extends Thread { public volatile boolean exit = false; public void run() { while (!exit){ //do something } } }
定义了一个退出标志 exit,当 exit 为 true 时,while 循环退出,exit 的默认值为 false.在定义 exit时,使用了一个 Java 关键字 volatile,这个关键字的目的是使 exit 同步,也就是说在同一时刻只能由一个线程来修改 exit 的值。
3. Interrupt 方法结束线程
使用 interrupt()方法来中断线程有两种情况:
- 线程处于阻塞状态:如使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时,会使线程处于阻塞状态。当调用线程的 interrupt()方法时,会抛出 InterruptException 异常。阻塞中的那个方法抛出这个异常,通过代码捕获该异常,然后 break 跳出循环状态,从而让我们有机会结束这个线程的执行。通常很多人认为只要调用 interrupt 方法线程就会结束,实际上是错的, 一定要先捕获 InterruptedException 异常之后通过 break 来跳出循环,才能正常结束 run 方法。
- 线程未处于阻塞状态:使用 isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置 true,和使用自定义的标志来控制循环是一样的道理。
public class ThreadSafe extends Thread { public void run() { while (!isInterrupted()){ //非阻塞过程中通过判断中断标志来退出 try{ Thread.sleep(5*1000);//阻塞过程捕获中断异常来退出 }catch(InterruptedException e){ e.printStackTrace(); break;//捕获到异常之后,执行 break 跳出循环 } } } }
4. stop 方法终止线程(线程不安全)
程序中可以直接使用 thread.stop()来强行终止线程,但是 stop 方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,不安全主要是:thread.stop()调用之后,创建子线程的线程就会抛出 ThreadDeatherror 的错误,并且会释放子线程所持有的所有锁。一般任何进行加锁的代码块,都是为了保护数据的一致性,如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。因此,并不推荐使用 stop 方法来终止线程。
sleep 与 wait 区别
- 对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于Object 类中的。
- sleep()方法导致了程序暂停执行指定的时间,让出 cpu 该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
- 在调用 sleep()方法的过程中,线程不会释放对象锁。
- 而当调用 wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
start 与 run 区别
- start()方法来启动线程,真正实现了多线程运行。这时无需等待 run 方法体代码执行完毕,可以直接继续执行下面的代码。
- 通过调用 Thread 类的 start()方法来启动一个线程, 这时此线程是处于就绪状态, 并没有运行。
- 方法 run()称为线程体,它包含了要执行的这个线程的内容,线程进入了运行状态,开始运行 run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。
线程的基本方法
线程相关的基本方法有 wait,notify,notifyAll,sleep,join,yield 等。
1. 线程等待(wait)
调用该方法的线程进入 WAITING 状态,只有等待另外线程的通知或被中断才会返回,需要注意的是调用 wait()方法后,会释放对象的锁。因此,wait 方法一般用在同步方法或同步代码块中。
2. 线程睡眠(sleep)
sleep 导致当前线程休眠,与 wait 方法不同的是 sleep 不会释放当前占有的锁,sleep(long)会导致线程进入 TIMED-WATING 状态,而 wait()方法会导致当前线程进入 WATING 状态
3. 线程让步(yield)
yield 会使当前线程让出 CPU 执行时间片,与其他线程一起重新竞争 CPU 时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 CPU 时间片,但这又不是绝对的,有的操作系统对线程优先级并不敏感。
4. 线程中断(interrupt)
中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)。
- 调用 interrupt()方法并不会中断一个正在运行的线程。也就是说处于 Running 状态的线程并不会因为被中断而被终止,仅仅改变了内部维护的中断标识位而已。
- 若调用 sleep()而使线程处于 TIMED-WATING 状态,这时调用 interrupt()方法,会抛出InterruptedException,从而使线程提前结束 TIMED-WATING 状态。
- 许多声明抛出 InterruptedException 的方法(如 Thread.sleep(long mills 方法)),抛出异常前,都会清除中断标识位,所以抛出异常后,调用 isInterrupted()方法将会返回 false。
- 中断状态是线程固有的一个标识位,可以通过此标识位安全的终止线程。比如,你想终止一个线程 thread 的时候,可以调用 thread.interrupt()方法,在线程的 run 方法内部可以根据 thread.isInterrupted()的值来优雅的终止线程。
5. Join 等待其他线程终止
join() 方法,等待其他线程终止,在当前线程中调用一个线程的 join() 方法,则当前线程转为阻塞状态,直到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu 的宠幸。(join线程其实是一个VIP线程插队的过程)
6. 为什么要用 join()方法?
很多情况下,主线程生成并启动了子线程,需要用到子线程返回的结果,也就是需要主线程需要在子线程结束后再结束,这时候就要用到 join() 方法。
System.out.println(Thread.currentThread().getName() + "线程运行开始!"); Thread6 thread1 = new Thread6(); thread1.setName("线程 B"); thread1.join(); System.out.println("这时 thread1 执行完毕之后才能执行主线程");
7. 线程唤醒(notify)
Object 类中的 notify() 方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() 方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞
争。类似的方法还有 notifyAll() ,唤醒再次监视器上等待的所有线程。
8. 其他方法:
- sleep():强迫一个线程睡眠N毫秒。
- isAlive(): 判断一个线程是否存活。
- join(): 等待线程终止。
- activeCount(): 程序中活跃的线程数。
- enumerate(): 枚举程序中的线程。
- currentThread(): 得到当前线程。
- isDaemon(): 一个线程是否为守护线程。
- setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线
程依赖于主线程结束而结束) - setName(): 为线程设置一个名称。
- wait(): 强迫一个线程等待。
- notify(): 通知一个线程继续运行。
- setPriority(): 设置一个线程的优先级。
- getPriority()::获得一个线程的优先级。
带返回值的线程Callable
Runnable 与 Callable
接口定义
//Callable接口
public interface Callable {
V call() throws Exception;
}
//Runnable接口
public interface Runnable {
public abstract void run();
}
Callable使用步骤
- 编写类实现Callable接口 , 实现call方法
class XXX implements Callable {
@Override
public call() throws Exception {
return T;
}
} - 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask future = new FutureTask<>(callable); - 通过Thread,启动线程
new Thread(future).start()
Runnable 与 Callable的相同点
都是接口
都可以编写多线程程序
都采用Thread.start()启动线程
Runnable 与 Callable的不同点
Runnable没有返回值;Callable可以返回执行结果
Callable接口的call()允许抛出异常;Runnable的run()不能抛出
Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
具体案例:
通过运行结果可以看到两个线程在执行的过程中因为子线程没有调用get方法获取返回值所以主线程不会被阻塞,因此两个线程抢占的时间片是相等的。但是下边通过给子线程调用get方法获取callable的返回值此时主线程会被阻塞。优先执行子线程,子线程执行完毕主线程才会继续执行。
线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的好处1 降低资源消耗。2 提高响应速度。3 提高线程的可管理性。
通常设置这样一个线程池,有定长的 还有不定长的
定长的
创建了一个线程池,刚开始线程池里的四个线程都是空闲状态,随着任务列表里加入了任务,空闲的线程会去领任务,同时将状态改为忙碌的状态。当A把它的任务执行完成可以接收任务列表新的等待的任务,那么会造成D线程一直出于“饥饿状态”,加入你有500个任务那么创建800个容量的线程池可能会造成资源浪费,所以合理设计线程池是必要的。
当四个线程都领到任务处于忙碌的状态时,任务列表中的任务会处于等待状态,但是对于非定长的线程池,他会自己再扩充容量,以便完成任务列表中的任务,经过一段时间后,他会把一些一直都处于空闲的线程在释放掉。
线程池的实现原理
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。他的主要特点为:线程复用;控制最大并发数;管理线程。
线程池的组成
一般的线程池主要分为以下 4 个组成部分:
- 线程池管理器:用于创建并管理线程池
- 工作线程:线程池中的线程
- 任务接口:每个任务必须实现的接口,用于工作线程调度其运行
- 任务队列:用于存放待处理的任务,提供一种缓冲机制
Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor,Executors,ExecutorService,ThreadPoolExecutor ,Callable 和 Future、FutureTask 这几个类。
拒绝策略
线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列也已经排满了,再也塞不下新任务了。这时候我们就需要拒绝策略机制合理的处理这个问题。
JDK 内置的拒绝策略如下: - AbortPolicy : 直接抛出异常,阻止系统正常运行。
- CallerRunsPolicy : 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。
- DiscardOldestPolicy : 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
- DiscardPolicy : 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢失,这是最好的一种方案。
以上内置拒绝策略均实现了 RejectedExecutionHandler 接口,若以上策略仍无法满足实际需要,完全可以自己扩展 RejectedExecutionHandler 接口。
1缓存线程池
/** * 缓存线程池. * (长度无限制) * 执行流程: * 1. 判断线程池是否存在空闲线程 * 2. 存在则使用 * 3. 不存在,则创建线程 并放入线程池, 然后使用 */ ExecutorService service = Executors.newCachedThreadPool(); //向线程池中 加入 新的任务 service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } });
2 定长线程池
package thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Demo14 { /*定长线程池 长度是指定的线程池 加入任务后的执行流程 1 判断线程池是否存在空闲线程 2 存在则使用 3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用 4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程 **/ public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(2); service.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }); service.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }); service.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); } }); } }
3 单线程线程池
package thread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Demo15 { /*单线程线程池 执行流程 1 判断线程池的那个线程是否空闲 2 空闲则使用 3 不空闲则等待它空闲后再使用 **/ public static void main(String[] args) { ExecutorService service = Executors.newSingleThreadExecutor(); service.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); } }); service.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); } }); service.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); } }); } }
4 周期定长线程池
package thread; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class Demo16 { /*周期任务 定长线程池 执行流程 1 判断线程池是否存在空闲线程 2 存在则使用 3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用 4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程 周期性任务执行时 定时执行 当某个任务触发时 自动执行某任务 **/ public static void main(String[] args) { ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2); //定时执行一次 //参数1:定时执行的任务 //参数2:时长数字 //参数3:2的时间单位 Timeunit的常量指定 /* scheduledExecutorService.schedule(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); } },5, TimeUnit.SECONDS); //5秒钟后执行*/ /* 周期性执行任务 参数1:任务 参数2:延迟时长数字(第一次在执行上面时间以后) 参数3:周期时长数字(没隔多久执行一次) 参数4:时长数字的单位 * **/ scheduledExecutorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); } },5,1,TimeUnit.SECONDS); } }
LamBda表达式
Lambda表达式是一个函数式接口,只关注结果
用函数式编程:
再看一个案例:
两个数求和,使用Lambda表达式改进后
其实也就是删除了创建对象的过程。
为什么使用Lambda表达式:
1避免匿名内部类定义过多
2可以让代码看起来更简洁
3去掉了一堆没有意义的代码,只留下核心的逻辑
以上内容是关于java多线程的一些基本知识点,后续请关注公众号(小小李童鞋)获取多线程面试考点。
这篇关于java路线-----多线程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-19JAVA分布式id教程:轻松入门与实践
- 2024-11-19Java高并发教程:入门与实践指南
- 2024-11-19JAVA高并发直播教程:新手入门指南
- 2024-11-19Java高并发直播教程:入门与实践指南
- 2024-11-19Java微服务教程:初学者快速入门指南
- 2024-11-19JAVA微服务教程:新手入门的详细指南
- 2024-11-19Java微服务教程:从零开始搭建你的第一个微服务应用
- 2024-11-19Java项目开发教程:初学者必备指南
- 2024-11-19Java项目开发教程:新手快速入门指南
- 2024-11-19Java项目开发教程:零基础入门到实战