多线程,匿名内部类,lambda表达式 整合使用
多线程
多线程的3种实现方式
- Thread是一个类,可以通过继承Thread开启新线程
- Runnable是一个接口,可以通过实现Runnable接口开启新线程
- Callable是一个接口,可以通过实现Callable接口开启新线程
1 继承Thread类创建线程代码
class NewThread extends Thread{
//NewThread通过继承Thread,重写run方法,实现多线程
@Override
public void run() {
//多线程实现异步逻辑
System.out.println("创建新线程"+this.currentThread().getName());
}
}
public class Test{
public static void main(String[] args) {
//通过new方式创建新线程实例
NewThread newThread = new NewThread();
//通过newThread的start方法开启新线程
newThread.start();
}
}
这里执行start()方法,jvm就会有两条线程去执行代码,一条是main方法执行的主线程,
和start方法开辟的新线程,两个线程交互异步执行
1.2 不通过start()方法直接执行run()方法(注意这样不会开启多线程)
class NewThread extends Thread{
//NewThread通过继承Thread,重写run方法,实现多线程
@Override
public void run() {
//多线程实现异步逻辑
System.out.println("创建新线程"+this.currentThread().getName());
}
}
public class Test{
public static void main(String[] args) {
//通过new方式创建新线程实例
NewThread newThread = new NewThread();
//直接调用newThread的run方法
newThread.run();
}
}
这里只是通过main方法的主线程去调用了newThread类的run方法,没有开启新线程,
jvm还是只有一条线程去执行代码
直接调用run方法,只是当前main方法的主线程调用run方法,
而不是jvm去创建新线程调用。
2 实现Runnable接口创建线程代码
class NewThread implements Runnable{
//实现Runnable接口的NewThread重写run()方法实现新线程异步逻辑
@Override
public void run() {
System.out.println("通过实现Runnable接口开启新线程");
}
}
public class Test{
public static void main(String[] args) {
//创建实现Runnable接口的NewThread实例对象
NewThread newThread = new NewThread();
//创建代理实例
Thread proxyThread = new Thread(newThread);
//通过调用代理实例的start()方法开启新线程
proxythread.start();
}
}
这里需要注意通过实现Runnable接口的NewThread类,不能直接使用start()方法
开辟新线程,必须通过Thread类去代理NewThread类的实例,
再由代理类实例 proxyThread调用start()方法,开启新线程。
为什么调用start()开启线程会执行run()方法
//Thread start方法实现
@Override
public void start() {
if (target != null) {
target.run();
}
}
---
//target就是传入的Runnable实现类
new Thread(Runnable target);
3实现Callable接口创建线程代码(方式2用到了Executors 线程池服务)
//与Rannable区别在于(好处)
//1实现Callable接口开启的线程有返回值
//2能抛出异常
public class NewThread implements Callable<Boolean>{
@Override
public Boolean call() throws Exception {
System.out.println("实现Callable接口开启新线程");
return true;
}
}
//实现方式1
public class Test1{
public static void main(String[] args) throws Exception {
NewThread newThread = new NewThread();
// 将Callable实现类包装成FutureTask,FutureTask其实也是Runnable子接口的实现类
FutureTask<Boolean> futureTask = new FutureTask<>(newThread);
new Thread(futureTask);
// get方法会阻塞调用的线程
Boolean f1 = futureTask.get();
}
}
Callable 是一种函数式接口
(下面讲Lambda表达式 会介绍到函数式接口 FunctionalInterface)
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
FutureTask
public class FutureTask<V> implements RunnableFuture<V> {
// 构造函数
public FutureTask(Callable<V> callable);
// 取消线程
public boolean cancel(boolean mayInterruptIfRunning);
// 判断线程
public boolean isDone();
// 获取线程执行结果
public V get() throws InterruptedException, ExecutionException;
}
RunnableFuture
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
//实现方式2
public class Test2{
public static void main(String[] args) throws ExecutionException, InterruptedException {
NewThread newThread = new NewThread();
//创建执行服务
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
//提交执行
Future<Boolean> submit = scheduledThreadPool.submit(newThread);
//获取返回值
Boolean s1 = submit.get();
//关闭服务
scheduledThreadPool.shutdownNow();
}
}
Executors,ScheduledExecutorService,Future(不过多介绍自己去看)
4直接用Thread创建线程(偷懒用)
public class Test{
public void TestThread(){
//看这里,直接new Thread类.start()开启线程
new Thread(){
public void run(){
System.out.println("开启新线程");
}
}.start();
}
}
线程状态(废话理论,理解就行)
在Java当中,线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
- 第一是创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
- 第二是就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
- 第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
- 第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
- 第五是死亡状态。如果一个线程的 run方法执行结束或者调用stop方法后,该线程就会死亡。 对于已经死亡的线程,无法再使用start方法令其进入就绪。
扩展知识点(sleep,interrupt,wait,notify,notifyall,supend,stop)
sleep() 与 interrupt()
public static native void sleep(long millis) throws InterruptedException;
public void interrupt();
sleep(long millis): 睡眠指定时间,程序暂停运行,睡眠期间会让出CPU的执行权,去执行其它线程,同时CPU也会监视睡眠的时间,一旦睡眠时间到就会立刻执行(因为睡眠过程中仍然保留着锁,有锁只要睡眠时间到就能立刻执行)。
- sleep(): 睡眠指定时间,即让程序暂停指定时间运行,时间到了会继续执行代码,如果时间未到就要醒需要使用interrupt()来随时唤醒
- interrupt(): 唤醒正在睡眠的程序,调用interrupt()方法,会使得sleep()方法抛出InterruptedException异常,当sleep()方法抛出异常就中断了sleep的方法,从而让程序继续运行下去
public static void main(String[] args) throws Exception {
Thread thread0 = new Thread(()-> {
try {
System.out.println(new Date() + " " + Thread.currentThread().getName() + " 睡10秒,有事通过interrupt()唤起");
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println(new Date() + " " + Thread.currentThread().getName() + " 被interrupt()唤起,终止sleep,继续运行");
}
});
thread0.start();
// 这里睡眠main方法的主线程,让thread0线程先执行
Thread.sleep(2000);
new Thread(()-> {
System.out.println(new Date() + " " + Thread.currentThread().getName() + " 这里通过interrupt()唤醒thread0线程");
// 无需获取锁就可以调用interrupt
thread0.interrupt();
}).start();
}
wait() 与 notify()
wait、notify和notifyAll方法是Object类的final native方法。所以这些方法不能被子类重写,Object类是所有类的超类,因此在程序中可以通过this或者super来调用this.wait(), super.wait()
- wait(): 导致线程进入等待 阻塞状态,会一直等待直到它被其他线程通过notify()或者notifyAll唤醒。wait()方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。wait(long timeout): 时间到了自动执行,类似于sleep(long millis)
- notify(): notify方法只能在同步方法或同步块内部调用, 调用notify方法后,随机选择一个在该对象上调用wait方法的 线程,解除其阻塞状态
- notifyAll(): 唤醒所有线程的wait对象
注意:
- Object.wait()和Object.notify()和Object.notifyall()必须写在synchronized方法内部或者synchronized块内部
- 让哪个对象等待wait就去通知notify哪个对象,不要让A对象等待,结果却去通知B对象,要操作同一个对象
Object
public class Object {
public final void wait() throws InterruptedException;
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException;
public final native void notify();
public final native void notifyAll();
}
WaitNotifyTest
public class WaitNotifyTest {
//同步方法
private synchronized void synMethod() throws InterruptedException {
System.out.println(new Date() + " " + Thread.currentThread().getName() + " 调用wait()方法,进入线程等待 阻塞状态");
this.wait();
System.out.println(new Date() + "\t" + Thread.currentThread().getName() + " 被其它线程调用notify或notifyall唤醒");
}
private synchronized void synNotify() {
this.notify();
System.out.println(new Date() + " " + Thread.currentThread().getName() + " notify通知其它同步wait线程...");
}
}
public static void main(String[] args) throws Exception {
WaitNotifyTest waitNotifyTest = new WaitNotifyTest();
new Thread(() -> {
try {
waitNotifyTest.synMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
waitNotifyTest.synMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println(new Date() + " " + Thread.currentThread().getName() + " sleep1秒,让上面的线程先执行,即先执行wait(),然后再通过这个线程去notify");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
waitNotifyTest.synNotify();
}).start();
}
输出结果:
2021xxxx 线程1 调用wait()方法,进入线程等待 阻塞状态
2021xxxx 线程2 调用wait()方法,进入线程等待 阻塞状态
2021xxxx 线程3 sleep1秒,让上面的线程先执行,先执行wait(),然后再通过这个线程去notify
2021xxxx 线程3 notify通知其它同步wait线程...
2021xxxx 线程2 被其它线程调用notify或notifyall唤醒
this.notifyAll();
输出结果:
2021xxxx 线程1 调用wait()方法,进入线程等待 阻塞状态
2021xxxx 线程2 调用wait()方法,进入线程等待 阻塞状态
2021xxxx 线程3 sleep1秒,让上面的线程先执行,先执行wait(),然后再通过这个线程去notify
2021xxxx 线程3 notify通知其它同步wait线程...
2021xxxx 线程1 被其它线程调用notify或notifyall唤醒
2021xxxx 线程2 被其它线程调用notify或notifyall唤醒
匿名内部类
匿名内部类就是一个隐匿且无名的内部类,内部类就是字面意思写在一个类里面的类,正常内部类需要在一个类里面定义,然后在类里面通过new的方式对内部类进行实例化,而匿名内部类不需要定义,在用到它的时候才定义并实例化用,直接在new的时候进行定义和实例化。匿名内部类是一种特殊的内部类由于它没有名字,所以也是独一无二的存在,在一个类里面只能有一个匿名内部类,当我们需要定义一个只需要用一次的内部类是就可以使用匿名内部类来简化代码编写,使用匿名内部类还有个前提条件:必须继承一个父类或实现一个接口,但最多只能继承一个父类,或实现一个接口。
abstract class Person{
public abstract void makeLove();
}
public class Demo {
//这个逼待会被写成匿名内部类(Demo类里只有一个Child内部类,且它只需要被使用一次)
static class Child extends Person{
@Override
public void makeLove() {
System.out.println("做爱");
}
}
//测试方法用到内部类
public static void main(String[] args) {
//正常调用内部类
Child child = new Child();
child.makeLove();
}
}
//抽象父类
abstract class Person{
public abstract void makeLove();
}
public class Demo {
//测试方法用到内部类
public static void main(String[] args) {
//匿名内部类用法
Person child=new Person() {
@Override
public void makeLove() {
System.out.println("做爱");
}
};
child.makeLove();
//缩简版 一气呵成调用
new Person(){
@Override
public void makeLove() {
System.out.println("做爱");
}
}.makeLove();
}
}
预热下 更简洁的Lambda写法(代码实践发现实现接口才能写成Lambda,继承抽象类不行)
//接口
interface Person{
public void makeLove();
}
public class Demo {
public static void main(String[] args){
//Lambda表达式调用
Person person = () -> {
System.out.println("做爱");
};
person.makeLove();
//没有缩简版写法(下面这样写没用会报错)
(Person)()->{System.out.println("做爱");}.makeLove();
}
}
Lambda表达式
λ(Lambda)表达式可以被当做是一个Object(注意措辞,可以是,只能是一点点)。
λ(Lambda)表达式的目标类型是“函数式接口(functional interface)”。
函数式接口(functional interface)是Java8新引入的概念。
它的定义是:一个接口,如果只有一个显式声明的抽象方法,
那么它就是一个函数式接口。一般用@FunctionalInterface标注出来(也可以不标)。
函数式接口(functional interface)
@FunctionalInterface
public interface Runnable { void run(); }
上面函数式接口写成Lambda表达式
//没错就是这么简洁
()->{run方法代码}
//使用方式
Runnable runnable=()->{System.out.println("Lambda");};
//或者
Object obj=(Runnable)()->{System.out.println("Lambda");};
λ(Lambda)表达式主要用于替换以前广泛使用的内部匿名类
//匿名内部类
Thread newThread = new Thread( new Runnable () {
@Override
public void run() {
System.out.println("This is from an anonymous class.");
}
} );
//Lambda表达式
Thread newThread = new Thread( () -> {
System.out.println("This is from an anonymous method (lambda exp).");
} );
//最简洁的开启线程写法
new Thread(()->{System.out.println("最简洁的开启线程写法");}).start();