多线程-静态代理设计模式
使用implements Runnable接口实现多线程,必须使用Thread类的对象来启动线程,这个Thread类的对象是静态代理对象。
静态代理是,使用的类是已经构建好的,直接使用;
动态代理是,使用的类是临时写的,所以叫动态代理。
静态代理可以记录日志,谁什么时候进入,什么时候退出。
静态代理模式的代理角色和真实角色必须实现同一个类,在代理角色中**“忙前忙后”**中间做真实角色的事情。
下面是一个使用静态代理模式的程序实例:
package ThreadStudy;
/**
* 静态代理模式
* 1.真实对象
* 2.代理对象
*
* 静态代理模式需要有的结构包括:
* 1.一个接口;
* 2.一个真实对象实现这个接口;
* 3.一个代理对象实现这个接口,并且传递真实对象参数,在代理对象类中进行“代理”操作。
*
* @author 发达的范
* @version 1.0
* @date 2021/04/19 21:53
*/
public class StaticProxy {
public static void main(String[] args) {
new WeddingProxy(new Person()).marry();
}
}
interface Marry {
void marry();
}
//真实对象
class Person implements Marry {
@Override
public void marry() {
System.out.println("恭喜你结婚了!");
}
}
//代理对象
class WeddingProxy implements Marry {
private Person person;
public WeddingProxy(Person person) {//构造器,有属性
this.person = person;
}
@Override
public void marry() {//代理函数
prepare();
this.person.marry();
finish();
}
public void prepare() {
System.out.println("结婚的准备工作做完了!");
}
public void finish() {
System.out.println("结婚的后续工作完成了!");
}
}
运行结果:
感觉静态代理模式有点像装饰器模式,都是使用一个类对另一个类进行修饰工作,或者说添加一些操作,装饰器设计模式是使用一个装饰器类和一个被装饰的类共同实现一个接口,然后再装饰器类中放置一个被装饰的类的对象,然后在装饰器类中对被装饰的类的对象进行修饰(比如数值放大等),装饰器设计模式的更多内容看17节。而静态代理模式跟装饰器模式的区别就是在代理角色类中的接口方法中,对传入的真实角色类的对象的接口方法的前后进行**“忙前忙后”**操作,也就是准备和善后。这就是和装饰器设计模式唯一的区别了。
静态代理可以记录日志,谁什么时候进入,什么时候退出,就是使用代理角色的接口方法中忙前忙后的操作来记录。也可以做监控,用了多少内存等。
lambda表达式
主要用于简化多线程中使用次数比较少的对象的匿名内部类。
lambda表达式:简化线程的使用,这个线程使用一次,或者很少的次数;
lambda表达式里面只能有一个方法,接口中不能有多个方法。
避免匿名内部类使用过多,简化编程,属于函数式编程。
下面看一个lambda表达式的推导过程实例:
package ThreadStudy;
/**
* lambda表达式的原理推导及其使用
*
* @author 发达的范
* @version 1.0
* @date 2021/04/25 20:24
*/
public class LambdaThread {
//静态内部类,不使用它的外部类就不会被编译,使用了才会编译
static class Test implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("吃饭");
}
}
}
public static void main(String[] args) {
new Thread(new Test()).start();//开启静态内部类的线程
//局部内部类
class Test2 implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("睡觉");
}
}
}
new Thread(new Test2()).start();//开启局部内部类的线程
//匿名内部类,匿名匿掉的是类名,必须借助接口或者父类
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("刷牙");
}
}
}).start();
//jdk8简化,lambda表达式,简化简单的线程类
//上一步是匿名内部类匿掉了类名,到这里lambda表达式直接连接口/父类名都不要了,run方法名也不要了
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("洗脸");
}
}).start();
}
}
运行结果正常。
我们看到这个程序,
-
首先是普通的外部类,这个类implements Runnable接口,那么就要重写run方法,在main方法中借助new Thread 对象的start方法开启线程;
-
然后是静态内部类,就是在普通的类中定义一个static修饰的静态内部类,这个静态内部类implements Runnable接口,在main方法中把静态内部类的对象传入new Thread (),然后使用new Thread ()的start方法开启线程;
-
然后是局部内部类,局部内部类是定义在(main)方法中的类,同样也需要implements Runnable,并重写run方法,同理也是把局部内部类的对象传入new Thread (),然后使用new Thread ()的start方法开启线程;
-
然后是匿名内部类,匿名匿掉的是他自己的类名,也就是说按照前面的思路,应该是把XX内部类的对象传入new Thread (),然后使用new Thread ()的start方法开启线程,但是这里是匿名内部类,也就无法把XX内部类的对象传入new Thread (),所以直接借助接口或者父类,直接在new Thread ()里面实现run方法,如下:
new Thread(new Runnable() { public void run() { for (int i = 0; i < 10; i++) { System.out.println("刷牙"); } } }).start();
-
然后就是lambda表达式,继续简化掉了借助接口或者父类的操作,和run方法的方法名,直接写方法体,如下:
new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println("洗脸"); } }).start();
需要注意的是:
- lambda表达式:简化线程的使用,这个线程使用一次,或者很少的次数;
- lambda表达式里面只能有一个方法,接口中不能有多个方法。
2021/05/11 20.11增加内容:
如何直接写lambda表达式?
可以每次都思考一遍这个过程:普通外部类->静态内部类->局部内部类->匿名内部类->lambda表达式。
我们知道,匿名内部类匿掉的是自己的类名,直接使用接口/父类来实现线程的操作和开启,而lambda表达式是更进一步,直接把接口/父类都省略以及run方法方法名都省略直接写方法体,所以可以把这个过程记忆为:匿名内部类->lambda表达式。
下面是一个使用自定义的接口实现lambda表达式的推导的程序:
package ThreadStudy;
/**
* @author 发达的范
* @version 1.0
* @date 2021/04/25 21:10
*/
public class LambdaMy {
public static void main(String[] args) {
Person person = new Student0();
person.eat();
person = new Student1();
person.eat();
//静态内部类
static class Student1 implements Person {
@Override
public void eat() {
System.out.println("Student is eat1!");
}
}
//局部内部类
class Student2 implements Person {
@Override
public void eat() {
System.out.println("Student is eat2!");
}
}
person = new Student2();
person.eat();
//匿名内部类,new 了一个接口,直接接口后面写方法,匿名匿掉的是类名,借助接口或者父类
person = new Person() {
@Override
public void eat() {
System.out.println("Student is eat3!");
}
};
person.eat();
//lambda表达式,lambda推导必须存在类型,也即是必须先实例化person,对象不能这样() -> {}
person = () -> {
System.out.println("Student is eat4!");
};
person.eat();
}
//接口
interface Person {
void eat();
}
}
//外部类
class Student0 implements LambdaMy.Person {
@Override
public void eat() {
System.out.println("Student is eat0!");
}
}
运行结果:
使用自定义的类实现lambda表达式,需要实例化一个实现了接口的类的对象,然后使用这个对象来做lambda表达式()->{},表达式里面写接口的方法体,然后使用这个对象来调用这个抽象方法。
不管是哪种方法都需要写方法体,而且经过测试如果lambda表达式的方法体中的语句与他的实现类中不同,在运行lambda表达式的时候运行的是lambda表达式的方法体,如下:
package ThreadStudy; /** * lambda表达式,加入参数 * * @author 发达的范 * @version 1.0 * @date 2021/04/25 22:00 */ public class LambdaMy03 { public static void main(String[] args) { //接口不能被实例化,但是这里并不是实例化接口,也是父类引用指向子类的对象 Person person = (a, b) -> { // System.out.println("I'm " + a +" "+ "I'm " + b); System.out.println("I'm " + 100 * a + " " + "I'm " + 100 * b); return a + b; }; System.out.println(person.lambda(1, 2)); } interface Person { int lambda(int a, int b); } static class Teacher implements Person { public int lambda(int a, int b) { System.out.println("I'm " + a + "I'm " + b); return a + b; } } }
运行结果:
下面是一个加入参数,有返回值的接口方法的lambda实例:
package ThreadStudy;
/**
* lambda表达式,加入参数
*
* @author 发达的范
* @version 1.0
* @date 2021/04/25 22:00
*/
public class LambdaMy03 {
public static void main(String[] args) {
Person person = (int a, int b) -> {
System.out.println("I'm " + a + " " + "I'm " + b);
return a + b;
};
System.out.println(person.lambda(50, 5));
person = (a, b) -> {
System.out.println("I'm " + a + " " + "I'm " + b);
return a + b;
};
System.out.println(person.lambda(40, 4));
person = (a, b) -> a + b;//返回值是a+b,代表return a + b;
System.out.println(person.lambda(30, 3));
}
interface Person {
int lambda(int a, int b);
}
class Teacher implements Person {
public int lambda(int a, int b) {
System.out.println("I'm " + a + "I'm " + b);
return a + b;
}
}
}
运行结果:
其实看了这么多使用lambda表达式进行推导的程序,但是还是觉得理解不是很深刻,不会用,但是经过观察发现,lambda表达式的推导过程总结如下:
-
写一个接口和抽象方法;
-
在类中implements这个接口和他的抽象方法,这里的类可以使外部类,普通内部类,静态内部类,局部内部类,匿名内部类等;
-
对于外部类,普通内部类,静态内部类,局部内部类都需要实例化一个对象,使用对象来调用抽象方法;对于匿名内部类,匿名匿掉的就是类名,所以需要借助它的接口或者父类实现,省略掉方法名,如下:
person = new Person() { @Override public void eat() { System.out.println("Student is eat3!"); } }; person.eat();
-
对于lambda表达式,则是相比匿名内部类的实现方法更加精简,省略接口或父类,省略方法名,直接写方法体:
person = () -> { System.out.println("Student is eat4!"); }; person.eat();
而对于有参和有返回值的接口及抽象方法,lambda表达式推导与上面相似。
下面看使用lambda表达式实现多线程的程序实例:
package ThreadStudy;/**使用lambda表达式实现多线程 * * * @author 发达的范 * @version 1.0 * @date 2021/04/25 22:00 */public class LambdaMy04 { public static void main(String[] args) { //使用lambda表达式开启两个线程 new Thread(()->{ for (int i = 0; i < 20; i++) { System.out.println("Thread111"+" "+i); } }).start(); new Thread(()->{ for (int i = 0; i < 20; i++) { System.out.println("Thread222"+" "+i); } }).start(); //使用匿名内部类实现多线程 new Thread(new Runnable() { public void run() { for (int i = 0; i < 20; i++) { System.out.println("Thread333"+" "+i); } } }).start(); }}
运行结果:
可见,使用lambda表达式可以简化多线程的操作,只需要记住lambda的推导方式即可。
其实实质上就是省略了接口和方法名,直接写方法体。