Java【教程】多线程

  • 进程与线程
  • 同步与异步
  • 并发与并行
  • 创建线程的2种方式
    • 方式1:继承Thread
    • 方式2:实现Runnable(更好)
      • 优势
  • Thread类
  • 线程的中断
  • 守护线程
  • 线程安全
    • 线程不安全问题
      • 解决方法1:同步代码块
    • 解决方法2:同步方法
      • 多个同步方法/同步代码块共用一把锁
    • 显示锁(公平锁,非公平锁)
  • 线程死锁
  • 多线程通信
  • 线程的六种状态
  • 带返回值的线程Callable(仅作了解,用的少)
    • Runnable接口 与 Callable接口
      • 接口定义
      • Runnable 与 Callable的相同点
      • Runnable 与 Callable的不同点
    • Callable使用步骤
    • Callable获取返回值
    • FutureTask类的方法
  • 线程池
    • 线程池的作用/好处
    • 缓存线程池
    • 定长线程池
    • 单线程线程池
    • 周期定长线程池
  • Lambda表达式

多线程是面试重点。

进程与线程

进程
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。

线程

  • 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
  • 是进程中的一个执行路径,多个线程共享一个内存空间,线程之间可以自由切换,并发执行。
  • 一个进程最少有一个线程。
  • 同一进程的每个线程都拥有自己的栈空间,它们共用一个堆内存。

线程调度

  • 分时调度
    所有线程轮流得到 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
  • 抢占式调度(Java使用)
    优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),CPU使用抢占式调度模式在多个线程间进行着高速的切换。

注意:

  • 某个时刻,一个CPU的核只能做一件事情(即只能执行一个线程)。而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就像在同一时刻运行。
  • 多个线程排队运行,与进行线程调度相比,线程的执行时间是一样的,但后者的线程切换次数比前者多,线程切换开销大。

同步与异步

同步:排队执行 , 效率低,线程安全。
异步:同时执行 , 效率高,线程不安全。

并发与并行

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

创建线程的2种方式

方式1:继承Thread

步骤:

  • 类extends Thread
  • 重写run方法,里面的内容就是线程要执行的任务
  • new一个线程对象
  • 用start方法触发(启动)线程(注意:不是调用run方法!)

例子:

public class Demo1 {public static void main(String[] args) {MyThread t = new MyThread();t.start();for (int i = 0; i < 20; i++) {System.out.println("汗滴禾下土"+i);}}}class MyThread extends Thread {//run方法就是线程要执行的任务方法@Overridepublic void run() {//这里的代码就是一条新的执行路径//这个执行路径是触发方式,不是调用run方法,而是通过thread对象的start方法来启动任务for (int i = 0; i < 20; i++) {System.out.println("锄禾日当午"+i);}}
}

运行结果:

分析:
并不是“先打印20句锄禾日当午,再打印20句汗滴禾下土”。因为main是一条线程,t也是一条线程,它们交替运行,并且每次运行的顺序都可能不一样。

方式2:实现Runnable(更好)

步骤:

  • 创建一个任务类(implements Runnable)
  • 重写run方法
  • new一个任务对象
  • new一个线程对象,并为其分配一个任务(传参)
  • start方法触发线程

例子:

public class Demo2 {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("汗滴禾下土"+i);}}
}class MyRunnable implements Runnable{@Overridepublic void run() {//线程的任务for (int i = 0; i < 10; i++) {System.out.println("锄禾日当午"+i);}}
}

运行结果:

优势

实现Runnable与继承Thread相比有如下优势:

1.通过创建任务,然后给线程分配任务的方式实现多线程,更适合多个线程同时执行任务的情况
2.可以避免单继承所带来的局限性
3.任务与线程是分离的,提高了程序的健壮性
4.后期学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程

继承Thread也不是百无一用,以下的匿名内部类写法较为常用。

public class Demo2 {public static void main(String[] args) {new Thread(){@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("12345"+i);}}}.start();for (int i = 0; i < 10; i++) {System.out.println("汗滴禾下土"+i);}}
}

Thread类

常用构造方法:

  • Thread()
  • Thread​(Runnable target)
  • Thread​(Runnable target, String name)
  • Thread​(String name)

String name是给线程起名字。

常用方法:

  • String getName() 返回此线程的名称。
  • long getId() 返回此Thread的标识符。
  • int getPriority() 返回此线程的优先级。
  • void setPriority​(int newPriority) 更改此线程的优先级。
  • 注意:void stop() 已过时。 这种方法本质上是不安全的。
  • 最常用:static void sleep​(long millis) 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。
  • void setDaemon​(boolean on) 将此线程标记为daemon线程(守护线程)或用户线程。 用户线程自己决定自己的死亡,守护线程的死亡依赖于用户线程,当所有用户线程都死亡了,守护线程就死了。
  • static Thread currentThread() 返回对当前正在执行的线程对象的引用。

方法演示(currentThread,getName,setName):

public class Demo3 {public static void main(String[] args) {//如何获取线程的名称System.out.println(Thread.currentThread().getName());//两种设置线程名称的方式Thread t = new Thread(new MyRunnable());t.setName("wwww");t.start();new Thread(new MyRunnable(),"锄禾日当午").start();//不设置的有默认的名字new Thread(new MyRunnable()).start();}static class MyRunnable implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}}
}


方法演示(sleep):
每隔1秒,打印一下。

public class Demo4 {public static void main(String[] args) throws InterruptedException {//线程的休眠for (int i = 0; i < 10; i++) {System.out.println(i);Thread.sleep(1000);     //1000毫秒}}
}

线程的中断

一个线程是一个独立的执行路径,它是否结束,应该由其自身决定。外部不应该强行地掐死一个线程(已经过时的stop方法),因为该线程的善后工作没有做(如释放资源),容易出问题。

我们的做法应该是:外部给目标线程添加中断标记,即调用目标线程的interrupt方法,目标线程在特定时候会抛出InterruptedException,目标线程自己捕获这个异常,在catch块里决定如何处理它,也可以无视它。可以在catch块里做善后工作,释放完所有资源。

InterruptedException:
Thrown when a thread is waiting, sleeping, or otherwise occupied, and the thread is interrupted, either before or during the activity.

public class Demo6 {public static void main(String[] args) {Thread t1 = new Thread(new MyRunnable());t1.start();for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+":"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}//添加中断标记t1.interrupt();}static class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+":"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {System.out.println("接收到了中断标记。");e.printStackTrace();}}}}
}

t1并没有停止执行。

守护线程

线程分为守护线程和用户线程:

  • 用户线程:当一个进程不包含任何的存活的用户线程时,进程结束。
  • 守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
public class Demo6 {public static void main(String[] args) {//线程分为守护线程和用户线程//用户线程:当一个进程不包含任何的存活的用户线程时,进程结束//守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。Thread t1 = new Thread(new MyRunnable());//设置守护线程t1.setDaemon(true);t1.start();for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+":"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}static class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+":"+i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
}

由于t1是main的守护线程,main线程结束了,t1线程也结束了。main做完自己的善后工作过后,t1才结束,在这之前,t1又打印了一次。

线程安全

线程不安全问题

有三个线程都在操作同一个变量count,结果出问题了。

public class Demo7 {public static void main(String[] args) {//线程不安全Runnable run = new Ticket();new Thread(run).start();new Thread(run).start();new Thread(run).start();}static class Ticket implements Runnable{//总票数private int count = 10;@Overridepublic void run() {while (count>0){//卖票System.out.println("正在准备卖票");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}count--;System.out.println("卖票结束,余票:"+count);}}}
}

解决方法1:同步代码块

格式:
synchronized(锁对象){

}

原理:

  • 任何对象都可以作为锁对象。
  • 每个线程进这段代码前,先看锁对象是上锁了还是没上锁。如果没上锁就执行这段代码,执行完了把锁打开。如果上锁了,就等着。
  • 让多个线程排队。
  • 注意:多个线程应该共用同一个锁对象!如果创建锁的代码在run方法里,即每个线程都会有自己的锁,这样就无法实现排队,依然会出问题。
//线程同步synchronizedpublic class Demo8 {public static void main(String[] args) {Object o = new Object();//线程不安全//解决方案1  同步代码块//格式:synchronized(锁对象){//////      }Runnable run = new Ticket();new Thread(run).start();new Thread(run).start();new Thread(run).start();}static class Ticket implements Runnable{//总票数private int count = 10;private Object o = new Object();@Overridepublic void run() {//Object o = new Object();    //这里不是同一把锁,所以锁不住while (true) {synchronized (o) {if (count > 0) {//卖票System.out.println("正在准备卖票");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}count--;System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);}else {break;}}try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}}
}

解决方法2:同步方法

给方法加上synchronized。也是通过锁机制实现的同步,方法的锁是方法的调用者(this,或静态类类名.class)。下边例子的锁是run对象。

//线程同步synchronizedpublic class Demo9 {public static void main(String[] args) {Object o = new Object();//线程不安全//解决方案2  同步方法Runnable run = new Ticket();new Thread(run).start();new Thread(run).start();new Thread(run).start();}static class Ticket implements Runnable{//总票数private int count = 10;@Overridepublic void run() {while (true) {//卖票boolean flag = sale();if(!flag){break;}//卖完票休息一会try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}}}public synchronized boolean sale(){if (count > 0) {//卖票System.out.println("正在准备卖票");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}count--;System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);return true;}return false;}}
}


如果new三个Ticket对象传入三个Thread,就是三个人各卖各的10张票,不共享变量。

    public static void main(String[] args) {Object o = new Object();//线程不安全//解决方案2  同步方法//Runnable run = new Ticket();new Thread(new Ticket()).start();new Thread(new Ticket()).start();new Thread(new Ticket()).start();}

多个同步方法/同步代码块共用一把锁

同一时刻,只能有一个同步方法或同步代码块在执行。它们共有的锁是this。

显示锁(公平锁,非公平锁)

同步代码块、同步方法都属于隐式锁。显示锁即创建ReentrantLock类的对象作为锁,有开锁、关锁两个方法。

公平锁:先到先来。创建ReentrantLock类的对象时,传入true。
非公平锁:一通乱抢。创建ReentrantLock类的对象时,传入false。

关键代码:
private Lock l = new ReentrantLock();
l.lock();
l.unlock();

大厂面试题:
显示锁和隐式锁的区别?
公平锁与非公平锁?

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;//同步代码块和同步方法都属于隐式锁
//线程同步lockpublic class Demo10 {public static void main(String[] args) {Object o = new Object();//线程不安全//解决方案1   显示锁  Lock  子类 ReentrantLockRunnable run = new Ticket();new Thread(run).start();new Thread(run).start();new Thread(run).start();}static class Ticket implements Runnable{//总票数private int count = 10;//参数为true表示公平锁    默认是false 不是公平锁private Lock l = new ReentrantLock(true);@Overridepublic 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();}}}
}

线程死锁

以下的警察和罪犯对峙的代码,可能会引起死锁。
注意:say和fun方法不能同时执行,因为它们共有一把锁:this。

public class Demo11 {public static void main(String[] args) {//线程死锁Culprit c = new Culprit();Police p = new Police();//线程2new MyThread(c,p).start();//线程1c.say(p);}static class MyThread extends Thread{private Culprit c;private Police p;MyThread(Culprit c,Police p){this.c = c;this.p = p;}@Overridepublic void run() {p.say(c);}}static class Culprit{//注意:say和fun方法不能同时执行,因为它们共有一把锁:thispublic synchronized void say(Police p){System.out.println("罪犯:你放了我,我放了人质");p.fun();}public synchronized void fun(){System.out.println("罪犯被放了,罪犯也放了人质");}}static class Police{//注意:say和fun方法不能同时执行,因为它们共有一把锁:thispublic synchronized void say(Culprit c){System.out.println("警察:你放了人质,我放了你");c.fun();}public synchronized void fun(){System.out.println("警察救了人质,但是罪犯跑了");}}
}

死锁情况:
警察喊话后,等待罪犯回应(等待调用罪犯的fun方法,但罪犯的say方法未结束,没有释放锁)
罪犯喊话后,等待警察回应(等待调用警察的fun方法,但警察的say方法未结束,没有释放锁)

没有死锁的情况:
main线程:c.say§执行完了,子线程:new MyThread(c,p).start()才开始执行。
一个线程都执行完了,另一个线程还没有开始。

多线程通信

Object的方法:

void wait()
void wait​(long timeoutMillis)
void wait​(long timeoutMillis, int nanos)
void notify()
void notifyAll()

生产者、消费者问题(能理解就行):

厨师:生产100个菜,“老干妈小米粥”-“香辣味”、“煎饼果子”-"甜辣味"交替生产。每个菜的做菜时间:100ms。
服务员:没100ms就去端一次菜。
只有一个盘子。

未实现同步时,生产者做的菜的菜名和味道可能错乱,因为Food的name刚被厨师设置好,由于间隔100ms才设置taste,在此期间,服务员就上菜了,taste还是上一道菜的值。

为了解决该问题,如果把Food里的setNameAndTaste和get方法变成同步方法,还是会有问题,可能厨师还没做出来新菜,服务员就端了原来的菜很多次,即get方法运行完又进了get方法。

为了使两个方法交替执行(做菜、端菜交替执行),使用变量flag,和使线程通信的wait、notify方法。以下代码为改进后的。

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;}@Overridepublic 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;}@Overridepublic 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
在Java虚拟机中执行的线程处于此状态。 (排队时的的状态)
BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
WAITING
无限期等待另一个线程执行特定操作的线程处于此状态。
TIMED_WAITING
正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。
TERMINATED
已退出的线程处于此状态。

带返回值的线程Callable(仅作了解,用的少)

面试偶尔会问:Java中的第三种线程实现方式?

Runnable接口 与 Callable接口

接口定义

//Callable接口
public interface Callable<V> {V call() throws Exception;
}
//Runnable接口
public interface Runnable {public abstract void run();
}

Runnable 与 Callable的相同点

都是接口
都可以编写多线程程序
都采用Thread.start()启动线程

Runnable 与 Callable的不同点

Runnable没有返回值;Callable可以返回执行结果
Callable接口的call()允许抛出异常;Runnable的run()不能抛出

Callable使用步骤

//1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {@Overridepublic <T> call() throws Exception {return T;}
}
//2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
//3. 通过Thread,启动线程
new Thread(future).start();

Callable获取返回值

Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

FutureTask类的方法

get:
V get() 等待计算完成,然后返回其结果。 (V:泛型变量)
V get​(long timeout, TimeUnit unit) 在指定时间内,等待计算完成,然后返回其结果(如果得到了结果)。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class DemoCallable {public static void main(String[] args) throws ExecutionException, InterruptedException {Callable<Integer> c = new MyCallable();FutureTask<Integer> task = new FutureTask<>(c);new Thread(task).start();//main线程等子线程返回值过后,才会继续执行//Integer num =task.get();//System.out.println("返回值:"+num);for(int i=0;i<10;i++){Thread.sleep(100);System.out.println(i);}}static class MyCallable implements Callable<Integer>{@Overridepublic Integer call() throws Exception {for(int i=0;i<10;i++){Thread.sleep(100);System.out.println(i);}return 100;}}
}

未调用get时,main线程和子线程交替执行:
调用get,等待子线程运行完循环,返回结果后,main线程的循环才开始运行:

isDone:
boolean isDone() 如果此任务完成,则返回 true 。

cancel:
boolean cancel​(boolean mayInterruptIfRunning) 尝试取消执行此任务。

线程池

线程池的作用/好处

创建线程和关闭线程会消耗大量时间:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

降低资源消耗。
提高响应速度。
提高线程的可管理性。

有定长的线程池,也有变长的线程池,当任务过多的时候,会扩容,任务少了又缩容。

缓存线程池

缓存线程池无限制长度。
任务加入后的执行流程:
1判断线程池是否存在空闲线程
2存在则使用
3不存在则创建线程并使用

先创建3个任务,2000ms后,再创建一个任务。经观察,最后创建的任务使用的线程是之前使用过的线程。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class Demo13 {public static void main(String[] args) {//缓存线程池//无限制长度//任务加入后的执行流程//1判断线程池是否存在空闲线程  2存在则使用   3不存在则创建线程并使用//向线程池中加入新的任务ExecutorService service = Executors.newCachedThreadPool();//指挥线程池执行新的任务service.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"锄禾日当午");}});service.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"锄禾日当午");}});service.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"锄禾日当午");}});//主线程休眠2000ms,观察休眠后的启动的任务使用的哪个线程try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}service.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"锄禾日当午");}});}
}

定长线程池

定长线程池:长度是指定的线程池
加入任务后的执行流程:
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程

指定长度为2,创建了3个任务,有重复使用线程:

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() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"锄禾日当午");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}});service.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"锄禾日当午");try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}});service.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"锄禾日当午");}});}
}

单线程线程池

单线程线程池执行流程:
1 判断线程池的那个线程是否空闲
2 空闲则使用
3 不空闲则等待它空闲后再使用

创建了3个任务,使用的都是同一线程:

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() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"锄禾日当午");}});service.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"锄禾日当午");}});service.execute(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"锄禾日当午");}});}
}

周期定长线程池

创建方式和以上线程池不一样。

周期任务 定长线程池 执行流程:
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程

定时执行 :
当某个任务触发时,自动执行某任务。
参数1:定时执行的任务
参数2:时长数字
参数3:2的时间单位 Timeunit的常量指定

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);scheduledExecutorService.schedule(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"锄禾日当午");}
},5, TimeUnit.SECONDS);      //5秒钟后执行

周期性执行任务:
参数1:任务
参数2:延迟时长数字(第一次在执行上面时间以后)
参数3:周期时长数字(没隔多久执行一次)
参数4:时长数字的单位

5秒后开始执行,每1秒执行一次:

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);scheduledExecutorService.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"锄禾日当午");}
},5,1,TimeUnit.SECONDS);

Lambda表达式

lambda表达式:函数式编程,关注的是方法,而不是面向对象。
使用条件:接口里只有一个抽象方法。

public class Demo17 {/*lambda表达式函数式编程思想**/public static void main(String[] args) {//面向对象,匿名内部类//冗余的Runnable编写方式/* Thread t = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("锄禾日当午");}});t.start();*///如果需要传入参数,()里写参数Thread t = new Thread(() -> {System.out.println("锄禾日当午");});t.start();}
}
public class Demo16 {public static void main(String[] args) {//面向对象//创建匿名内部类(实现了接口)//传入x和yprint(new MyMath() {@Overridepublic int sum(int x, int y) {return x+y;}},100,200);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);}
}

Java【教程】多线程相关推荐

  1. java核心教程_核心Java教程

    java核心教程 Welcome to Core Java Tutorial. I have written a lot on Core Java and Java EE frameworks. Th ...

  2. Java 并发/多线程教程(四)-并发模型

    本系列译自jakob jenkov的Java并发多线程教程(本章节部分内容参考http://ifeve.com/并发编程模型),个人觉得很有收获.由于个人水平有限,不对之处还望矫正! 并发系统可以有多 ...

  3. Java 并发/多线程教程(五)-相同线程

    本系列译自jakob jenkov的Java并发多线程教程,个人觉得很有收获.由于个人水平有限,不对之处还望矫正! 相同线程是一并发框架模型,是一个单线程系统向外扩展成多个单线程的系统.这样的结果就是 ...

  4. java 多线程缓存_[Java教程]【JAVA并发编程实战】12、使用condition实现多线程下的有界缓存先进先出队列...

    [Java教程][JAVA并发编程实战]12.使用condition实现多线程下的有界缓存先进先出队列 0 2016-11-29 17:00:10 package cn.study.concurren ...

  5. Java并发/多线程教程——1

    本系列译自jakob jenkov的Java并发多线程教程,个人觉得很有收获.由于个人水平有限,不对之处还望矫正!在早期,计算机只有一个CPU,同一时刻只能执行一个程序,后来有了多任务的说法,多任务是 ...

  6. java教程java自学_15必须阅读Java 8教程

    java教程java自学 Java 8于上个月发布,并且充满了新功能和幕后优化. 互联网在覆盖所有这些新增功能(包括好与坏)方面都做得相当不错 . 我认为最好汇总一下我们认为是其中最好的一些教程,以帮 ...

  7. javame学习_从零基础自学Java教程:648集全网最新Java学习教程,一学就会

    我们都知道Java的功能非常的强大,Java是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由Sun Microsystems公司于1995年5月推出的Java程序设计语言和Java平台(即J ...

  8. java web swing 教程_好程序员Java教程解读什么是swing

    原标题:好程序员Java教程解读什么是swing 好程序员Java教程解读什么是swing,swing是java GUI应用程序,也就是java做的桌面应用.运行swing程序要求用户电脑上有java ...

  9. 【Java教程系列】JavaSE核心知识点总结

    一:Java 概述 一.简介1. 定义:Java 是一门面向对象编程语言,不仅吸收了C++语言的各种优点,还摒弃了C++里难以理解的多继承.指针等概念,因此Java语言具有功能强大和简单易用两个特征. ...

  10. HowToDoInJava Java 教程·翻译完成

    原文:HowToDoInJava 协议:CC BY-NC-SA 4.0 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远. ApacheCN 学习资源 目录 核心 Java 教程 什 ...

最新文章

  1. IIS6 MVC3 配置
  2. 工作中linux定时任务的设置及相关配置
  3. Winform中设置ZedGraph的X轴的刻度根据曲线获取
  4. 解决 display 和 transition 冲突的问题
  5. Beetlex之websocket/tls服务压测工具
  6. 金山云发布全新Serverless产品 云原生基础设施再升级
  7. Java JDBC篇1——初识JDBC
  8. Rainbond 5.1.3 发布,快速部署和运维 Spring Cloud 集群
  9. 常用SQL语句汇总整理
  10. Unity3D中粒子系统
  11. 如何将bmp格式图片批量转换成jpg格式的
  12. html css jsp 数据库,html、css、js、jsp的区别是什么?
  13. 【模型复现】零样本预测文本分类模型——ESM 快速复现模型
  14. return的用法是什么?
  15. UbuntuWindows 双系统时间不同步,Windows 慢8个小时
  16. Android6.0 ios,意超越iOS9!谷歌寄希望于Android6.0系统
  17. 天敏的SDK2000
  18. PR是什么?怎么查询网站的谷歌PR权重?
  19. 我为什么信主---为主作见证
  20. 【附源码】Python计算机毕业设计企业固定资产信息管理系统

热门文章

  1. CPU 的功能和基本结构
  2. Arbitrum 的 Nitro 项目启动和交易执行源码解析
  3. linux dump core 文件位置,Linux core dump使用
  4. python+moviepy音视频处理(三):视频特效方法-vfx模块
  5. 区块链:一种新的数据传输方式
  6. mysql for_mysql 循环三种循环用法
  7. 前端零基础入门(八):background的妙用_css精灵
  8. python图像文字识别(附灰度化二值化和压缩图片)
  9. 2022年国家能源集团招聘公告!
  10. 建站技术:不懂设计怎么也能做出好看的banner