多线程
1.线程与进程
1.1线程与进程
进程
- 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
线程
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程
- 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程
1.2线程调度
分时调度
- 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度
- 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
- CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核心而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对我们的感觉更快,看上去就像是在同一时刻运行。其实,多线程并不能提高程序的运行速度,但能提高程序运行效率,让CPU的使用率更高。
2.同步与异步&并发与执行
2.1同步与异步
同步:排队执行,效率低但是安全
异步:同时执行,效率高但是不安全
2.2并发与并行
并发:指两个或多个事件在同一个时间段发生
并行:指两个或多个事件在同一个时刻发生
3.继承Thread
线程代码
public class MyThread extends Thread{
//run方法就是线程要执行的任务方法
public void run(){
//这里的代码就是一条新的执行路径
//这个执行路径的触发方式不是调用run方法,而是通过thread对象的start()来启动任务
for(int i=0;i<10;i++){
System.out.println("线程1"+i);
}
}
}
主函数
public static void main(String[] args){
MyThread m = MyThread();
m.start();
for(int i=0;i<10;i++){
System.out.println("线程2"+i);
}
}
因为java用的是抢占式分配,每次运行结果都会不同,并发执行
每个线程都拥有自己的栈空间,共用一份堆内存。
匿名内部类创建线程
public static void main(String[] args) {
new Thread(){
@Override
public void run(){
for(int i=0;i<10;i++){
System.out.println("线程 1 "+i);
}
}
}.start();
for(int i=0;i<10;i++){
System.out.println("线程 2 "+i);
}
}
4.实现Runnable
实现Runnable代码
//用于给线程进行执行的任务
public class MyRunnable implements Runnable{
@Override
public void run(){
//线程的任务
for(int i=0;i<10;i++){
System.out.println("线程1 "+i);
}
}
}
主函数
public static void main(String[] args) {
//实现Runnable
//1. 创建一个任务对象
MyRunnable r = new MyRunnable();
//2. 创建一个线程,并为其分配一个任务
Thread t = new Thread(r);
//3. 执行这个线程
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("线程2 " + i);
}
}
实现Runnable与继承Thread相比有如下优势:
- 通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况。
- 可以避免单继承所带来的局限性。
- 任务与线程本身是分离的,提高了程序的健壮性。
- 后续学习的线程池技术,接收Runnable类型的任务,不接受Thread类型的线程。
5.Thread类
构造方法
构造器 | 描述 |
---|---|
Thread() | 分配新的 Thread对象 |
Thread(Runnable target) | 分配新的 Thread对象 |
Thread(Runnable target, String name) | 分配新的 Thread对象 |
Thread(String name) | 分配新的 Thread对象 |
常用方法
变量和类型 | 方法 | 描述 |
---|---|---|
long | getId() | 返回此Thread的标识符。 |
String | getName() | 返回此线程的名称。 |
int | getPriority() | 返回此线程的优先级。 |
void | setName(String name) | 将此线程的名称更改为等于参数 name 。 |
void | setPriority(int newPriority) | 更改此线程的优先级。 |
void | start() | 导致此线程开始执行; Java虚拟机调用此线程的run方法。 |
static void | sleep(long millis) | 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。 (参数为毫秒) |
static void | sleep(long millis, int nanos) | 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。(参数为毫秒和纳秒) |
void | stop() | 已过时,这种方法本质上是不安全的。 |
void | setDaemon(boolean on) | 将此线程标记为 daemon(守护)线程或用户线程。守护线程依附于用户线程,用户线程全部死亡后,守护线程自动死亡 |
补充一下线程优先级的字段
变量和类型 | 字段 | 描述 |
---|---|---|
static int | MAX_PRIORITY | 线程可以拥有的最大优先级。 |
static int | MIN_PRIORITY | 线程可以拥有的最低优先级。 |
static int | NORM_PRIORITY | 分配给线程的默认优先级。 |
关于如何停止线程:
之前所使用的stop()方法会导致线程未释放资源而直接停止,不安全,因此弃用。
现在使用的停止变量的方式多为规划一个变量,当这个变量改变时停止线程,并停止前释放资源。
6.设置和获取线程名称
public static void main(String[] args) {
new Thread(new MyRunnable(),"锄禾日当午").start();
new Thread(new MyRunnable()).start();
new Thread(new MyRunnable()).start();
}
static class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
//currentThread()方法可以用来获取当前的线程
}
}
不设置线程名称默认是:Thread-0,Thread-1…
7.线程休眠sleep
public static void main(String[] args) throws InterruptedException {
for (int i=0;i<10;i++){
System.out.println(i);
Thread.sleep(1000);
}
}
改代码会每过一秒打印。
8.线程的中断
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。
下面是线程中断的示例代码
public static void main(String[] args){
Thread t1 = new Thread(new MyRunnable());
t1.start();
//main线程循环4次,在main结束后中断t1线程
for (int i=1;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//给t1添加中断标记,使其进入catch块
t1.interrupt();
}
static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i=1;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//e.printStackTrace();
System.out.println("检测到中断标记,线程死亡");
return;
}
}
}
}
运行截图
线程的中断绝对不应使用stop()方法
9.守护线程
- 线程:分为守护线程和用户线程
- 用户线程:当一个进程不包含任何存活的用户线程时,进程结束
- 守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
下面是演示代码
public static void main(String[] args){
Thread t1 = new Thread(new MyRunnable());
//设置t1为守护线程,守护线程要在线程开始前设置
t1.setDaemon(true);
t1.start();
//main线程循环4次
for (int i=1;i<5;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class MyRunnable implements Runnable {
@Override
public void run() {
for (int i=1;i<10;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
因为main方法结束需要时间,所以在main输出4之后t1线程在main结束的时间内输出了4和5,之后main结束,守护线程t1也随着结束。
10.线程安全问题
10.1线程不安全问题
线程不安全代码示例(卖票案例)
//线程不安全
public static void main(String[] args){
Runnable run = new MyRunnable();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class MyRunnable implements Runnable {
//票数
private int count = 10;
@Override
public void run() {
while(count>0){
//卖票
System.out.println("准备出票");
//休眠一秒,增大线程抢占的几率
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票完成,剩余票数:"+count);
}
}
}
代码看起来没有问题,下面是最后的运行截图
我们发现,在代码中的while规定了count要大于0,但结果出现了-1和-2这种不合理的结果。
这是因为在进程中,假设有三个线程A,B,C。线程A先进入,此时count为1,当A在休眠过程中,被B强占时间片,但是因为A在休眠,count仍然是1,线程B可以进入循环,然而在A休眠结束,准备count–并且B在休眠时,C进入了循环,这就导致了结果产生0,-1还有-2,产生了线程不安全问题。
10.1.1线程不安全问题解决1:同步代码块
可以简单认为:被同步代码块所括住的内容需要排队执行
格式:synchronized(锁对象){ }
代码示例:
public static void main(String[] args){
Runnable run = new MyRunnable();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class MyRunnable implements Runnable {
private Object o = new Object();
//票数
private int count = 10;
@Override
public void run() {
while(true) {
synchronized(o) {
if (count > 0) {
//卖票
System.out.println("准备出票");
//休眠一秒,增大线程抢占的几率
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票完成" + Thread.currentThread().getName() + "剩余:" + count);
} else {
break;
}
}
}
}
}
排队解决这个问题的效率并不高,虽然分了三个线程但是仍然是在排队出票。
10.1.2线程不安全问题解决2:同步方法
和同步代码块相似,将方法修饰为同步。
public static void main(String[] args){
Runnable run = new MyRunnable();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class MyRunnable implements Runnable {
private Object o = new Object();
//票数
private int count = 10;
@Override
public void run() {
while(true) {
boolean flag = sale();
if(!flag){
break;
}
}
}
public synchronized boolean sale(){
if (count > 0) {
//卖票
System.out.println("准备出票");
//休眠一秒,增大线程抢占的几率
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票完成" + Thread.currentThread().getName() + "剩余:" + count);
return true;
} else {
return false;
}
}
}
锁对象为this,如果是静态方法,锁是类名.class
如果同步方法的锁是this,同时又在一个同步代码块中上传了一个this的锁,这个锁将同时控制这两段代码。
10.1.3线程不安全问题解决3:显式锁Lock
同步代码块和同步方法都属于隐式锁
Lock 子类 ReentrantLock
public static void main(String[] args){
Runnable run = new MyRunnable();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class MyRunnable implements Runnable {
private Object o = new Object();
//票数
private int count = 10;
//显式锁 l
private Lock l = new ReentrantLock();
@Override
public void run() {
while(true) {
l.lock();
if (count > 0) {
//卖票
System.out.println("准备出票");
//休眠一秒,增大线程抢占的几率
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票完成"+Thread.currentThread().getName()+"剩余:"+ count);
}else{
break;
}
l.unlock();
}
}
}
10.2显式锁和隐式锁
下面为转载内容,附上链接
在面试的过程中有可能会问到:在Java并发编程中,锁有两种实现:使用隐式锁和使用显示锁分别是什么?两者的区别是什么?所谓的显式锁和隐式锁的区别也就是说说Synchronized(下文简称:sync)和lock(下文就用ReentrantLock来代之lock)的区别。
Java中隐式锁:synchronized;显式锁:lock
sync和lock的区别
-
出身不同
- 从sync和lock的出身(原始的构成)来看看两者的不同。
- Sync:Java中的关键字,是由JVM来维护的。是JVM层面的锁。
- Lock:是JDK5以后才出现的具体的类。使用lock是调用对应的API。是API层面的锁
- sync是底层是通过monitorenter进行加锁(底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖于monitor对象的。只有在同步块或者是同步方法中才可以调用wait/notify等方法的。因为只有在同步块或者是同步方法中,JVM才会调用monitory对象的);通过monitorexit来退出锁的。
- 而lock是通过调用对应的API方法来获取锁和释放锁的。
- 我们通过Javap命令来查看调用sync和lock的汇编指令:
-
使用方式不同
- Sync是隐式锁。Lock是显示锁
- 所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁的操作。
- 我们大家都知道,在使用sync关键字的时候,我们使用者根本不用写其他的代码,然后程序就能够获取锁和释放锁了。那是因为当sync代码块执行完成之后,系统会自动的让程序释放占用的锁。Sync是由系统维护的,如果非逻辑问题的话话,是不会出现死锁的。
- 在使用lock的时候,我们使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。手动获取锁方法:lock.lock()。释放锁:unlock方法。需要配合tyr/finaly语句块来完成。
-
等待是否可中断
-
Sync是不可中断的。除非抛出异常或者正常运行完成
-
Lock可以中断的。中断方式:
- 调用设置超时方法tryLock(long timeout ,timeUnit unit)
- 调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断
-
-
加锁的时候是否可以公平
- Sync;非公平锁
- lock:两者都可以的。默认是非公平锁。在其构造方法的时候可以传入Boolean值。
- true:公平锁
- false:非公平锁
-
锁绑定多个条件来condition
- Sync:没有。要么随机唤醒一个线程;要么是唤醒所有等待的线程。
- Lock:用来实现分组唤醒需要唤醒的线程,可以精确的唤醒,而不是像sync那样,不能精确唤醒线程。
10.3公平锁和不公平锁
不公平锁:先抢到先得
上述三个解决线程不安全问题的方法都是不公平锁,只有Lock可以变为公平锁
公平锁:先来先到,每个人排队
参数为true 就表示为公平锁
Lock l = new ReentrantLock(true)
11.线程死锁
死锁的概念可以从一个例子来描述:
警匪片中警察会对劫匪说:你放了人质,我们就放你走;劫匪会说:你放我走,我才放人质。如果我们不考虑警察进一步的动作,我们可以把现在警察与劫匪的关系叫做死锁,下面用代码来演示:
public static void main(String[] args) {
//线程死锁
//主线程控制罪犯说话,子线程控制警察说话
Police p = new Police();
Culprit c = new Culprit();
new MyThread(p,c).start();
c.say(p);
}
static class MyThread extends Thread {
private Police p;
private Culprit c;
public MyThread(Police p,Culprit c){
this.p = p;
this.c = c;
}
@Override
public void run() {
p.say(c);
}
}
//罪犯
static class Culprit{
public synchronized void say(Police p){
System.out.println("罪犯:你放了我,我放人质");
//劫匪说完话后,讨警察要一个回应
p.answer();
}
//回应方法
public synchronized void answer(){
System.out.println("我放人质了,我走了");
}
}
//警察
static class Police{
public synchronized void say(Culprit c){
System.out.println("警察:你放人质,我放了你");
//警察说完话后,向劫匪要一个回应
c.answer();
}
//回应方法
public synchronized void answer(){
System.out.println("警察救了人质,我放走罪犯了");
}
}
运行截图:
就这样,死锁产生了。
12.多线程通信问题
首先了解几个Object 下的方法
变量和类型 | 方法 | 描述 |
---|---|---|
void | notify() | 唤醒正在此对象监视器上等待的单个线程。 |
void | notifyAll() | 唤醒等待此对象监视器的所有线程。 |
void | wait() | 导致当前线程等待它被唤醒,通常是 通知或 中断 。 |
void | wait(long timeoutMillis) | 导致当前线程等待它被唤醒,通常是 通知或 中断 ,或者直到经过一定量的实时。(参数:毫秒) |
void | wait(long timeoutMillis, int nanos) | 导致当前线程等待它被唤醒,通常是 通知或 中断 ,或者直到经过一定量的实时。 (参数:毫秒,纳秒) |
12.1 生产者与消费者
这里用厨师与服务员来完成生产者与消费者的例子。
在这个例子里面,厨师完成一份菜后休息,唤醒服务生,服务生端完这份菜后休息,唤醒厨师,不考虑现实中的特殊情况。
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.setNameAndSaste("老干妈小米粥","香辣味");
}else{
f.setNameAndSaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
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 表示可以生产
private boolean flag = true;
public synchronized void setNameAndSaste(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();
}
}
}
}
13.线程的六种状态
线程状态。 线程可以处于以下状态之一:
- NEW :尚未启动的线程处于此状态。
- RUNNABLE :在Java虚拟机中执行的线程处于此状态。
- BLOCKED :被阻塞等待监视器锁定的线程处于此状态。
- WAITING :无限期等待另一个线程执行特定操作的线程处于此状态。
- TIMED_WAITING :正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。
- TERMINATED :已退出的线程处于此状态。
线程在给定时间点只能处于一种状态。 这些状态是虚拟机状态,不反映任何操作系统线程状态。
关系就类似与这种八爪鱼一样的。
14.带返回值的线程Callable
14.1Runnable 与 Callable
接口定义
//Callable接口
public interface Callable {
V call() throws Exception;
}
//Runnable接口
public interface Runnable {
public abstract void run();
}
14.2 Callable使用步骤
- 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
- 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
- 通过Thread,启动线程
new Thread(future).start();
14.3Runnable 与 Callable的相同点
- 都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动线程
14.4Runnable 与 Callable的不同点
- Runnable没有返回值;Callable可以返回执行结果
- Callable接口的call()允许抛出异常;Runnable的run()不能抛出
14.5Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
14.6实现Callable
在用代码完成之前,查看几个FutureTask的方法
变量和类型 | 方法 | 描述 |
---|---|---|
protected void | done() | 当此任务转换到状态 isDone (无论是正常还是通过取消),调用受保护的方法。 |
V | get() | 如果需要等待计算完成,然后检索其结果。 |
V | get(long timeout, TimeUnit unit) | 如果需要,最多等待计算完成的给定时间,然后检索其结果(如果可用)。 |
protected boolean | runAndReset() | 执行计算而不设置其结果,然后将此未来重置为初始状态,如果计算遇到异常或被取消则无法执行此操作。 |
protected void | set(V v) | 将此future的结果设置为给定值,除非已设置或已取消此未来。 |
protected void | setException(Throwable t) | 导致此未来报告带有给定throwable的ExecutionException作为其原因,除非此未来已设置或已取消。 |
String | toString() | 返回此FutureTask的字符串表示形式。 |
实现代码示例:
public static void main(String[] args) {
Callable<Integer> c = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(c);
new Thread(task).start();
for (int i=0;i<10;i++){
//加上休眠体现出并发执行的效果
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
static class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
//加上休眠体现出并发执行的效果
for (int i=0;i<10;i++){
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+":"+i);
}
return 100;
}
}
后半部分的运行截图:
使用一下FutureTask提供的get()方法,就用上面的例子,代码如下:
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> c = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(c);
new Thread(task).start();
//get方法在这,有异常直接抛出了
Integer j = task.get();
System.out.println("返回值为:"+j);
for (int i=0;i<10;i++){
//加上休眠体现出并发执行的效果
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
static class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
//加上休眠体现出并发执行的效果
for (int i=0;i<10;i++){
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+":"+i);
}
return 100;
}
}
get方法会先让子线程结束,运行截图:
15.线程池
15.1线程池概述Executors
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间。
线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
优点:
- 降低资源消耗。
- 提高响应速度。
- 提高线程的可管理性。
15.2Java中的四种线程池 ExecutorService
1. 缓存线程池. (长度无限制)
执行流程:
- 判断线程池是否存在空闲线程
- 存在则使用
- 不存在,则创建线程 并放入线程池, 然后使用
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. 定长线程池
定长线程池.(长度是指定的数值)
执行流程:
- 判断线程池是否存在空闲线程
- 存在则使用
- 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
- 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
ExecutorService service = Executors.newFixedThreadPool(2);
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());
}
});
3.单线程线程池
效果与定长线程池 创建时传入数值1 效果一致.
单线程线程池.
执行流程:
- 判断线程池 的那个线程 是否空闲
- 空闲则使用
- 不空闲,则等待 池中的单个线程空闲后 使用
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());
}
});
4… 周期性任务定长线程池
public static void main(String[] args) {
/**
* 周期任务 定长线程池.
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*
* 周期性任务执行时:
* 定时执行, 当某个时机触发时, 自动执行某任务 .
*/
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 定时执行
* 参数1. runnable类型的任务
* 参数2. 时长数字
* 参数3. 时长数字的单位
*/
/*service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,TimeUnit.SECONDS);
*/
/**
* 周期执行
* 参数1. runnable类型的任务
* 参数2. 时长数字(延迟执行的时长)
* 参数3. 周期时长(每次执行的间隔时间)
* 参数4. 时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,2,TimeUnit.SECONDS);
}
16.Lambda表达式
Lambda表达式是函数式编程思想
先用实现Runnable来观察一下Lambda表达式
public static void main(String[] args) {
//冗余的Runnable代码
/*
Thread t = new Thread(new Runnable(){
@Override
public void run() {
System.out.println("锄禾日当午");
}
});
t.start();
*/
//Lambda表达式
Thread t = new Thread(() -> {
System.out.println("锄禾日当午");
});
t.start();
}
下面用自己定义的实现接口来进一步了解
public static void main(String[] args) {
//普通写法
/*
print(new MyMath(){
@Override
public int sum(int x, int y) {
return x+y;
}
},100,200);
*/
//Lambda表达式
print((int x, int y) -> {
return x+y;
},100,200);
}
public static void print(MyMath m, int x, int y){
int num = m.sum(x,y);
System.out.println(num);
}
static interface MyMath{
int sum(int x,int y);
}