javaios中单例模式式是线程安全的吗

详细分析Java单例的几种写法(一)
单例模式简介单例模式是软件设计模式中最简单的一种设计模式。从名称中可以看出,单例的目的就是使系统中只能包含有一个该类的唯一一个实例。单例模式最初的定义出现于《设计模式》(艾迪生维斯理, 1994):“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”Java中单例模式定义:“一个类有且仅有一个实例,并且自行实例化向整个系统提供。”对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。本文将以Java语言来描述单例类的几种实现方式,以及分析给出的几种实现方式的优缺点和并用程序验证性能数据。
Java实例化的方式
我们知道在Java语言中创建实例的方法大体上有4种:
1、使用new关键字 import java.io.Simport java.util.R/** * @author thinkpad * */public class Singleton implements Serializable{private static final long serialVersionUID = -0184335L;private static Random random = new Random();private Sprivate int [] data = {random.nextInt(),random.nextInt(),random.nextInt()};//default constructor
public Singleton(){}//public constructorpublic Singleton(int id){this.id = id * 2;}//private constructorprivate Singleton(int id,String name){this(id);this.name = name + "." +}@Overridepublic String toString(){StringBuffer buf = new StringBuffer();buf.append(super.toString()).append("/n").append("id :").append(id).append("/n").append("name :").append(this.name).append("/n").append("data array :").append(data.toString());return buf.toString();}}
最常见的使用new 关键字,创建一个对象public class TestSingleton {public static void main(String[] args) {Singleton single = new Singleton(1);}}
2、使用反射
使用反射能够突破JAVA中可见权限,本示例中调用了Singleton私有的构造方法。
import java.lang.reflect.Cimport java.lang.reflect.T/** * @author thinkpad * */public class TestSingletonUseReflect {public static void main(String[] args) {try {Constructor&?&[] constructors = Singleton.class.getDeclaredConstructors();System.out.println(constructors.length);for(Constructor&?& con : constructors ) {Type[] types = con.getParameterTypes();if (types.length == 2){con.setAccessible(true);Singleton singleton =singleton = (Singleton) con.newInstance(1,"single1");System.out.println(singleton);Singleton singleton2 =singleton2 = (Singleton) con.newInstance(2,"single2");System.out.println(singleton2);}}}catch(Exception e){e.printStackTrace();}}}
3、使用反序列化
我们知道Java对象序列化(Object Serialization)将那些实现了Serializable接口的对象转换成一个字节序列,并可以通过反序列化将这个字节序列完全恢复成为原来的对象,因此反序列化提供了一个创建对象的方式。由于使用反序列化创建对象和使用new关键字创建对象有一些不同,反序列化过程中构造方法是没有被调用的(也不一定,若序列化对象父类没有实现Serializable接口,例如Object类,序列化过程中会递归调用父类的无参构造函数),而且其中的域的初始化代码也没有被执行。import java.io.ByteArrayInputSimport java.io.ByteArrayOutputSimport java.io.IOEimport java.io.ObjectInputSimport java.io.ObjectOutputSimport java.io.S/** * @author thinkpad * */public class TestSerializable {/** * @param args */public static void main(String[] args) {Singleton single1 = new Singleton(100);Singleton single2 = new Singleton(200);try { ByteArrayOutputStream buf = new ByteArrayOutputStream();ObjectOutputStream out = new ObjectOutputStream(buf);System.out.println("**********begin serializable**********");System.out.println(single1);System.out.println(single2);out.writeObject(single1);out.writeObject(single2);out.close();System.out.println("**********begin unserializable**********");ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));Singleton s1 = (Singleton)in.readObject();Singleton s2 = (Singleton)in.readObject();System.out.println(s1.toString());System.out.println(s2.toString());in.close();} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} }}
4、依赖注入(Spring DI)
依赖注入从JVM的角度来看的话,应该不算是一种创建对象的方式。关于依赖注入的概念,请参考Spring官方网站。 Java单例的几种实现方式
根据单例模式的定义及描述,要实现单例模式,有几个问题是需要考虑的。单例如何创建以及由谁创建,如何保证实例的唯一性,如何提供全局的访问点。由于Java语言中的实例化有多种,所以Java单例必须能够保证在各种条件下维持instance的唯一性,能够做到线程安全、延迟加载并且能够很好的抵抗反射攻击、反序列化攻击等。本文的以下内容将分析和验证常见的几种单例的设计方法,并用代码来验证效果(网上理论讲的很多,但是大部分都缺少实际的代码验证)。
实现单例,必须是私有的构造器,并导出公有的静态实例,根据静态实例的初始化方式,可以分为饿汉型和懒汉型,根据实例的暴露方式可以分为直接暴露和工厂方法间接暴露。饿汉型在类加载的时候就完成实例化,懒汉型就是在实际使用的时候才进行实例化。因此饿汉型的单例是能够保证线程安全的(JVM来保证ClassLoader只加载一次,不考虑多个ClassLoader的情况),懒汉型的线程安全却需要额外的同步来控制;饿汉型不能实现懒汉式的延迟加载。
1、直接暴露-饿汉型(1)
这种写法非常简单、粗暴,而且也能较好的满足需要,呵呵,有时候简单粗暴也是最能快速解决问题的。这种方式线程安全,但是缺乏灵活性,不容易扩展。
import java.io.Spublic class DirectHungrySingleton implements Serializable{private static final long serialVersionUID = 1265707L;//实例对象public static final DirectHungrySingleton singleton = new DirectHungrySingleton();//私有的构造方法private DirectHungrySingleton(){}}
2、工厂方法-饿汉型
采用工厂方法来暴露实例,对外提供了统一的API接口,扩展性和灵活性更强,而且也具有线程安全性,但是不适用需要延迟加载的场合。
import java.io.Spublic class FactoryMethodHungrySingleton implements Serializable{private static final long serialVersionUID = 1265707L;//私有的实例对象private static final FactoryMethodHungrySingleton singleton = new FactoryMethodHungrySingleton();//私有的构造方法private FactoryMethodHungrySingleton(){}//公有工厂方法public static FactoryMethodHungrySingleton getInstance(){}}
3、工厂方法-懒汉型(一)
由于是采用延迟初始化的方式,无法通过JVM来保证实例的唯一性,因此在工厂方法上增加synchronized关键字保证线程安全。这种写法拥有了线程安全、延迟加载的优点,但带来了代码性能上的损失。无论该对象是否已经实例化,getInstance()方法时,必须要获得Class对象锁,加锁和解锁必然会损耗一定的性能。
import java.io.Spublic class FactoryMethodLazySingleton implements Serializable{private static final long serialVersionUID = 1265707L;//私有的实例对象private static FactoryMethodLazySingleton singleton =//私有的构造方法private FactoryMethodLazySingleton(){}//4.增加线程安全性 【 在类对象上加锁,控制并发访问,防止由于实例化过程中(new对象和初始化过程中还未返回),其他线程调用getSingleton方法,导致存在多个实例】public static synchronized FactoryMethodLazySingleton getInstance(){if (singleton == null){singleton = new FactoryMethodLazySingleton();}}}
4、工厂方法-懒汉型(二) 使用双重检查(适用jdk1.5+)
我们分析代码发现,只需要在实例化过程中控制不能让多个线程进入即可,根据锁定的粒度最小化原则,只需要在new 代码行外加锁即可。这是著名的双重检查写法,一些网站资料上显示只适用于jdk1.5+,我个人的理解是jdk1.5+后,volatile关键字功能的增强,能够保证多线程环境下变量读写的可见性。但是具体资料还未找到,请知道的朋友告知一下。
import java.io.Spublic class FactoryMethodLazyDCSingleton implements Serializable{private static final long serialVersionUID = 1265707L;//私有的实例对象//使用volatile保证多线程情况下的可见性private static volatile FactoryMethodLazyDCSingleton singleton =//私有的构造方法private FactoryMethodLazyDCSingleton(){}//公有工厂方法public static FactoryMethodLazyDCSingleton getInstance(){//外层检查,避免实例化成功后进入同步块,提高性能if (singleton == null){synchronized(FactoryMethodLazyDCSingleton.class){//内层检查,避免多个线程进入后,重复实例化if (singleton == null)singleton = new FactoryMethodLazyDCSingleton();}}}} 5、工厂方法-懒汉型(三)
使用内部静态类
使用双重检查能够很好的满足线程安全和延迟加载,但是代码书写比较复杂、容易出错,在网上出现了一种使用内部静态类的写法,感觉非常优雅。它通过内部类的延迟加载特性实现lazy loading,同时借助JVM的类加载机制保证线程安全,是一种非常优雅的写法。
import java.io.Spublic class FactoryMethodLazyInnerClassSingleton implements Serializable {private static final long serialVersionUID = 1265707L;//内部静态类private static class Holder {static final FactoryMethodLazyInnerClassSingleton singleton = new FactoryMethodLazyInnerClassSingleton();}// 私有的构造方法private FactoryMethodLazyInnerClassSingleton() {}// 公有工厂方法public static FactoryMethodLazyInnerClassSingleton getInstance() {return Holder.}}
6、使用Enum (Jdk 1.5+)
在jdk1.5中提供了Enum关键字来定义枚举类型,我们知道枚举类是一种特殊的类。它有以下这些特点,为了保证类型安全,不对外提供公有的构造方法,所有的枚举对象均在类加载时完成初始化,并且均为static final类型,这些特点完全类似于第1种写法,简单优雅,线程安全,并完全提供抵抗反序列化攻击的机制。 //jdk1.6 Enum类 源码第199行可见
无法对enum进行反序列化,保证了唯一性/**
* prevent default deserialization
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
public enum EnumSingleton {//定义实例INSTANCE(1, "other");//定义其他域private int intFprivate String otherFprivate EnumSingleton(int intField, String otherField) {this.intField = intFthis.otherField = otherFSystem.out.println("init the enum type");}@Overridepublic String toString() {return new StringBuffer().append(super.toString()).append(intField).append(otherField).toString();}}
最新教程周点击榜
微信扫一扫找好工作,快人一步Java线程同步与单例模式的结合学习
Java线程同步与单例模式的结合学习
最近接触了一个项目的业务,是web客户端执行的备份和还原操作问题,当一个客户端点击了备份或还原操作时,在其它的web客户端则不能同时进行备份或还原,直到第一个客户端执行完全(需要5个小时),才能进行备份还原操作,并且这个项目是一个重构项目,不能影响之前的业务。
&&&&&&&& 在这里我们采用的是线程同步+单例的处理办法来解决的,改最少的代码最终解决问题,自己也好好的学习了一下java线程同步和单例,闲来无事,自己对线程这一块进行了整理,把自己的一点收获写下来。
1、& A线程在访问单例service的同步方法a的时候,其他线程能否访问service的同步方法a?同步方法b?非同步方法c?
2、& 两个以上线程同时访问同一个对象的同一个变量的情况下,会出现什么情况?
3、& 当service不是单例(普通实例)的情况下,A线程在访问service实例的同步方法a的时候,其他线程能否访问service实例的同步方法a?同步方法b?非同步方法c?
带着上面的问题,直接贴代码
下面是一个单例类,不用多做介绍:
1 package treadTest.threadA
3 import java.util.ArrayL
4 import java.util.L
* @author yhd
12 public class SingletonService {
private static SingletonS
private List&Integer&
List&Integer& newlist = new ArrayList&Integer&();
private SingletonService(){}
public static SingletonService getInstance(){
if(null == service){
service = new SingletonService();
//同步方法A
public synchronized
void testA(){
System.out.println("A 在执行" + Thread.currentThread());
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
//同步方法B
public synchronized
void testB(){
System.out.println("B 在执行" + Thread.currentThread());
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
//非同步方法C
public void testC(){
System.out.println("C 在执行" + Thread.currentThread());
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
//非同步方法C1
public void testC1(){
System.out.println("C1 在执行" + Thread.currentThread());
public void setList(List&Integer& list){
this.list =
//非同步方法D 传入参数
public void testD(){
System.out.println("D 在执行"+ Thread.currentThread());
for(int num:list){
System.out.println(num +" "+Thread.currentThread() + this.toString());
newlist.add(num);
System.out.println(newlist.toString()+"list的长度"+newlist.size());
下面是一个单例线程的测试类,主要是对上面的单例进行各种线程访问方法的测试
1 package treadT
* @author yhd
* 单例模式线程测试类
9 public class SingletonServiceTest extends Thread{
//单例模式下调用同步方法的时候,该单例会被锁住,其他线程想调用
//该单例的其它同步方法的时候就会进行同步,如果调用的不是单例方法则不受影响
SingletonService service = SingletonService.getInstance();
static int tag = 1;
public void run() {
if(tag == 1){
service.testA();
else if(tag == 2){
service.testB();
service.testC();
//这是使用代码块来实现的,相应的也学习了一下同步代码块和同步方法的使用
//这里的使用的是方法c和c1,这两个都不用同步了,因为这里已经对service同步了
public void run(){
synchronized (service) {
if(tag == 1){
service.testC();
service.testC1();
public static void main(String args[]){
new SingletonServiceTest().start();
//第二个线程能否访问该单例的同一个同步方法A
new SingletonServiceTest().start();
//下面主要是让main线程歇一会儿,否则main执行完了才会启动子线程,导致tag为3,没有意义
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
//第三个线程能否访问该单例的另外一个同步方法B
new SingletonServiceTest().start();
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
//第四个线程能否访问该单例的另外一个非同步方法C
new SingletonServiceTest().start();
运行结果:
A 在执行Thread[Thread-1,5,main]C 在执行Thread[Thread-3,5,main]B 在执行Thread[Thread-2,5,main]A 在执行Thread[Thread-0,5,main]
一开始是出现A和C,过5秒左右出现B,再过5秒出现A
说明:A线程在访问单例service的同步方法a的时候,其他线程不能否访问service的同步方法a和同步方法b,但是能访问非同步方法c
继续第二个问题的测试,直接上代码:
1 package treadT
3 import java.util.ArrayL
4 import java.util.L
7 //单例模式下,多个线程调用单例的用一个非同步方法,会出现什么情况?会混乱么
8 public class ServiceTest2 extends Thread{
static SingletonService service = SingletonService.getInstance();
static List&Integer& list1;
static List&Integer& list2;
public void run() {
service.testD();
public static void main(String args[]){
list1 = new ArrayList&Integer&();
//给list1添加1-1000的数据
for(int i=0;i&1000;i++){
list1.add(i);
list2 = new ArrayList&Integer&();
//给list2添加的数据
for(int i=1001;i&2000;i++){
list2.add(i);
//给service中的list赋list1
service.setList(list1);
//启动第一个线程
new ServiceTest2().start();
//同理 暂停main一会
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
//给service中的list赋list1
service.setList(list2);
//启动第一个线程
new ServiceTest2().start();
运行结果:
998 Thread[Thread-0,5,main]treadTest.SingletonService@61de33999 Thread[Thread-0,5,main]treadTest.SingletonService@61de33
1747 Thread[Thread-1,5,main]treadTest.SingletonService@61de33[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ………………1538] list的长度1539 或者出异常1748 Thread[Thread-1,5,main]treadTest.SingletonService@61de33
1998 Thread[Thread-1,5,main]treadTest.SingletonService@61de331999 Thread[Thread-1,5,main]treadTest.SingletonService@61de33[0, 1, 2, 3, 4, 5……, , , , , ……1999]list的长度1999
newlist并没有是单例中方法传入的多个list而混乱 &跟线程调用普通类的情形没有区别,结果说明:同时访问同一个对象的同一个变量的情况下会混乱,出现了线程安全的问题,如果想要
多个线程共享一个变量,那么可以采用ThreadLocal(线程局部变量),每个线程都有该变量的一个副本,互不影响
3、验证第三个问题
直接上代码:
类似于上面的单例类,只是改成了普通类而已
1 package treadT
2 public class Service {
//同步方法A
public synchronized
void testA(){
System.out.println("A 在执行" + Thread.currentThread());
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
//同步方法B
public synchronized
void testB(){
System.out.println("B 在执行" + Thread.currentThread());
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
//非同步方法C
public void testC(){
System.out.println("C 在执行"+ Thread.currentThread());
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
非单例类的测试类如下:与上面的测试类的区别就是把类的实例化放在了run方法中,从而使得每次都new一个对象
1 package treadT
4 //单例模式下,该单例有两个同步方法,当一个线程调用单例的其中一个同步方法A的时候,
5 //则其他的线程的这个单例能不能调用这个A方法?另外一个同步方法B呢?非同步方法呢?
6 public class ServiceTest extends Thread{
//单例模式下调用同步方法的时候,该单例会被锁住,其他线程想调用
//该单例的其它同步方法的时候就会进行同步,如果调用的不是单例方法则不受影响
static int tag = 1;
public void run() {
service = new Service();
if(tag == 1){
service.testA();
else if(tag == 2){
service.testB();
service.testC();
public static void main(String args[]){
new ServiceTest().start();
new ServiceTest().start();
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
new ServiceTest().start();
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
new ServiceTest().start();
运行结果:
A 在执行Thread[Thread-0,5,main]A 在执行Thread[Thread-1,5,main]B 在执行Thread[Thread-2,5,main]C 在执行Thread[Thread-3,5,main]
没有丝毫的延迟,说明当service是普通类的情况下,A线程在访问service实例的同步方法a的时候,其他线程可以访问service实例的所有同步和非同步方法
总结收获:
同步方法锁定的是调用改同步方法的实例,不允许同一个实例在不同的线程中访问实例中的同步方法。比如SingletonService的service实例,当线程a访问service的同步方法a,导致service被锁定,其它的线程想访问service的同步方法b的时候就会去尝试给service上锁,但是此时线程a已经锁定了service实例,所以其它线程无法访问service的同步方法了。但是其它线程可以访问非同步方法,非同步方法并不尝试上锁。
当不同线程中SingletonService的不同实例来访问同步方法的时候,此时只能保证同一个实例不能在不同的线程中访问同步方法,不能拒绝不同的实例访问各自实例的同步方法,因此不同实例访问互不影响
在项目的备份还原中用的就是多线程同步+单例,当一个客户端操作的时候,单例会被锁定,其它客户端在访问这个单例的同一个或其它同步方法的时候,都会等待第一个客户只需完毕。ok就这样解决了。O(∩_∩)O~
第一次发博,愚见……
附上一道华为的面试题,挺给力的,各位慢慢赏玩吧……
1 package treadT
2 //输出结果是什么呢?
3 public class Thread1{
public static synchronized void main(String args[]){
Thread t=new Thread(){
public void run(){
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.print("ping");
static synchronized void pong(){
System.out.print("pong");
发表评论:
TA的最新馆藏[转]&如何用一个简单的比喻,让完全不懂编程的女生瞬间明白 Java 中的单例模式是什么,以及项目开发中在哪里用?
妹子看过某电视节目后,对java的单例模式颇感兴趣,但我又疏于语言才能,希望高人们帮忙解惑
程序员:“单例模式简单来说就是,每次下雨都买把伞太浪费了,不如买把伞放包里,要用就拿出来,省钱。”妹纸:“这样做听起来真是太棒了,那为什么不把所有的东西都做成这样呢?”程序员:“因为这样做也有它的缺陷,比如说妹纸和男朋友一起出门上班,下雨了只有一把伞就不够用道上的黑话叫做共享资源争用,或者线程冲突。”程序员:“因为伞这种东西是不能两个人共用的(黑话叫线程不安全对象),所以家里只有一把伞就不够了。但是如果是像空调这样大家可以一起用的东西(黑话叫线程安全对象),就可以只买一个,全家人一起用(道上的黑话叫做享元模式)。”妹纸:“那就让男朋友自己再去买把伞么”程序员:“这样当然也可以(黑话叫做私有对象),但是要是男朋友搞丢了,伞也一起搞丢了,而且男人嫌麻烦,不喜欢每天揣把伞,图方便干脆要用的时候自己买伞了,还是很浪费。”妹纸:“这个简单,那就多买几把伞放家里,谁需要就拿去用好了。”程序员:“妹纸你真是太厉害了。这就是牛叉闪闪的对象池模式啊。只有高级程序员才会鼓捣个什么池出来(线程池、连接池),妹纸悟性这么高,要不加入编程这个很有前途的职业吧。。”
你说“我要吃西瓜”,我就去买一个,西瓜有很多很多个,每个人要的时候都可以拿走一个跟别人不同的。这就不是单例…你说“我要都敏俊”,那我没法给你弄来,全世界就一个,每个人要的时候都特么抢的是同一个,这就是单例…
楼上用鼠标、热血高校、商业街、宝马来跟妹子去解释一件她理解了也没什么卵用的技术,难怪你们没有女朋友!非要解释的话,还不如这么说:一个程序就是我,单例模式就是你我只有你这一个对象独特唯一
我想问是什么电视节目。。。
这就像追妹子一样。你不能大街上随便拉一个妹子就说是我对象,也不能自己做一个妹子出来说是自己对象。只能通过某种方法(追妹子),来产生一个对象。同时在调用这个方法的时候,会对你是否已经有对象进行检查。如果有,那么方法会直接返回,或者产生一个异常(抽你一巴掌,或者new Exception(“流氓!”)),从而保证你只有一个对象。这就是单例模式。
哥今天良心发现。听着,哥教你哈,你得这么说:“妹子,这么说吧,其实哥就是个活生生的单例器!而你,就是哥心里唯一的那个单例!哥这辈子心里再也装不下别人了,你知道吗?哥这辈子只爱你一个!妹啊,你能为哥也做个单例器吗?其实哥是想说,嫁给哥好吗?”此处要有舌吻!
楼上都太拿衣服了,会问这个问题的女友真的不懂什么是单例,还是真的想知道单例么?正确答案就是:“你就是我的单例”
你女朋友就是单例啊!全局里就是这一个(这里默认你没踏俩船啊)。至于用法,比如你的钱都在你领导那,无论哪里花钱,都需要向你领导审批,用不用都由你领导决定。这像不像登录的验证啊。登录验证是单例模式的一个典型场景。单例模式的应用场景大约是系统里有且只有一个,比如登录模块,配置模块等等。最后,觉得你这是炫耀!很安格瑞!!!
1、法律允许并且只允许你拥有一个老公。(single)2、这个老公可以等你想结婚的时候再找,而不是从一开始就必须要有。(Lazy Initialization)3、这个老公是不变的,从婚姻开始到结束。(Static)4、当有人提起XXX的老公时,这个头衔是唯一的,只会指向你老公,而不会指向别的男人或女人;因此任何其他认识你的人都可以通过你而找到你老公。(public accessor)5、老公是私有的,不可以与其他男人或女人分享。(private & no subclasses allowed)6、老公只能由你自己来找。(private constructor)
为什么要让 完全不懂编程的妹子去懂Java。。明明应该是去聊点别的=
已有帐号?
无法登录?
社交帐号登录}

我要回帖

更多关于 java单例模式线程安全 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信