国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【Java并發(fā)編程】變量的線程安全分析

這篇具有很好參考價(jià)值的文章主要介紹了【Java并發(fā)編程】變量的線程安全分析。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

1.成員變量和靜態(tài)變量是否線程安全?

  • 如果他們沒有共享,則線程安全
  • 如果被共享:
    • 只有讀操作,則線程安全
    • 有寫操作,則這段代碼是臨界區(qū),需要考慮線程安全

2.局部變量是否線程安全

  • 局部變量是線程安全的
  • 當(dāng)局部變量引用的對(duì)象則未必
    • 如果給i對(duì)象沒有逃離方法的作用訪問,則是線程安全的
    • 如果該對(duì)象逃離方法的作用范圍,需要考慮線程安全

3.局部變量的線程安全分析

public static void test1() {
 int i = 10;
 i++;
}

每個(gè)線程調(diào)用該方法時(shí)局部變量i,會(huì)在每個(gè)線程的棧幀內(nèi)存中被創(chuàng)建多分,因此不存在共享

【Java并發(fā)編程】變量的線程安全分析

當(dāng)局部變量的引用有所不同

先來看一個(gè)成員變量的里例子:

public class ThreadUnsafeDemo {
    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
        ThreadUnsafe test = new ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
            new Thread(() -> {
                test.method1(LOOP_NUMBER);
            }, "Thread" + i).start();
        }
    }
}

class ThreadUnsafe {
    ArrayList<String> list = new ArrayList<>();

    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            // 臨界區(qū),會(huì)產(chǎn)生競(jìng)態(tài)條件
            method2();
            method3();
        }
    }

    private void method2() {
        list.add("1");
    }

    private void method3() {
        list.remove(0);
    }

}

可能會(huì)發(fā)生一種情況:線程1和線程2都去執(zhí)行method2,但是由于并發(fā)執(zhí)行導(dǎo)致最后只有一個(gè)元素添加成功,當(dāng)執(zhí)行了兩次移除操作,所以就會(huì)報(bào)錯(cuò)。

Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:659)
	at java.util.ArrayList.remove(ArrayList.java:498)
	at org.example.juc.ThreadUnsafe.method3(ThreadUnsafeDemo.java:39)
	at org.example.juc.ThreadUnsafe.method1(ThreadUnsafeDemo.java:30)
	at org.example.juc.ThreadUnsafeDemo.lambda$main$0(ThreadUnsafeDemo.java:17)
	at java.lang.Thread.run(Thread.java:750)

進(jìn)程已結(jié)束,退出代碼0

分析

  • 無論哪個(gè)線程中的 method2 引用的都是同一個(gè)對(duì)象中的list成員變量
  • method2 和 method3 分析相同

【Java并發(fā)編程】變量的線程安全分析

但如果將list修改為局部變量,就不會(huì)有上訴的問題了。

class Threadsafe {

    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            ArrayList<String> list = new ArrayList<>();
            // 臨界區(qū),會(huì)產(chǎn)生競(jìng)態(tài)條件
            method2(list);
            method3(list);
        }
    }

    private void method2(ArrayList<String> list) {
        list.add("1");
    }

    private void method3(ArrayList<String> list) {
        list.remove(0);
    }

}

分析

  • list 是局部變量,每個(gè)線程調(diào)用時(shí)會(huì)創(chuàng)建其不同實(shí)例,沒有共享
  • 而 method2 的參數(shù)是從 method1 中傳遞過來的,與 method1 中引用通過一個(gè)對(duì)象
  • menthod3 的參數(shù)分析與 method2 相同

【Java并發(fā)編程】變量的線程安全分析

方法訪問修飾符帶來的思考,如果把 method2 和 method3 的方法修改為 public 會(huì)不會(huì)代理線程安全問題?

  • 情況1:有其他線程調(diào)用 mthod2 和 method3
  • 情況2:在情況1的基礎(chǔ)上,為 ThreadSafe 類添加子類,子類覆蓋為 method2 或 method3 方法

我們先來看情況1,這兩個(gè)方法的訪問修飾符修改為public,其他線程就可以調(diào)用了,但是它們不能調(diào)用 method1,所以 method1里的局部變量list是安全的,其他線程要調(diào)用 method2 的話只能使用自己創(chuàng)建新的list變量。

我們?cè)賮砜辞闆r2,訪問修飾符修改為 public ,也就意味著子類可以去覆蓋重寫 method2 和 method3 方法,即

class ThreadUnsafe {

    public void method1(int loopNumber) {
        for (int i = 0; i < loopNumber; i++) {
            ArrayList<String> list = new ArrayList<>();
            // 臨界區(qū),會(huì)產(chǎn)生競(jìng)態(tài)條件
            method2(list);
            method3(list);
        }
    }

    public void method2(ArrayList<String> list) {
        list.add("1");
    }

    public void method3(ArrayList<String> list) {
        list.remove(0);
    }

}

class ThreadSafeSubClass extends ThreadUnsafe {
    @Override
    public void method3(ArrayList<String> list) {
        new Thread(() -> {
            list.remove(0);
        }).start();
    }
}

我們重寫方法中,開啟了一個(gè)新的線程,這個(gè)線程就能夠去操作method1方法中的局部變量 list,此時(shí) list就變成共享變量了,會(huì)有多個(gè)線程去修改它,也就產(chǎn)生了線程不安全的問題。也就是我們前面提到的局部變量的引用逃離了方法的作用范圍(有其他線程去使用)就可能會(huì)產(chǎn)生安全問題。

4.常見線程安全類

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java.util.concurrent 包下的類

這里說的線程安全是指,多個(gè)線程調(diào)用他們同一個(gè)實(shí)例的方法時(shí),時(shí)線程安全的,也可以理解為:

Hashtable table = new Hashtable();
new Thread(()->{
 table.put("key", "value1");
}).start();
new Thread(()->{
 table.put("key", "value2");
}).start();

他們的每個(gè)方法是原子的,但它們多個(gè)方法的組合不是原子的,比如:

Hashtable table = new Hashtable();
// 線程1
if( table.get("key") == null) {
 table.put("key", "t1");
}
// 線程2
if( table.get("key") == null) {
 table.put("key", "t2");
}

這里也就是檢查和上鎖不同步導(dǎo)致的線程不安全。

不可變線程安全性

String、Integer 等都是不可變類,因?yàn)槠鋬?nèi)部的狀態(tài)不可以改變,因?yàn)樗麄兊姆椒ǘ际蔷€程安全的。

有同學(xué)或許有疑問,String 有 replace,substring 等方法【可以】改變值啊,那么這些方法又是如何保證線程安 全的呢?

5.深入刨析String類為什么不可變?

什么是不可變?

String s = "aaa";
s = "bbb";

我們現(xiàn)在有一個(gè)字符串 s = "aaa",如果我把它第二次賦值 s = "bbb",這個(gè)操作并不會(huì)在原內(nèi)存地址上修改數(shù)據(jù),也就是不會(huì)吧 “aaa” 的那塊地址里的數(shù)據(jù)修改為"bbb",而是重新指向了一個(gè)新的 內(nèi)存地址,即”bbb"的內(nèi)存地址,所以說 String 類是不可變的,一旦創(chuàng)建不可被修改的。

String 類里的replace方法

我們可以看到就是創(chuàng)建了一個(gè)新的String對(duì)象。

 public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }

不可變的本質(zhì)

我們看String類的源碼就可以發(fā)現(xiàn),

【Java并發(fā)編程】變量的線程安全分析

  1. String 類是一個(gè) final 類

String類由final修飾,我們都知道當(dāng)final修飾一個(gè)類時(shí),該類不可以被其他類繼承,自然String類就沒有子類,也更沒有方法被子類重寫的說法了,所以這就保證了外界無法通過繼承String類,來實(shí)現(xiàn)對(duì)String不可變性的破壞。

  1. String底層是通過一個(gè)char[]來存儲(chǔ)數(shù)據(jù)的,且該char[]由private final修飾。

該value數(shù)組被final修飾,我們知道被final修飾的引用類型的變量就不能再指向其他對(duì)象了,也就是說value數(shù)組只能指向堆中屬于自己的那一個(gè)數(shù)組,不可以再指向其他數(shù)組了。但是我們可以改變它指向的這個(gè)數(shù)組里面的內(nèi)容啊,比如咱們隨便舉個(gè)例子:

public class StringDemo {
    public static void main(String[] args) {
        final char[] c = {'a', 'b', 'c'};
        c[0] = 'd';
        System.out.println(Arrays.toString(c));
    }
}

其實(shí)不然,我們雖然可以修改一個(gè)對(duì)象的內(nèi)容,但是我們根本無法修改String類里的數(shù)據(jù),因?yàn)?String 類里的 value 數(shù)組是私有的,也沒有對(duì)外修改的public方法,所以根本就沒有可以修改的機(jī)會(huì)。

保證String類不可變靠的就是以下三點(diǎn):

  • String 類被 final 修飾導(dǎo)致其不能被繼承,進(jìn)而避免了子類破壞 String 不可變性。

  • 保存字符串的value數(shù)組被 final 修飾且為私有的。

  • String 類里沒有提供或暴露修改這個(gè)value數(shù)組的方法。

6.實(shí)例分析

我們來看幾個(gè)例子,檢驗(yàn)一下我們學(xué)的怎么樣吧

線程安不安全,看這幾個(gè)方便:

  1. 是否是共享變量
  2. 是否存在多個(gè)線程并發(fā)
  3. 是否有寫操作

**前置知識(shí):**tomcat中一個(gè)servet類只會(huì)有一個(gè)實(shí)例,所以多個(gè)請(qǐng)求用的都是同一個(gè)servet對(duì)象

例1

public class MyServlet extends HttpServlet {
 // 是否安全?
 Map<String,Object> map = new HashMap<>();
 // 是否安全?
 String S1 = "...";
 // 是否安全?
 final String S2 = "...";
 // 是否安全?
 Date D1 = new Date();
 // 是否安全?
 final Date D2 = new Date();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
     // 使用上述變量
 }
}

他們都是成員變量

  • map:HashMap是線程不安全的類,所以不安全
  • S1 :可以修改其對(duì)象的引用地址,線程不安全
  • S2 :被final修飾,所以不能修改它的引用地址,也不可能修改它的值
  • D1 :Date()是線程不安全的類
  • D2:雖然被final修飾,但可以修改它里面的值

例2

public class MyServlet extends HttpServlet {
 // 是否安全?
 private UserService userService = new UserServiceImpl();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
 userService.update(...);
 }
}
public class UserServiceImpl implements UserService {
 // 記錄調(diào)用次數(shù)
 private int count = 0;
 
 public void update() {
 // ...
 count++;
 }
}
  • userService:成員變量,不安全,有多個(gè)線程會(huì)修改它的count變量

例三

@Aspect
@Component
public class MyAspect {
 // 是否安全?
 private long start = 0L;
 
 @Before("execution(* *(..))")
 public void before() {
 start = System.nanoTime();
 }
 
 @After("execution(* *(..))")
 public void after() {
 long end = System.nanoTime();
 System.out.println("cost time:" + (end-start));
 }
}

MyAspect沒有指定是單例對(duì)象還是多例對(duì)象,Spring默認(rèn)是單例。所以多個(gè)線程都共享一個(gè)MyAspect

  • start:成員變量,線程不安全

例四

public class MyServlet extends HttpServlet {
 // 是否安全
 private UserService userService = new UserServiceImpl();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
 userService.update(...);
 }
}
public class UserServiceImpl implements UserService {
 // 是否安全
 private UserDao userDao = new UserDaoImpl();
 
 public void update() {
 userDao.update();
 }
}
public class UserDaoImpl implements UserDao { 
 public void update() {
 String sql = "update user set password = ? where username = ?";
 // 是否安全
 try (Connection conn = DriverManager.getConnection("","","")){
 // ...
 } catch (Exception e) {
 // ...
 }
 }
}

UserDaoImpl中的update方法中的 conn 是局部變量,并且沒有逃離方法的作用范圍,所以 conn是線程安全的,UserServiceImpl 中的 UserDao是成員變量,但是userDao它調(diào)用的方法是線程安全的,所以userDao也是線程安全的,同理,userService也是線程安全的。

例5

public class MyServlet extends HttpServlet {
 // 是否安全
 private UserService userService = new UserServiceImpl();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
 userService.update(...);
 }
}
public class UserServiceImpl implements UserService {
 // 是否安全
 private UserDao userDao = new UserDaoImpl();
 
 public void update() {
 userDao.update();
 }
}
public class UserDaoImpl implements UserDao { 
 public void update() {
 // 是否安全
 private Connection conn = null;
 public void update() throws SQLException {
 String sql = "update user set password = ? where username = ?";
 conn = DriverManager.getConnection("","","");
 // ...
 conn.close();
 }
}

conn是成員變量,多個(gè)線程用的是同一個(gè)conn,所以是線程不安全的,同時(shí) userDao 也是線程不安全的,userService也是線程不安全的。

例6

public class MyServlet extends HttpServlet {
 // 是否安全
 private UserService userService = new UserServiceImpl();
 
 public void doGet(HttpServletRequest request, HttpServletResponse response) {
 userService.update(...);
 }
}
public class UserServiceImpl implements UserService { 
 public void update() {
 UserDao userDao = new UserDaoImpl();
 userDao.update();
 }
}
public class UserDaoImpl implements UserDao {
 // 是否安全
 private Connection = null;
 public void update() throws SQLException {
 String sql = "update user set password = ? where username = ?";
 conn = DriverManager.getConnection("","","");
 // ...
 conn.close();
 }
}

UserServiceImpl中不在用的是成員變量而是局部變量,所以 conn 雖然是局部變量但是不被多個(gè)線程之間共享,所以conn是線程安全的,所以u(píng)serDao也是線程安全的,userService也是線程安全的。

例7

public abstract class Test {

    public void bar() {
        // 是否安全
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        foo(sdf);
    }

    public abstract foo(SimpleDateFormat sdf);


    public static void main(String[] args) {
        new Test().bar();
    }
}

foo 方法是抽象方法,所以它的行為是不確定的,可能導(dǎo)致不安全的方法,被稱之為外星方法

public void foo(SimpleDateFormat sdf) {
    String dateStr = "1999-10-11 00:00:00";
    for (int i = 0; i < 20; i++) {
        new Thread(() -> {
            try {
                sdf.parse(dateStr);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

例8

private static Integer i = 0;
public static void main(String[] args) throws InterruptedException {
    List<Thread> list = new ArrayList<>();
    for (int j = 0; j < 2; j++) {
        Thread thread = new Thread(() -> {
            for (int k = 0; k < 5000; k++) {
                synchronized (i) {
                    i++;
                }
            }
        }, "" + j);
        list.add(thread);
    }
    list.stream().forEach(t -> t.start());
    list.stream().forEach(t -> {
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    log.debug("{}", i);
}

這里雖然i是靜態(tài)變量,但是又synchronized給修改i的代碼塊上了鎖,所以是線程安全的。文章來源地址http://www.zghlxwxcb.cn/news/detail-476246.html

到了這里,關(guān)于【Java并發(fā)編程】變量的線程安全分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Java并發(fā)編程之線程池詳解

    Java并發(fā)編程之線程池詳解

    目錄 ??今日良言:不悲傷 不彷徨 有風(fēng)聽風(fēng) 有雨看雨 ??一、簡(jiǎn)介 ??二、相關(guān)代碼 ??1.線程池代碼 ??2.自定義實(shí)現(xiàn)線程池 ??三、ThreadPoolExecutor類 首先來介紹一下什么是線程池,線程池是一種利用池化技術(shù)思想來實(shí)現(xiàn)的線程管理技術(shù),主要是為了復(fù)用線程、便利地管理線程

    2024年02月12日
    瀏覽(24)
  • Java并發(fā)編程面試題——線程池

    Java并發(fā)編程面試題——線程池

    參考文章: 《Java 并發(fā)編程的藝術(shù)》 7000 字 + 24 張圖帶你徹底弄懂線程池 (1) 線程池 (ThreadPool) 是一種用于 管理和復(fù)用線程的機(jī)制 ,它是在程序啟動(dòng)時(shí)就預(yù)先創(chuàng)建一定數(shù)量的線程,將這些線程放入一個(gè)池中,并對(duì)它們進(jìn)行有效的管理和復(fù)用,從而在需要執(zhí)行任務(wù)時(shí),可以從

    2024年02月07日
    瀏覽(44)
  • 關(guān)于并發(fā)編程與線程安全的思考與實(shí)踐 | 京東云技術(shù)團(tuán)隊(duì)

    關(guān)于并發(fā)編程與線程安全的思考與實(shí)踐 | 京東云技術(shù)團(tuán)隊(duì)

    作者:京東健康 張娜 并發(fā)編程的意義是充分的利用處理器的每一個(gè)核,以達(dá)到最高的處理性能,可以讓程序運(yùn)行的更快。而處理器也為了提高計(jì)算速率,作出了一系列優(yōu)化,比如: 1、硬件升級(jí):為平衡CPU 內(nèi)高速存儲(chǔ)器和內(nèi)存之間數(shù)量級(jí)的速率差,提升整體性能,引入了多

    2024年02月07日
    瀏覽(25)
  • JUC并發(fā)編程-集合不安全情況以及Callable線程創(chuàng)建方式

    JUC并發(fā)編程-集合不安全情況以及Callable線程創(chuàng)建方式

    1)List 不安全 ArrayList 在并發(fā)情況下是不安全的 解決方案 : 1.Vector 2.Collections.synchonizedList() 3. CopyOnWriteArrayList 核心思想 是,如果有 多個(gè)調(diào)用者(Callers)同時(shí)要求相同的資源 (如內(nèi)存或者是磁盤上的數(shù)據(jù)存儲(chǔ)),他們 會(huì)共同獲取相同的指針指向相同的資源 , 直到某個(gè)調(diào)用者

    2024年01月23日
    瀏覽(39)
  • 【Java 并發(fā)編程】一文讀懂線程、協(xié)程、守護(hù)線程

    【Java 并發(fā)編程】一文讀懂線程、協(xié)程、守護(hù)線程

    在 Java 線程的生命周期一文中提到了 就緒狀態(tài)的線程在獲得 CPU 時(shí)間片后變?yōu)檫\(yùn)行中狀態(tài) ,否則就會(huì)在 可運(yùn)行狀態(tài) 或者 阻塞狀態(tài) ,那么系統(tǒng)是如何分配線程時(shí)間片以及實(shí)現(xiàn)線程的調(diào)度的呢?下面我們就來講講線程的調(diào)度策略。 線程調(diào)度是指系統(tǒng)為線程分配 CPU 執(zhí)行時(shí)間片

    2023年04月08日
    瀏覽(24)
  • Java面試_并發(fā)編程_線程基礎(chǔ)

    Java面試_并發(fā)編程_線程基礎(chǔ)

    進(jìn)程是正在運(yùn)行程序的實(shí)例, 進(jìn)程中包含了線程, 每個(gè)線程執(zhí)行不同的任務(wù) 不同的進(jìn)程使用不同的內(nèi)存空間, 在當(dāng)前進(jìn)程下的所有線程可以共享內(nèi)存空間 線程更輕量, 線程上下文切換成本一般上要比進(jìn)程上下文切換低(上下文切換指的是從一個(gè)線程切換到另一個(gè)線程) 并發(fā)是單個(gè)

    2024年02月07日
    瀏覽(30)
  • 【Java并發(fā)編程】線程中斷機(jī)制(輔以常見案例)

    本文由淺入深介紹了中斷機(jī)制、中斷的常見案例和使用場(chǎng)景。 因?yàn)橐恍┰蛐枰∠菊趫?zhí)行的線程。我們舉幾個(gè)栗子: 假設(shè)踢足球點(diǎn)球時(shí),A隊(duì)前4輪中了4個(gè)球,B隊(duì)前4輪只中了2個(gè)球,此時(shí)勝負(fù)已分,第5輪這個(gè)點(diǎn)球就不用踢了,此時(shí)需要停止A隊(duì)的線程和B隊(duì)的線程(共

    2024年02月13日
    瀏覽(30)
  • java并發(fā)編程:多線程基礎(chǔ)知識(shí)介紹

    最初的計(jì)算機(jī)只能接受一些特定的指令,用戶每輸入一個(gè)指令,計(jì)算機(jī)就做出一個(gè)操作。當(dāng)用戶在思考或者輸入時(shí),計(jì)算機(jī)就在等待。這樣效率非常低下,在很多時(shí)候,計(jì)算機(jī)都處在等待狀態(tài)。 后來有了 批處理操作系統(tǒng) ,把一系列需要操作的指令寫下來,形成一個(gè)清單,一次

    2024年02月07日
    瀏覽(21)
  • Java并發(fā)編程第6講——線程池(萬字詳解)

    Java并發(fā)編程第6講——線程池(萬字詳解)

    Java中的線程池是運(yùn)用場(chǎng)景最多的并發(fā)框架,幾乎所有需要異步或并發(fā)執(zhí)行任務(wù)的程序都可以使用線程池,本篇文章就詳細(xì)介紹一下。 定義:線程池是一種用于管理和重用線程的技術(shù)(池化技術(shù)),它主要用于提高多線程應(yīng)用程序的性能和效率。 ps:線程池、連接池、內(nèi)存池

    2024年02月11日
    瀏覽(27)
  • Java并發(fā)編程學(xué)習(xí)18-線程池的使用(下)

    上篇介紹了 ThreadPoolExecutor 配置和擴(kuò)展相關(guān)的信息,本篇開始將介紹遞歸算法的并行化。 還記得我們?cè)凇禞ava并發(fā)編程學(xué)習(xí)11-任務(wù)執(zhí)行演示》中,對(duì)頁面繪制程序進(jìn)行一系列改進(jìn),這些改進(jìn)大大地提供了頁面繪制的并行性。 我們簡(jiǎn)單回顧下相關(guān)的改進(jìn)過程: 第一次新增時(shí),頁

    2024年02月12日
    瀏覽(24)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包