您的位置:首页 > 编程语言 > Java开发

Java基础进阶_day16_(多线程,互斥锁,线程池,定时器,设计模式)

2017-04-15 23:35 841 查看

Java基础进阶_day16_(多线程,线程池,定时器,设计模式)

Java基础进阶_day16_多线程线程池定时器设计模式

多线程
1 线程死锁
死锁案例

2 互斥锁
案例代码

3 多线程间的通信
线程间的等待唤醒机制
方式1

方式2

4 线程组

5 多线程总结

线程池

定时器

设计模式
1 设计模式概述

2 设计模式分类
21 简单工厂模式

22 工厂方法模式

23 单例模式

24 模板设计模式

25 装饰设计模式

1. 多线程

1.1 线程死锁

线程死锁:是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象.多由同步代码块的嵌套产生.

死锁案例

public class My_DeadLock_Demo01 {
public static void main(String[] args) {
// 死锁验证,方式1
//      Object obj1 = new Object();
//      Object obj2 = new Object();
//      DeadLock dl = new DeadLock(true, obj1, obj2);
//      DeadLock dl2 = new DeadLock(false, obj1, obj2);
//      dl.start();
//      dl2.start();
// 死锁验证,方式2
DeadLock2 dl2 = new DeadLock2();
Thread t1 = new Thread(dl2);
Thread t2 = new Thread(dl2);
t1.start();
t2.start();
}
}
// 死锁验证方式1
class DeadLock extends Thread {
// 定义两把锁对象
private Object obj1 = new Object();
private Object obj2 = new Object();
private boolean flag = true;
public DeadLock(boolean flag, Object obj1, Object obj2) {
this.flag = flag;
this.obj1 = obj1;
this.obj2 = obj2;
}
@Override
public void run() {
if (flag) {
synchronized (obj1) {
System.out.println("if obj1");
synchronized (obj2) {
System.out.println("if obj2");
}
}
} else {
synchronized (obj2) {
System.out.println("else obj2");
synchronized (obj1) {
System.out.println("else obj1");
}
}
}
}
}
// 死锁验证方式2
class DeadLock2 implements Runnable {
// 定义两把锁对象
private Object obj1 = new Object();
private Object obj2 = new Object();
@Override
public void run() {
if (Math.random() > 0.5) {
synchronized (obj1) {
System.out.println("同步代码块1,obj1");
synchronized (obj2) {
System.out.println("同步代码块1,obj2");
}
}
} else {
synchronized (obj2) {
System.out.println("同步代码块2,obj2");
synchronized (obj1) {
System.out.println("同步代码块2,obj1");
}
}
}
}
}


1.2 互斥锁

Lock接口:Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作.

方法:将需要进行同步的线程操作代码使用下面的两个方法进行限定:

​ void lock():获取锁;

​ void unlock():释放锁.

案例代码

public class My_Lock_Demo01 {
public static void main(String[] args) {
SellTickets s = new SellTickets();
Thread t1 = new Thread(s, "窗口1");
Thread t2 = new Thread(s, "窗口2");
Thread t3 = new Thread(s, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
class SellTickets implements Runnable {
private int tickets = 100;
// 定义锁对象
private ReentrantLock rl = new ReentrantLock();
@Override
public void run() {
// 获取锁
try {
rl.lock();
while (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在售卖第"
+ (tickets--) + "张票");
}
} finally {
// 释放锁
rl.unlock();
}
}
}


1.3 多线程间的通信

线程间通信:不同种类的线程针对同一种资源进行操作,不同种类的线程使用的锁对象相同.

线程间的等待唤醒机制

方式1

# 等待唤醒机制针对的是锁对象,方法定义在Object类中.
* public final void wait():将当前使用的锁对象的线程设置为等待状态.
wait()方法执行时,将对象释放并在停止地方一直等待,当被唤醒时,会从等待的地方继续向后执行.
* public final void notify():唤醒在此锁对象监视器上等待的单个线程,唤醒是随机的.
notify()方法唤醒其他线程,只是通知其他线程,其他线程的执行还是需要抢占CPU的执行权.
* public final void notifyAll():唤醒在此锁对象监视器上等待的所有线程.


案例代码

/*
*使用同步代码块的等待唤醒机制
*/
public class My_ThreadCommunication_Demo01 {
public static void main(String[] args) {
// 不同的线程使用共享的数据
Person p = new Person();
// 操作数据对象
SetInfo si = new SetInfo(p);
GetInfo gi = new GetInfo(p);
// 操作数据的线程
Thread t1 = new Thread(si);
Thread t2 = new Thread(gi);
// 启动线程
t1.start();
t2.start();
}
}
// 设置数据信息
class SetInfo implements Runnable {
private Person p = null;
int num = 1;
public SetInfo(Person p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized (p) {
// 设置等待,当数据为空时,将线程设为等待
if(p.flag) {
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (num % 2 == 0) {
p.name = "张三";
p.age = 23;
} else {
p.name = "李四";
p.age = 24;
}
num++;
// 当数据源有数据时,将该锁对象上的其他线程唤醒
p.flag = true;
p.notify();
}
}
}
}
// 获取数据信息
class GetInfo implements Runnable {
private Person p = null;
public GetInfo(Person p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized (p) {
if(!p.flag) {
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(p.name + "..." + p.age);
p.flag = false;
p.notify();
}
}
}
}
// 原始数据类
class Person {
String name;
int age;
// 定义是否有数据的标识
boolean flag = false;
}

/*
* 使用同步方法的方式实现等待唤醒机制.
*/
public class My_ThreadCommunication_Demo02 {
public static void main(String[] args) {
// 不同的线程使用共享的数据
Person2 p = new Person2();
// 操作数据对象
SetInfo2 si = new SetInfo2(p);
GetInfo2 gi = new GetInfo2(p);
// 操作数据的线程
Thread t1 = new Thread(si);
Thread t2 = new Thread(gi);
// 启动线程
t1.start();
t2.start();
}
}
// 设置数据信息
class SetInfo2 implements Runnable {
private Person2 p = null;
int num = 1;
public SetInfo2(Person2 p) {
this.p = p;
}
@Override
public void run() {
while (true) {
if (num % 2 == 0) {
p.setData("张三", 23);
} else {
p.setData("李四", 24);
}
num++;
}
}
}
// 获取数据信息
class GetInfo2 implements Runnable {
private Person2 p = null;
public GetInfo2(Person2 p) {
this.p = p;
}
@Override
public void run() {
while (true) {
p.getData();
}
}
}
// 原始数据类
class Person2 {
private String name;
private int age;
// 定义是否有数据的标识
private boolean flag = false;
// 使用同步的方法获取和设置数据的值
// 锁对象是本类的实例
public synchronized void setData(String name, int age) {
if (this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.age = age;
this.flag = true;
this.notify();
}
public synchronized void getData() {
if (!this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.name + "..." + this.age);
this.flag = false;
this.notify();
}
}


方式2

# ReentrantLock类是Lock接口的实现类.
* 多线程间的等待唤醒机制:
使用ReentrantLock类的newCondition()方法可以获取Condition对象;
需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法;
不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了.


案例代码

/*
* ReentrantLock类:多线程间的等待唤醒机制
*/
public class My_Lock_Demo02 {
public static void main(String[] args) {
final Printer p = new Printer();
// 创建线程1
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print1();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
// 创建线程2
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
// 创建线程3
new Thread() {
@Override
public void run() {
while (true) {
try {
p.print3();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
}
class Printer {
// 定义Lock锁对象
private ReentrantLock rl = new ReentrantLock();
// 定义第一个方法的Condition
Condition c1 = rl.newCondition();
// 定义第二个方法的Condition
Condition c2 = rl.newCondition();
// 定义第三个方法的Condition
Condition c3 = rl.newCondition();
// 定义方法执行标志
private int flag = 1;
public void print1() throws InterruptedException {
rl.lock();
if (flag != 1) {
// 该线程等待
c1.await();
}
System.out.print("张");
System.out.print("三");
System.out.println();
flag = 2;
// 唤醒c2对应的线程
c2.signal();
rl.unlock();
}
public void print2() throws InterruptedException {
rl.lock();
if (flag != 2) {
c2.await();
}
System.out.print("李");
System.out.print("四");
System.out.println();
flag = 3;
c3.signal();
rl.unlock();
}
public void print3() throws InterruptedException {
rl.lock();
if (flag != 3) {
c3.await();
}
System.out.print("王");
System.out.print("五");
System.out.println();
flag = 1;
c1.signal();
rl.unlock();
}
}


1.4 线程组

线程组:Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。所有线程默认的是属于主线程组.

Thread类中:

public final ThreadGroup getThreadGroup():返回该线程所属的线程组;

public Thread(ThreadGroup group,Runnable target):将创建的线程对象添加到指定的线程组中.

ThreadGroup类:

public ThreadGroup(String name):构造一个新线程组;

public final String getName():返回此线程组的名称;

public final void setDaemon(boolean daemon):将此线程组的所有线程设为守护线程;

public final void setMaxPriority(int pri):设置线程组的最高优先级;

public final void destroy():销毁此线程组及其所有子组.

例如:

获取当前线程所属的线程组

System.out.println(Thread.currentThread().getThreadGroup().getName()); // main

1.5 多线程总结

# 多线程常见问题总结:
* 1.多线程常见实现方式:
A:继承Thread类,重写run方法,在run方法中定义需要线程执行的代码,然后使用start方法启动线程;
B:实现Runnable接口,重写run方法,在run方法中定义需要执行的代码,然后作为Thread实例的参数,再使用start方法启动线程;
C:Callable接口实现,需要依赖线程池启动线程.
* 2.线程的同步方式:
A:同步代码块
同步代码块的锁对象是任意对象;
静态代码块的锁对象是.class文件
B:同步方法
同步方法的锁对象是this
* 3.run()和start()区别:
run()方法是定义需要线程执行的代码,直接调用和普通方法效果相同;
start()方法是启动线程,由jvm调用线程的run()方法.
* 4.sleep()和wait()方法的区别:
sleep()方法:必须指定线程的休眠时间,休眠时间到了则会继续执行程序,并且在同步方法或同步代码块中不释放锁对象;
wait()方法:参数的时间可有可无,参数的含义是多长时间后开始等待,并且在同步方法或同步代码块中会释放锁对象.
* 5.wait(),notify()和notifyall()方法均定义在Object类中:
由于同步代码块的锁对象是任意对象,而这些方法都是由锁对象进行调用的,所以将这些方法均定义在Object中.
* 6.线程的生命周期图(常见的两种)
新建-->就绪-->运行-->死亡
新建-->就绪-->运行-->阻塞-->就绪-->运行-->死亡
* 7.线程的状态转换图
新建-->就绪-->运行-->死亡
新建-->就绪-->运行-->就绪-->运行-->死亡
新建-->就绪-->运行-->同步阻塞-->就绪--运行-->死亡
新建-->就绪-->运行-->等待阻塞-->同步阻塞-->就绪-->运行-->死亡
新建-->就绪-->其他阻塞-->就绪-->运行-->死亡


线程状态转换图



2. 线程池

线程池:使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池.

线程池特点:

​ 线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用.

JDK5版本增加工厂类创建线程池

# Executors类:
public static ExecutorService newCachedThreadPool():创建带有缓冲区功能的线程池;
public static ExecutorService newFixedThreadPool(int nThreads):创建含有指定个数线程的线程池;
public static ExecutorService newSingleThreadExecutor():创建单个线程的线程池.
这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程.它提供了如下方法:
Future<?> submit(Runnable task):提交runnable方式的任务;
<T> Future<T> submit(Callable<T> task):提交callable方式的任务;


案例代码

public class My_ThreadPool_Demo01 {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);
// 提交任务给线程池进行执行
pool.submit(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()
+ ":" + i);
}
}
}
});
// 关闭线程池
pool.shutdown();
}
}


实现Callable接口的方式创建线程

# 线程实现的Callable接口的方式:
* Callable接口:返回结果并且可能抛出异常的任务,实现者定义了一个不带任何参数的叫做call的方法.
*  Callable接口实现的线程只能通过线程池进行调用.
# 好处:
*  可以有返回值;
*  可以抛出异常.
# 弊端:
*  调用依赖线程池;
*  代码较为复杂,一般不使用.


案例代码

public class My_ThreadPool_Demo02 {
public static void main(String[] args) throws Exception, ExecutionException {
MyThread mt = new MyThread(8);
MyThread mt2 = new MyThread(9);
ExecutorService pool = Executors.newFixedThreadPool(2);
// pool提交任务后可以通过Future异步的方式获取结果
Future<Integer> f1 = pool.submit(mt);
Future<Integer> f2 = pool.submit(mt2);
System.out.println(f1.get() + ".." + f2.get());
// 关闭线程池
pool.shutdown();
}
}
class MyThread implements Callable<Integer> {
private int num = 0;
public MyThread(int num) {
this.num = num;
}
@Override
public Integer call() throws Exception {
int sum = 1;
for (int i = 1; i <= num; i++) {
sum = sum * i;
}
return sum;
}
}


3. 定时器

定时器:定时器可用于调度多个定时任务以后台线程的方式执行.在Java中,可以通过Timer和TimerTask类来实现定义调度的功能.

# Timer类:Timer
*  构造方法:public Timer():创建一个计时器.
*  成员方法:
public void schedule(TimerTask task,Date time):安排在指定的时间执行指定的任务;
public void schedule(TimerTask task, long delay):创建在延迟指定毫秒值后执行的任务;
public void schedule(TimerTask task,long delay,long period):创建指定延迟时间并间隔period重复执行任务.
# TimerTask抽象类:
public abstract void run():定义计时器要执行的任务;
public boolean cancel():取消任务.


案例代码

public class My_Timer_Demo01 {
public static void main(String[] args) {
Timer t = new Timer();
// 开启定时器
t.schedule(new MyTimer(t), 2000, 2000);
}
}
// 定时器任务类
class MyTimer extends TimerTask {
private Timer t = null;
public MyTimer(Timer t) {
this.t = t;
}
@Override
public void run() {
System.out.println("hello somnus");
// 终止计时器
t.cancel();
}
}


4. 设计模式

4.1 设计模式概述

# 设计模式:设计模式(Design pattern)是一套被反复使用,多数人知晓的,经过分类编目的,代码设计经验的总结.
* 使用设计模式是为了可重用代码,让代码更容易被他人理解,保证代码可靠性.
* 设计模式不是一种方法和技术,而是一种思想.
* 设计模式和具体的语言无关,学习设计模式就是要建立面向对象的思想,尽可能的面向接口编程,低耦合,高内聚,使设计的程序可复用.


4.2 设计模式分类

# 设计模式的分类
* 创建型模式:对象的创建
* 结构型模式:对象的组成(结构)
* 行为型模式 :对象的行为
# 常见的设计模式:
* 创建型模式:简单工厂模式,工厂方法模式,抽象工厂模式,建造者模式,原型模式,单例模式.(6个)
* 结构型模式:外观模式,适配器模式,代理模式,装饰模式,桥接模式,组合模式,享元模式.(7个)
* 行为型模式:模版方法模式,观察者模式,状态模式,职责链模式,命令模式,访问者模式,策略模式,备忘录模式,迭代器模式,解释器模式.(10个)


4.2.1 简单工厂模式

# 简单工厂模式:
* 又叫静态工厂方法模式,它定义一个具体的工厂类负责创建一些类的实例.
# 优点:
* 客户端不需要在负责对象的创建,从而明确了各个类的职责.
# 缺点:
* 这个静态工厂类负责所有对象的创建,如果有新的对象增加,或者某些对象的创建方式不同,就需要不断的修改工厂类,不利于后期的维护.


案例代码

public class PersonDemo {
public static void main(String[] args) {
Person p = PersonFactory.creatStudent();
Person p2 = PersonFactory.creatTeacher();

p.eat();
p2.eat();
}
}
abstract class Person {
public abstract void eat();
}
class Student extends Person {
@Override
public void eat() {
System.out.println("学生吃饭");
}
}
class Teacher extends Person {
@Override
public void eat() {
System.out.println("教师吃饭");
}
}
class PersonFactory {
private PersonFactory() {
}
public static Student creatStudent() {
return new Student();
}
public static Teacher creatTeacher() {
return new Teacher();
}
}


4.2.2 工厂方法模式

# 工厂方法模式:
* 工厂方法模式中抽象工厂类负责定义创建对象的接口,具体对象的创建工作由继承抽象工厂的具体类实现.
# 优点:
* 客户端不需要在负责对象的创建,从而明确了各个类的职责,如果有新的对象增加,只需要增加一个具体的类和具体的工厂类即可,
* 不影响已有的代码,后期维护容易,增强了系统的扩展性.
# 缺点:
* 需要额外的编写代码,增加了工作量.


案例代码

public class AnimalDemo {
public static void main(String[] args) {
Factory f1 = new CreatDog();
Animal dog = f1.creatAnimal();
Factory f2 = new CreatCat();
Animal cat = f2.creatAnimal();
dog.eat();
cat.eat();
}
}
abstract class Animal {
public abstract void eat();
}
interface Factory{
public abstract Animal creatAnimal();
}
class CreatDog implements Factory {
@Override
public Animal creatAnimal() {
return new Dog();
}
}
class CreatCat implements Factory {
@Override
public Animal creatAnimal() {
return new Cat();
}
}
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗吃骨头");
}
}
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫吃鱼");
}
}


4.2.3 单例模式

# 单例模式:API中的Runtime类是单例模式
* 单例模式就是要确保类在内存中只有一个对象,该实例必须自动创建,并且对外提供.
* 优点:
在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能.
* 缺点:
没有抽象层,因此扩展很难;
职责过重,在一定程序上违背了单一职责.
# 单例模式分类:选用饿汉式
* 饿汉式:类在加载的时候创建对象
* 懒汉式:调用的时候才创建对象
* 声明静态常量的方式.
# 单例模式的思想和实现方式:
* 单例的思想是保证类在内存中只有一个对象.
* 实现方式:
饿汉式:其操作是原子性的不会出现线程安全问题;
懒汉式:延迟加载,可能出现线程安全问题(多条语句操作共享数据,解决方法是使用同步方法).


案例代码

public class My_SingleObject_Demo {
public static void main(String[] args) {
}
}
// 饿汉式单例
class SingleObject {
// 私有构造方法
private SingleObject(){
}
// 创建本类的实例对象
public static SingleObject s = new SingleObject();
// 提供获取该类对象的方法
public static SingleObject getSingleObject() {
return s;
}
}
// 懒汉式单例
class SingleObject2 {
// 私有构造方法
private SingleObject2(){
}
// 定义本类的实例对象
public static SingleObject2 s = null;
// 提供获取该类对象的方法
// 方式1,将方法定义为同步方法,避免线程安全问题
public synchronized static SingleObject2 getSingleObject() {
// 有两条语句操作共享数据s,则使用同步方法
if(s == null) {
s = new SingleObject2();
}
return s;
}
// 方式2,双重判断,避免线程安全问题
/*public static SingleObject2 getSingleObject() {
// 有两条语句操作共享数据s,则使用同步方法
if(s == null) {
synchronized(SingleObject2.class){
if(s == null) {
s = new SingleObject2();
}
}
}
return s;
}*/
}
// 静态常量的单例模式
class SingleObject3 {
// 私有构造方法
private SingleObject3(){
}
// 创建本类的静态常量
public static final SingleObject3 so = new SingleObject3();
}


4.2.4 模板设计模式

# 模板设计模式:
* 模版方法模式就是定义一个算法的骨架,而将具体的算法延迟到子类中来实现.
# 优点:
* 使用模版方法模式,在定义算法骨架的同时,可以很灵活的实现具体的算法,满足用户灵活多变的需求.
# 缺点:
* 如果算法骨架有修改的话,则需要修改抽象类.


案例代码

public class My_Templet_Demo {
public static void main(String[] args) {
// 测试运行某段代码的时间
GetTime gt = new GetTime() {
@Override
public void code() {
for (int i = 0; i < 100000; i++) {
System.out.println(i);
}
}
};
// 获取代码运行的时间
long time = gt.getTime();
System.out.println(time+"毫秒");
}
}
// 定义计算某段代码运行的时间
abstract class GetTime {
public long getTime() {
// 初始时间
long start = System.currentTimeMillis();
// 计算的代码
this.code();
// 程序运行的时间
long end = System.currentTimeMillis();
// 将时间返回
return end - start;
}
// 需要被计算时间的代码
public abstract void code();
}


4.2.5 装饰设计模式

# 装饰设计模式:
* 装饰模式就是使用被装饰类的一个子类的实例,在客户端将这个子类的实例交给装饰类,是继承的替代方案.
# 优点:
* 使用装饰模式,可以提供比继承更灵活的扩展对象的功能,它可以动态的添加对象的功能,并且可以随意的组合这些功能.
# 缺点:
* 正因为可以随意组合,所以就可能出现一些不合理的逻辑.
# 关系:
*      接口      --->    实现接口的抽象类
*       |                      |
*      实现接口的子类     不同的装饰类


案例代码

public class My_Decorate_Demo {
public static void main(String[] args) {
//  普通类测试
Phone p = new HuaWei();
p.call();
// 装饰彩铃
PhoneDecorate pd = new RingPhoneDecorate(p);
pd.call();
// 装饰音乐
PhoneDecorate pd2 = new MusicPhoneDecorate(p);
pd2.call();
// 先装饰彩铃,再装饰音乐
PhoneDecorate pd3 = new RingPhoneDecorate(pd2);
pd3.call();
}
}
// 定义接口
interface Phone {
// 定义打电话的功能
public abstract void call();
}
// 定义实现接口的类
class HuaWei implements Phone {
@Override
public void call() {
System.out.println("手机打电话");
}
}
// 定义实现接口的抽象类
abstract class PhoneDecorate implements Phone {
private Phone p;
public PhoneDecorate(Phone p) {
this.p = p;
}
// 重写call方法,并调用p的call方法
@Override
public void call() {
this.p.call();
}
}
// 定义具体的装饰类
class RingPhoneDecorate extends PhoneDecorate {
public RingPhoneDecorate(Phone p) {
super(p);
}
// 重写call方法,加入要装饰的内容
@Override
public void call() {
System.out.println("手机彩铃");
super.call();
}
}
// 定义装饰音乐的装饰类
class MusicPhoneDecorate extends PhoneDecorate {
public MusicPhoneDecorate(Phone p) {
super(p);
}
// 重写call方法,加入要装饰的内容
@Override
public void call() {
super.call();
System.out.println("手机听音乐");
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐