- 简介
- 线程创建
- 继承Thread类
- 网图下载
- 实现Runnable接口
- 继承Thread类和实现Runnable接口的区别
- 并发问题
- 龟兔赛跑
- 实现Callable接口
- 继承Thread类
- 静态代理模式
- 静态代理模式的好处
- Lambda表达式
- 为什么要使用Lambda表达式
- 函数式接口Functional Interface
- 线程状态
- 线程停止
- 线程休眠
- 模拟网络延时:放大问题发生可能性
- 模拟倒计时
- 模拟时钟
- 线程礼让
- 线程强制执行
- 线程状态观测
- 线程状态
- 线程优先级
- 守护线程(daemon)
https://www.bilibili.com/video/BV1V4411p7EF/
简介
- 程序:指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
- 进程Process:执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。一个进程中可以包含多个线程,至少有一个线程,否则没有存在的意义。
- 线程Thread:CPU调度和执行的单位。
- 真正的多线程指多核。很多多线程是模拟出来的,即在一个CPU的情况下,快速切换线程。
线程创建
继承Thread类
- 自定义线程类继承Thread类
- 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程
package com.qing.demo01;/** * 创建线程方式一:继承Thread类,重写run()方法,调用start()开启线程 * 注意:线程开启不一定立即执行,由CPU调度执行 */public class TestThread1 extends Thread { @Override public void run() { //run方法线程体 for (int i = 0; i < 200; i++) { System.out.println("run线程--" + i); } } //main线程,主线程 public static void main(String[] args) { TestThread1 testThread1 = new TestThread1(); testThread1.start(); for (int i = 0; i < 200; i++) { System.out.println("main线程--" + i); } }}main线程--83main线程--84run线程--0run线程--1run线程--2run线程--3run线程--4run线程--5run线程--6run线程--7run线程--8run线程--9run线程--10run线程--11run线程--12run线程--13run线程--14run线程--15run线程--16run线程--17run线程--18run线程--19run线程--20run线程--21run线程--22main线程--85run线程--23run线程--24main线程--86main线程--87main线程--88main线程--89main线程--90网图下载
package com.qing.demo01;import org.apache.commons.io.FileUtils;import java.io.File;import java.io.IOException;import java.net.URL;/** * 练习Thread,实现多线程同步下载图片 */public class TestThread2 extends Thread { private String url;//网络图片地址 private String name;//保存的文件名 public TestThread2(String url, String name) { this.url = url; this.name = name; } @Override public void run() { WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url, name); System.out.println("下载文件,name: " + name + ",url: " + url); } public static void main(String[] args) { TestThread2 t1 = new TestThread2("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605724974060&di=abc373a617b841a8e78b3c53fcbd9b5c&imgtype=0&src=http%3A%2F%2Fimg.ewebweb.com%2Fuploads%2F20191203%2F16%2F1575361350-YwtjLgUXBq.jpg", "1.jpg"); TestThread2 t2 = new TestThread2("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1578345841,1712921595&fm=26&gp=0.jpg", "2.jpg"); TestThread2 t3 = new TestThread2("https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3131683833,708813674&fm=26&gp=0.jpg", "3.jpg"); t1.start(); t2.start(); t3.start(); }}//下载器class WebDownloader { //下载方法 public void downloader(String url, String name) { try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,downloader方法出现问题"); } }}下载文件,name: 2.jpg,url: https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1578345841,1712921595&fm=26&gp=0.jpg下载文件,name: 3.jpg,url: https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3131683833,708813674&fm=26&gp=0.jpg下载文件,name: 1.jpg,url: https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605724974060&di=abc373a617b841a8e78b3c53fcbd9b5c&imgtype=0&src=http%3A%2F%2Fimg.ewebweb.com%2Fuploads%2F20191203%2F16%2F1575361350-YwtjLgUXBq.jpg实现Runnable接口
- 定义类实现Runnable接口
- 实现run()方法,编写线程执行体
- 传入类对象创建线程对象Thread,调用start()方法启动线程
package com.qing.demo01;//创建线程方式二:实现Runnable接口,重写run()方法,传入类对象创建Thread对象,调用start()方法启动线程public class TestThread3 implements Runnable { @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("run线程--" + i); } } public static void main(String[] args) { TestThread3 testThread3 = new TestThread3(); new Thread(testThread3).start(); for (int i = 0; i < 200; i++) { System.out.println("main线程--" + i); } }}main线程--0run线程--0run线程--1run线程--2run线程--3run线程--4run线程--5run线程--6run线程--7run线程--8run线程--9run线程--10run线程--11run线程--12main线程--1main线程--2main线程--3main线程--4main线程--5main线程--6main线程--7main线程--8main线程--9main线程--10main线程--11main线程--12run线程--13继承Thread类和实现Runnable接口的区别
- 继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免单继承局限性
- 实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:new Thread(实现类对象).start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
并发问题
package com.qing.demo01;/** * 买火车票的例子 * 多个线程同时操作同一个对象 * 发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱。 */public class TestThread4 implements Runnable { //票数 private int ticketNums = 10; @Override public void run() { while (true) { if (ticketNums <= 0) { break; } //模拟延时 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-->买到了第" + ticketNums-- + "票"); } } public static void main(String[] args) { TestThread4 testThread4 = new TestThread4(); new Thread(testThread4,"杨康").start(); new Thread(testThread4,"郭靖").start(); new Thread(testThread4,"黄蓉").start(); }}黄蓉-->买到了第10票杨康-->买到了第9票郭靖-->买到了第10票黄蓉-->买到了第8票郭靖-->买到了第7票杨康-->买到了第6票杨康-->买到了第5票郭靖-->买到了第5票黄蓉-->买到了第5票杨康-->买到了第4票郭靖-->买到了第2票黄蓉-->买到了第3票郭靖-->买到了第1票黄蓉-->买到了第-1票杨康-->买到了第0票龟兔赛跑
package com.qing.demo01;/** * 模拟龟兔赛跑 */public class Race implements Runnable { //胜利者 private static String winner; @Override public void run() { for (int i = 1; i <= 100; i++) { //模拟兔子睡觉 if ("兔子".equals(Thread.currentThread().getName()) && i%10==0) { try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } //判断比赛是否结束 boolean flag = gameOver(i); //如果比赛结束了,终止程序 if (flag) { break; } System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "步"); } } //判断是否完成比赛 private boolean gameOver(int steps) { //判断是否有胜利者 if (winner != null) { return true; } //判断是否跑完100步 if (steps >= 100) { winner = Thread.currentThread().getName(); System.out.println("winner is " + winner); return true; } return false; } public static void main(String[] args) { Race race = new Race(); new Thread(race,"兔子").start(); new Thread(race,"乌龟").start(); }}乌龟-->跑了98步乌龟-->跑了99步winner is 乌龟兔子-->跑了28步实现Callable接口
- 实现Callable接口,需要返回值类型
- 重新call()方法,需要抛出异常
- 创建目标对象
- 创建执行服务:ExecutorService service = Executors.newFixedThreadPool(3);
- 提交执行:Future
r1 = service.submit(t1); - 获取结果:boolean rs1 = r1.get();
- 关闭服务:service.shutdownNow();
package com.qing.demo02;import com.qing.demo01.TestThread2;import org.apache.commons.io.FileUtils;import java.io.File;import java.io.IOException;import java.net.URL;import java.util.concurrent.*;/** * 实现多线程同步下载图片 * 线程创建方式三:实现Callable接口 * Callable的好处 * 1.可以定义返回值 * 2.可以抛出异常 */public class TestCallable implements Callable<Boolean> { private String url;//网络图片地址 private String name;//保存的文件名 public TestCallable(String url, String name) { this.url = url; this.name = name; } @Override public Boolean call() throws Exception { WebDownloader webDownloader = new WebDownloader(); webDownloader.downloader(url, name); System.out.println("下载文件,name: " + name + ",url: " + url); return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { TestCallable t1 = new TestCallable("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605724974060&di=abc373a617b841a8e78b3c53fcbd9b5c&imgtype=0&src=http%3A%2F%2Fimg.ewebweb.com%2Fuploads%2F20191203%2F16%2F1575361350-YwtjLgUXBq.jpg", "1.jpg"); TestCallable t2 = new TestCallable("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1578345841,1712921595&fm=26&gp=0.jpg", "2.jpg"); TestCallable t3 = new TestCallable("https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3131683833,708813674&fm=26&gp=0.jpg", "3.jpg"); //创建执行服务 ExecutorService service = Executors.newFixedThreadPool(3); //提交执行 Future<Boolean> r1 = service.submit(t1); Future<Boolean> r2 = service.submit(t2); Future<Boolean> r3 = service.submit(t3); //获取结果 boolean rs1 = r1.get(); boolean rs2 = r2.get(); boolean rs3 = r3.get(); System.out.println(rs1); System.out.println(rs2); System.out.println(rs3); //关闭服务 service.shutdownNow(); }}//下载器class WebDownloader { //下载方法 public void downloader(String url, String name) { try { FileUtils.copyURLToFile(new URL(url), new File(name)); } catch (IOException e) { e.printStackTrace(); System.out.println("IO异常,downloader方法出现问题"); } }}下载文件,name: 3.jpg,url: https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3131683833,708813674&fm=26&gp=0.jpg下载文件,name: 1.jpg,url: https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605724974060&di=abc373a617b841a8e78b3c53fcbd9b5c&imgtype=0&src=http%3A%2F%2Fimg.ewebweb.com%2Fuploads%2F20191203%2F16%2F1575361350-YwtjLgUXBq.jpg下载文件,name: 2.jpg,url: https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=1578345841,1712921595&fm=26&gp=0.jpgtruetruetrue静态代理模式
- 真实对象和代理对象都要实现同一个接口
- 代理对象要代理真实对象,真实对象作为代理对象的属性
//真实对象public class TestThread3 implements Runnable { @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("run线程--" + i); } } public static void main(String[] args) { TestThread3 testThread3 = new TestThread3(); new Thread(testThread3).start(); }}//代理对象public class Thread implements Runnable { //真实对象 private Runnable target; public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } @Override public void run() { if (target != null) { target.run(); } }}静态代理模式的好处
- 代理对象可以做很多真实对象做不了的事情
- 真实对象专注做自己的事情
Lambda表达式
为什么要使用Lambda表达式
- 避免匿名内部类定义过多
- 可以让代码看起来简洁
- 去掉了一堆没有意义的代码,只留下核心的逻辑
函数式接口Functional Interface
- 定义:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。
public interface Runnable { public abstract void run();}- 对于函数式接口,可以通过lambda表达式来创建该接口的对象。
- 推导lambda表达式
package com.qing.lambda;/** * 推导lambda表达式 */public class TestLambda01 { //3.静态内部类 static class Like2 implements ILike { @Override public void lambda() { System.out.println("I like lambda2"); } } public static void main(String[] args) { ILike like = new Like(); like.lambda(); like = new Like2(); like.lambda(); //4.局部内部类 class Like3 implements ILike { @Override public void lambda() { System.out.println("I like lambda3"); } } like = new Like3(); like.lambda(); //5.匿名内部类:没有类的名称,必须借助接口或者父类 like = new ILike() { @Override public void lambda() { System.out.println("I like lambda4"); } }; like.lambda(); //6.用lambda表达式简化 like = ()->{ System.out.println("I like lambda5"); }; like.lambda(); }}//1.定义一个函数式接口:只有一个抽象方法的接口interface ILike { void lambda();}//2.实现类class Like implements ILike { @Override public void lambda() { System.out.println("I like lambda"); }}I like lambdaI like lambda2I like lambda3I like lambda4I like lambda5package com.qing.lambda;/** * lambda表达式 */public class TestLambda01 { public static void main(String[] args) { ILike like = ()->{ System.out.println("I like lambda5"); }; like.lambda(); }}//1.定义一个函数式接口:只有一个抽象方法的接口interface ILike { void lambda();}- 有参数lambda表达式
package com.qing.lambda;public class TestLambda02 { public static void main(String[] args) { //1.lambda表达式简化 ILove love = (int i)->{ System.out.println("I love you-->" + i); }; love.love(520); //简化1.简化参数类型 love = (i)->{ System.out.println("I love you-->" + i); }; love.love(521); //简化2.简化参数括号 love = i->{ System.out.println("I love you-->" + i); }; love.love(522); //简化3.简化方法括号 love = i->System.out.println("I love you-->" + i); love.love(523); }}interface ILove { void love(int i);}I love you-->520I love you-->521I love you-->522I love you-->523package com.qing.lambda;public class TestLambda02 { public static void main(String[] args) { ILove love = i->System.out.println("I love you-->" + i); love.love(523); }}interface ILove { void love(int i);}线程状态
- 创建状态
- 就绪状态
- 运行状态
- 阻塞状态
- 死亡状态



线程停止
- 不推荐使用JDK提供的stop()、destroy()方法(已废弃)。
- 推荐线程自己停止下来。
- 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行。
package com.qing.state;/** * 测试停止线程 * 1.建议线程正常停止-->利用次数,不建议死循环。 * 2.建议使用标志位-->设置一个标志位。 * 3.不要使用stop()或destroy()等过时或者JDK不建议使用的方法。 */public class TestStop implements Runnable { //1.设置一个标志位 private boolean flag = true; @Override public void run() { int i = 0; while (flag) { System.out.println("run...Thread" + i++); } } //2.设置一个公开的方法停止线程:转换标志位 public void stop() { this.flag = false; } public static void main(String[] args) { TestStop testStop = new TestStop(); new Thread(testStop).start(); for (int i = 0; i < 1000; i++) { System.out.println("main..." + i); if (i == 900) { //调用stop方法切换标志位,让线程停止 testStop.stop(); System.out.println("线程该停止了"); } } }}main...899main...900run...Thread1366run...Thread1367线程该停止了main...901main...902线程休眠
- sleep(毫秒)指定当前线程阻塞的毫秒数。
- sleep存在异常InterruptedException。
- sleep时间达到后线程进入就绪状态。
- sleep可以模拟网络延时、倒计时等。
- 每一个对象都有一个锁,sleep不会释放锁。
模拟网络延时:放大问题发生可能性
package com.qing.state;/** * 模拟网络延时:放大问题发生可能性。 */public class TestSleep implements Runnable { //票数 private int ticketNums = 10; @Override public void run() { while (true) { if (ticketNums <= 0) { break; } //模拟延时 try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-->买到了第" + ticketNums-- + "票"); } } public static void main(String[] args) { TestSleep testSleep = new TestSleep(); new Thread(testSleep,"杨康").start(); new Thread(testSleep,"郭靖").start(); new Thread(testSleep,"黄蓉").start(); }}郭靖-->买到了第9票杨康-->买到了第8票黄蓉-->买到了第10票郭靖-->买到了第7票杨康-->买到了第6票黄蓉-->买到了第5票黄蓉-->买到了第4票杨康-->买到了第3票郭靖-->买到了第2票杨康-->买到了第1票郭靖-->买到了第-1票黄蓉-->买到了第0票模拟倒计时
package com.qing.state;/** * 模拟倒计时 */public class TestSleep2 { public static void tenDown() throws InterruptedException { int num = 10; while (true) { Thread.sleep(1000); System.out.println(num--); if (num <= 0) { break; } } } public static void main(String[] args) { try { tenDown(); } catch (InterruptedException e) { e.printStackTrace(); } }}10987654321模拟时钟
package com.qing.state;import java.text.SimpleDateFormat;import java.util.Date;/** * 模拟时钟 */public class TestSleep3 { public static void main(String[] args) { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); while (true) { try { System.out.println(sdf.format(new Date())); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }}23:45:3923:45:4023:45:4123:45:4223:45:43线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞。
- 将线程从运行状态转为就绪状态。
- 让CPU重新调度,礼让不一定成功,由CPU决定。
package com.qing.state;/** * 测试礼让线程 * 礼让不一定成功,由CPU决定 */public class TestYield { public static void main(String[] args) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + "线程开始执行"); Thread.yield(); System.out.println(Thread.currentThread().getName() + "线程执行完成"); },"a").start(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + "线程开始执行"); System.out.println(Thread.currentThread().getName() + "线程执行完成"); },"b").start(); }}//礼让成功a线程开始执行b线程开始执行b线程执行完成a线程执行完成//礼让不成功a线程开始执行a线程执行完成b线程开始执行b线程执行完成线程强制执行
- join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。
- 可以想象成插队。
package com.qing.state;/** * 测试join方法,想象为插队 */public class TestJoin { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { for (int i = 0; i < 500; i++) { System.out.println("线程vip来了" + i); } }); thread.start(); for (int i = 0; i < 500; i++) { if (i == 100) { thread.join(); } System.out.println("main线程" + i); } }}main线程98main线程99线程vip来了82线程vip来了83线程vip来了84...线程vip来了498线程vip来了499main线程100main线程101线程状态观测
public class Thread implements Runnable { public enum State { NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED; }}线程状态
- NEW:未启动状态。
- RUNNABLE:就绪状态。
- BLOCKED:阻塞状态。
- WAITING:阻塞状态。
- TIMED_WAITING:阻塞状态。
- TERMINATED:死亡状态。
package com.qing.state;/** * 观察测试线程的状态 */public class TestState { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("--------------------------"); } }); //观察状态 Thread.State state = thread.getState(); System.out.println(state);//NEW //观察启动后状态 thread.start(); state = thread.getState(); System.out.println(state);//RUNNABLE //只要线程不终止,就一直输出状态 while (state != Thread.State.TERMINATED) { Thread.sleep(500); state = thread.getState(); System.out.println(state); } }}NEWRUNNABLETIMED_WAITINGTIMED_WAITING--------------------------TIMED_WAITING--------------------------TIMED_WAITINGTIMED_WAITING--------------------------TIMED_WAITINGTIMED_WAITING--------------------------TIMED_WAITINGTIMED_WAITING--------------------------TERMINATED线程优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
- 线程的优先级用数字表示,范围从1~10。
- Thread.MIN_PRIORITY = 1;
- Thread.MAX_PRIORITY = 10;
- Thread.NORM_PRIORITY = 5;
- 使用以下方式改变或获取优先级。
- getPriority()
- setPriority(int xxx)
- 优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度。
- 优先级高的大部分情况先执行,但并不绝对。
package com.qing.state;/** * 测试线程的优先级 */public class TestPriority { public static void main(String[] args) { //主线程默认优先级 System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); MyPriority myPriority = new MyPriority(); Thread t1 = new Thread(myPriority); Thread t2 = new Thread(myPriority); Thread t3 = new Thread(myPriority); Thread t4 = new Thread(myPriority); Thread t5 = new Thread(myPriority); Thread t6 = new Thread(myPriority); //先设置优先级在启动线程 t1.start(); t2.setPriority(Thread.MIN_PRIORITY); t2.start(); t3.setPriority(Thread.NORM_PRIORITY); t3.start(); t4.setPriority(Thread.MAX_PRIORITY); t4.start(); t5.setPriority(3); t5.start(); t6.setPriority(7); t6.start(); }}class MyPriority implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority()); }}main-->5Thread-3-->10Thread-5-->7Thread-1-->1Thread-0-->5Thread-4-->3Thread-2-->5守护线程(daemon)
- 线程分为用户线程和守护线程。
- 虚拟机必须确保用户线程执行完毕。
- 虚拟机不用等待守护线程执行完毕。
- 守护线程,如:后台记录操作日志,监控内存,垃圾回收等待等。
package com.qing.state;/** * 测试守护线程 */public class TestDaemon { public static void main(String[] args) { Thread god = new Thread(()->{ while (true) { System.out.println("上帝保佑你"); } }); //设置为守护线程,默认是false,表示是用户线程,正常的线程都是用户线程 god.setDaemon(true); god.start(); Thread you = new Thread(()->{ for (int i = 0; i < 100; i++) { System.out.println(i); } System.out.println("==========================="); }); you.start(); }}上帝保佑你上帝保佑你上帝保佑你0123979899上帝保佑你上帝保佑你上帝保佑你===========================上帝保佑你上帝保佑你上帝保佑你原文转载:http://www.shaoqun.com/a/494883.html
isbn:https://www.ikjzd.com/w/174
敦煌网站:https://www.ikjzd.com/w/189
retriever:https://www.ikjzd.com/w/773
目录简介线程创建继承Thread类网图下载实现Runnable接口继承Thread类和实现Runnable接口的区别并发问题龟兔赛跑实现Callable接口静态代理模式静态代理模式的好处Lambda表达式为什么要使用Lambda表达式函数式接口FunctionalInterface线程状态线程停止线程休眠模拟网络延时:放大问题发生可能性模拟倒计时模拟时钟线程礼让线程强制执行线程状态观测线程状态线程
parenthood:parenthood
FEN:FEN
端午节成都新开4条旅游公交线路 串联高铁站和城区商圈景点:端午节成都新开4条旅游公交线路 串联高铁站和城区商圈景点
马尔代夫房型介绍 :马尔代夫房型介绍
海南呀诺达热带雨林景区怎么样?好玩吗?:海南呀诺达热带雨林景区怎么样?好玩吗?
No comments:
Post a Comment