導航:首頁 > 源碼編譯 > 非公平鎖源碼解析

非公平鎖源碼解析

發布時間:2022-04-05 06:06:26

java並發包源碼怎麼讀

1. 各種同步控制工具的使用

1.1 ReentrantLock

ReentrantLock感覺上是synchronized的增強版,synchronized的特點是使用簡單,一切交給JVM去處理,但是功能上是比較薄弱的。在JDK1.5之前,ReentrantLock的性能要好於synchronized,由於對JVM進行了優化,現在的JDK版本中,兩者性能是不相上下的。如果是簡單的實現,不要刻意去使用ReentrantLock。

相比於synchronized,ReentrantLock在功能上更加豐富,它具有可重入、可中斷、可限時、公平鎖等特點。

首先我們通過一個例子來說明ReentrantLock最初步的用法:

package test;

import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static int i = 0;

@Override public void run() { for (int j = 0; j < 10000000; j++)
{ lock.lock(); try
{
i++;
} finally
{ lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}

}

有兩個線程都對i進行++操作,為了保證線程安全,使用了ReentrantLock,從用法上可以看出,與synchronized相比,ReentrantLock就稍微復雜一點。因為必須在finally中進行解鎖操作,如果不在finally解鎖,有可能代碼出現異常鎖沒被釋放,而synchronized是由JVM來釋放鎖。

那麼ReentrantLock到底有哪些優秀的特點呢?

1.1.1 可重入

單線程可以重復進入,但要重復退出

lock.lock();
lock.lock();try{
i++;

}
finally{
lock.unlock();
lock.unlock();
}

由於ReentrantLock是重入鎖,所以可以反復得到相同的一把鎖,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那麼獲取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放(重入鎖)。這模仿了synchronized的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者後續)synchronized塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個synchronized塊時,才釋放鎖。

public class Child extends Father implements Runnable{ final static Child child = new Child();//為了保證鎖唯一
public static void main(String[] args) { for (int i = 0; i < 50; i++) { new Thread(child).start();
}
}
public synchronized void doSomething() {
System.out.println("1child.doSomething()");
doAnotherThing(); // 調用自己類中其他的synchronized方法
}
private synchronized void doAnotherThing() { super.doSomething(); // 調用父類的synchronized方法
System.out.println("3child.doAnotherThing()");
}
@Override
public void run() {
child.doSomething();
}
}class Father { public synchronized void doSomething() {
System.out.println("2father.doSomething()");
}
}

我們可以看到一個線程進入不同的synchronized方法,是不會釋放之前得到的鎖的。所以輸出還是順序輸出。所以synchronized也是重入鎖

輸出:

1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
...

1.1.2.可中斷

與synchronized不同的是,ReentrantLock對中斷是有響應的。中斷相關知識查看[高並發Java 二] 多線程基礎

普通的lock.lock()是不能響應中斷的,lock.lockInterruptibly()能夠響應中斷。

我們模擬出一個死鎖現場,然後用中斷來處理死鎖

package test;import java.lang.management.ManagementFactory;import java.lang.management.ThreadInfo;import java.lang.management.ThreadMXBean;import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); int lock; public Test(int lock)
{ this.lock = lock;
} @Override
public void run()
{ try
{ if (lock == 1)
{
lock1.lockInterruptibly(); try
{
Thread.sleep(500);
} catch (Exception e)
{ // TODO: handle exception
}
lock2.lockInterruptibly();
} else
{
lock2.lockInterruptibly(); try
{
Thread.sleep(500);
} catch (Exception e)
{ // TODO: handle exception
}
lock1.lockInterruptibly();
}
} catch (Exception e)
{ // TODO: handle exception
} finally
{ if (lock1.isHeldByCurrentThread())
{
lock1.unlock();
} if (lock2.isHeldByCurrentThread())
{
lock2.unlock();
}
System.out.println(Thread.currentThread().getId() + ":線程退出");
}
} public static void main(String[] args) throws InterruptedException {
Test t1 = new Test(1);
Test t2 = new Test(2);
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
Thread.sleep(1000); //DeadlockChecker.check();
} static class DeadlockChecker
{ private final static ThreadMXBean mbean = ManagementFactory
.getThreadMXBean(); final static Runnable deadlockChecker = new Runnable()
{ @Override
public void run()
{ // TODO Auto-generated method stub
while (true)
{ long[] deadlockedThreadIds = mbean.findDeadlockedThreads(); if (deadlockedThreadIds != null)
{
ThreadInfo[] threadInfos = mbean.getThreadInfo(deadlockedThreadIds); for (Thread t : Thread.getAllStackTraces().keySet())
{ for (int i = 0; i < threadInfos.length; i++)
{ if(t.getId() == threadInfos[i].getThreadId())
{
t.interrupt();
}
}
}
} try
{
Thread.sleep(5000);
} catch (Exception e)
{ // TODO: handle exception
}
}

}
};
public static void check()
{
Thread t = new Thread(deadlockChecker);
t.setDaemon(true);
t.start();
}
}

}

上述代碼有可能會發生死鎖,線程1得到lock1,線程2得到lock2,然後彼此又想獲得對方的鎖。

我們用jstack查看運行上述代碼後的情況

下面舉個例子:

package test;import java.util.concurrent.CyclicBarrier;public class Test implements Runnable{ private String soldier; private final CyclicBarrier cyclic; public Test(String soldier, CyclicBarrier cyclic)
{ this.soldier = soldier; this.cyclic = cyclic;
} @Override
public void run()
{ try
{ //等待所有士兵到齊
cyclic.await();
dowork(); //等待所有士兵完成工作
cyclic.await();
} catch (Exception e)
{ // TODO Auto-generated catch block
e.printStackTrace();
}

} private void dowork()
{ // TODO Auto-generated method stub
try
{
Thread.sleep(3000);
} catch (Exception e)
{ // TODO: handle exception
}
System.out.println(soldier + ": done");
} public static class BarrierRun implements Runnable
{ boolean flag; int n; public BarrierRun(boolean flag, int n)
{ super(); this.flag = flag; this.n = n;
} @Override
public void run()
{ if (flag)
{
System.out.println(n + "個任務完成");
} else
{
System.out.println(n + "個集合完成");
flag = true;
}

}

} public static void main(String[] args)
{ final int n = 10;
Thread[] threads = new Thread[n]; boolean flag = false;
CyclicBarrier barrier = new CyclicBarrier(n, new BarrierRun(flag, n));
System.out.println("集合"); for (int i = 0; i < n; i++)
{
System.out.println(i + "報道");
threads[i] = new Thread(new Test("士兵" + i, barrier));
threads[i].start();
}
}

}

列印結果:

集合

士兵5: done士兵7: done士兵8: done士兵3: done士兵4: done士兵1: done士兵6: done士兵2: done士兵0: done士兵9: done10個任務完成

1.7 LockSupport

提供線程阻塞原語

和suspend類似

LockSupport.park();
LockSupport.unpark(t1);

與suspend相比不容易引起線程凍結

LockSupport的思想呢,和Semaphore有點相似,內部有一個許可,park的時候拿掉這個許可,unpark的時候申請這個許可。所以如果unpark在park之前,是不會發生線程凍結的。

下面的代碼是[高並發Java 二] 多線程基礎中suspend示例代碼,在使用suspend時會發生死鎖。

而使用LockSupport則不會發生死鎖。

另外

park()能夠響應中斷,但不拋出異常。中斷響應的結果是,park()函數的返回,可以從Thread.interrupted()得到中斷標志。

在JDK當中有大量地方使用到了park,當然LockSupport的實現也是使用unsafe.park()來實現的。

public static void park() { unsafe.park(false, 0L);
}

1.8 ReentrantLock 的實現

下面來介紹下ReentrantLock的實現,ReentrantLock的實現主要由3部分組成:

❷ Java多線程是什麼意思

Java多線程實現方式主要有三種:繼承Thread類、實現Runnable介面、使用ExecutorService、Callable、Future實現有返回結果的多線程。其中前兩種方式線程執行完後都沒有返回值,只有最後一種是帶返回值的。

1、繼承Thread類實現多線程
繼承Thread類的方法盡管被我列為一種多線程實現方式,但Thread本質上也是實現了Runnable介面的一個實例,它代表一個線程的實例,並且,啟動線程的唯一方法就是通過Thread類的start()實例方法。start()方法是一個native方法,它將啟動一個新線程,並執行run()方法。這種方式實現多線程很簡單,通過自己的類直接extend Thread,並復寫run()方法,就可以啟動新線程並執行自己定義的run()方法。例如:

代碼說明:
上述代碼中Executors類,提供了一系列工廠方法用於創先線程池,返回的線程池都實現了ExecutorService介面。
public static ExecutorService newFixedThreadPool(int nThreads)
創建固定數目線程的線程池。
public static ExecutorService newCachedThreadPool()
創建一個可緩存的線程池,調用execute 將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則創建一個新線程並添加到池中。終止並從緩存中移除那些已有 60 秒鍾未被使用的線程。
public static ExecutorService newSingleThreadExecutor()
創建一個單線程化的Executor。
public static ScheledExecutorService newScheledThreadPool(int corePoolSize)
創建一個支持定時及周期性的任務執行的線程池,多數情況下可用來替代Timer類。

總結:ExecutoreService提供了submit()方法,傳遞一個Callable,或Runnable,返回Future。如果Executor後台線程池還沒有完成Callable的計算,這調用返回Future對象的get()方法,會阻塞直到計算完成。

❸ 什麼是Java中的公平鎖

首先Java中的ReentrantLock 默認的lock()方法採用的是非公平鎖。
也就是不用考慮其他在排隊的線程的感受,lock()的時候直接詢問是否可以獲取鎖,而不用在隊尾排隊。

下面分析下公平鎖的具體實現。
重點關注java.util.concurrent.locks.AbstractQueuedSynchronizer類
幾乎所有locks包下的工具類鎖都包含了該類的static子類,足以可見這個類在java並發鎖工具類當中的地位。
這個類提供了對操作系統層面線程操作方法的封裝調用,可以幫助並發設計者設計出很多優秀的API

ReentrantLock當中的lock()方法,是通過static 內部類sync來進行鎖操作

public void lock()
{
sync.lock();
}

//定義成final型的成員變數,在構造方法中進行初始化
private final Sync sync;
//無參數默認非公平鎖
public ReentrantLock()
{
sync = new NonfairSync();
}
//根據參數初始化為公平鎖或者非公平鎖
public ReentrantLock(boolean fair)
{
sync = fair ? new FairSync() : new NonfairSync();
}

❹ java中的非公平鎖不怕有的線程一直得不到執行嗎

首先來看公平鎖和非公平鎖,我們默認使用的鎖是非公平鎖,只有當我們顯示設置為公平鎖的情況下,才會使用公平鎖,下面我們簡單看一下公平鎖的源碼,如果等待隊列中沒有節點在等待,則佔有鎖,如果已經存在等待節點,則返回失敗,由後面的程序去將此線程加入等待隊列

上面的方法,實現了一個新的讀線程獲取鎖的中斷,它會讀取等待隊列中下一個等待鎖的線程,如果它是獲取寫鎖的線程,那麼此方法返回為真,調用它的程序會把這個試圖獲取讀鎖的線程加入到等待隊列,從而終止了讀線程一直都在佔有鎖的情況。

❺ 非公平鎖為什麼會降低上下文切換

非公平鎖會降低一定的上下文切換,降低性能開銷.因此,ReentrantLock默認選擇的是非公平鎖,則是為了減少一部分上下文切換,

❻ 自旋鎖和互斥鎖的區別 java中lock Syntronized區別

自旋鎖(Spin lock)
自旋鎖與互斥鎖有點類似,只是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環在那裡看是

否該自旋鎖的保持者已經釋放了鎖,"自旋"一詞就是因此而得名。其作用是為了解決某項資源的互斥使用。因為自旋鎖不會引起調用者睡眠,所以自旋鎖的效率遠
高於互斥鎖。雖然它的效率比互斥鎖高,但是它也有些不足之處:
1、自旋鎖一直佔用CPU,他在未獲得鎖的情況下,一直運行--自旋,所以佔用著CPU,如果不能在很短的時 間內獲得鎖,這無疑會使CPU效率降低。
2、在用自旋鎖時有可能造成死鎖,當遞歸調用時有可能造成死鎖,調用有些其他函數也可能造成死鎖,如 _to_user()、_from_user()、kmalloc()等。

因此我們要慎重使用自旋鎖,自旋鎖只有在內核可搶占式或SMP的情況下才真正需要,在單CPU且不可搶占式的內核下,自旋鎖的操作為空操作。自旋鎖適用於鎖使用者保持鎖時間比較短的情況下。

兩種鎖的加鎖原理

互斥鎖:線程會從sleep(加鎖)——>running(解鎖),過程中有上下文的切換,cpu的搶占,信號的發送等開銷。

自旋鎖:線程一直是running(加鎖——>解鎖),死循環檢測鎖的標志位,機制不復雜。

互斥鎖屬於sleep-waiting類型的鎖。例如在一個雙核的機器上有兩個線程(線程A和線程B),它們分別運行在Core0和
Core1上。假設線程A想要通過pthread_mutex_lock操作去得到一個臨界區的鎖,而此時這個鎖正被線程B所持有,那麼線程A就會被阻塞
(blocking),Core0 會在此時進行上下文切換(Context
Switch)將線程A置於等待隊列中,此時Core0就可以運行其他的任務(例如另一個線程C)而不必進行忙等待。而自旋鎖則不然,它屬於busy-waiting類型的鎖,如果線程A是使用pthread_spin_lock操作去請求鎖,那麼線程A就會一直在
Core0上進行忙等待並不停的進行鎖請求,直到得到這個鎖為止。

兩種鎖的區別

互斥鎖的起始原始開銷要高於自旋鎖,但是基本是一勞永逸,臨界區持鎖時間的大小並不會對互斥鎖的開銷造成影響,而自旋鎖是死循環檢測,加鎖全程消耗cpu,起始開銷雖然低於互斥鎖,但是隨著持鎖時間,加鎖的開銷是線性增長。

兩種鎖的應用

互斥鎖用於臨界區持鎖時間比較長的操作,比如下面這些情況都可以考慮

1 臨界區有IO操作

2 臨界區代碼復雜或者循環量大

3 臨界區競爭非常激烈

4 單核處理器

至於自旋鎖就主要用在臨界區持鎖時間非常短且CPU資源不緊張的情況下,自旋鎖一般用於多核的伺服器。

lock與Syntronized的區別

轉自自:

java並發之Lock與synchronized的區別

1)Lock是一個介面,而synchronized是Java中的關鍵字,synchronized是內置的語言實現;

2)synchronized在發生異常時,會自動釋放線程佔有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;

3)Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷;

4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。

5)Lock可以提高多個線程進行讀操作的效率。

在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。所以說,在具體使用時要根據適當情況選擇。

兩者在鎖的相關概念上區別:

1.可重入鎖

如果鎖具備可重入性,則稱作為可重入鎖。像synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實際上表明了鎖的分配機制:基於線程的分配,而不是基於方法調用的分配。舉個簡單的例子,當一個線程執行到某個synchronized方法時,比如說method1,而在method1中會調用另外一個synchronized方法method2,此時線程不必重新去申請鎖,而是可以直接執行方法method2。

看下面這段代碼就明白了:

1

2

3

4

5

6

7

8

9

class MyClass
{

public synchronized void method1()
{

method2();

}

public synchronized void method2()
{

}

}

上述代碼中的兩個方法method1和method2都用synchronized修飾了,假如某一時刻,線程A執行到了method1,此時線程A獲取了這個對象的鎖,而由於method2也是synchronized方法,假如synchronized不具備可重入性,此時線程A需要重新申請鎖。但是這就會造成一個問題,因為線程A已經持有了該對象的鎖,而又在申請獲取該對象的鎖,這樣就會線程A一直等待永遠不會獲取到的鎖。

而由於synchronized和Lock都具備可重入性,所以不會發生上述現象。

2.可中斷鎖

可中斷鎖:顧名思義,就是可以相應中斷的鎖。

在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。

如果某一線程A正在執行鎖中的代碼,另一線程B正在等待獲取該鎖,可能由於等待時間過長,線程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它,這種就是可中斷鎖。

在前面演示lockInterruptibly()的用法時已經體現了Lock的可中斷性。

3.公平鎖

公平鎖即盡量以請求鎖的順序來獲取鎖。比如同是有多個線程在等待一個鎖,當這個鎖被釋放時,等待時間最久的線程(最先請求的線程)會獲得該所,這種就是公平鎖。

非公平鎖即無法保證鎖的獲取是按照請求鎖的順序進行的。這樣就可能導致某個或者一些線程永遠獲取不到鎖。

在Java中,synchronized就是非公平鎖,它無法保證等待的線程獲取鎖的順序。

而對於ReentrantLock和ReentrantReadWriteLock,它默認情況下是非公平鎖,但是可以設置為公平鎖。

看一下這2個類的源代碼就清楚了:

在ReentrantLock中定義了2個靜態內部類,一個是NotFairSync,一個是FairSync,分別用來實現非公平鎖和公平鎖。

我們可以在創建ReentrantLock對象時,通過以下方式來設置鎖的公平性:

1

ReentrantLock
lock = new ReentrantLock(true);

如果參數為true表示為公平鎖,為fasle為非公平鎖。默認情況下,如果使用無參構造器,則是非公平鎖。

另外在ReentrantLock類中定義了很多方法,比如:

isFair() //判斷鎖是否是公平鎖

isLocked() //判斷鎖是否被任何線程獲取了

isHeldByCurrentThread() //判斷鎖是否被當前線程獲取了

hasQueuedThreads() //判斷是否有線程在等待該鎖

在ReentrantReadWriteLock中也有類似的方法,同樣也可以設置為公平鎖和非公平鎖。不過要記住,ReentrantReadWriteLock並未實現Lock介面,它實現的是ReadWriteLock介面。

4.讀寫鎖

讀寫鎖將對一個資源(比如文件)的訪問分成了2個鎖,一個讀鎖和一個寫鎖。

正因為有了讀寫鎖,才使得多個線程之間的讀操作不會發生沖突。

ReadWriteLock就是讀寫鎖,它是一個介面,ReentrantReadWriteLock實現了這個介面。

可以通過readLock()獲取讀鎖,通過writeLock()獲取寫鎖。

性能比較

在JDK1.5中,synchronized是性能低效的。因為這是一個重量級操作,它對性能最大的影響是阻塞的是實現,掛起線程和恢復線程的操作都需要轉入內核態中完成,這些操作給系統的並發性帶來了很大的壓力。相比之下使用Java提供的Lock對象,性能更高一些。Brian

Goetz對這兩種鎖在JDK1.5、單核處理器及雙Xeon處理器環境下做了一組吞吐量對比的實驗,發現多線程環境下,synchronized的吞吐量下降的非常嚴重,而ReentrankLock則能基本保持在同一個比較穩定的水平上。但與其說ReetrantLock性能好,倒不如說synchronized還有非常大的優化餘地,於是到了JDK1.6,發生了變化,對synchronize加入了很多優化措施,有自適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在JDK1.6上synchronize的性能並不比Lock差。官方也表示,他們也更支持synchronize,在未來的版本中還有優化餘地,所以還是提倡在synchronized能實現需求的情況下,優先考慮使用synchronized來進行同步。

❼ java synchronized 公平鎖嗎

在Java中,synchronized就是非公平鎖,它無法保證等待的線程獲取鎖的順序。
關於非公平鎖
非公平鎖即無法保證鎖的獲取是按照請求鎖的順序進行的。這樣就可能導致某個或者一些線程永遠獲取不到鎖。

❽ java里是怎麼通過condition介面是獲取監視器方法的

ReentrantLock和condition是配合著使用的,就像wait和notify一樣,提供一種多線程間通信機制。

ReentrantLock 的lock方法有兩種實現:公平鎖與非公平鎖
看newCondition的源碼實現:

final ConditionObject newCondition() {
return new ConditionObject();}

其實就是只實例化一個個conditionObject對象綁定到lock罷了。也就是拿到了監視器,再深入到conditionObject這個裡面實現看看await方法:

public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException(); Node node = addConditionWaiter(); int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) {
LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break; }
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);}

大概就是將當前線程加入等待隊列,其中做一些邏輯判斷,再來看看喚醒的方法:singal和singalAll:

public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}

其實就是將等待隊列裡面的線程依次喚醒罷了,doSingalAll:

private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}

transferForSignal將線程轉移到syncQueue重新排隊,這里主要用到CAS(lock free)演算法改變狀態:

final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;

/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}

篇幅有限,沒有詳細描述...反正多看看源碼吧,結合著實例分析

❾ java單線程有哪些場景

main方法就是單線程的

閱讀全文

與非公平鎖源碼解析相關的資料

熱點內容
知乎軟體源碼 瀏覽:293
解壓音頻最新消息 瀏覽:113
如何弄崩一個mc伺服器 瀏覽:134
執行命令怎麼取消 瀏覽:902
美拍app長什麼樣 瀏覽:294
android滾動選擇圖片 瀏覽:484
有什麼畫畫app能把照片放上去畫 瀏覽:395
如何自己架設域名伺服器 瀏覽:311
ktv網站php源碼 瀏覽:957
啟信寶app干什麼的 瀏覽:996
解壓助眠快速采耳 瀏覽:875
手機視頻文字編程軟體 瀏覽:251
畫出圓的命令 瀏覽:842
三星快捷命令怎麼用 瀏覽:451
手機怎麼取消手機加密的密碼 瀏覽:175
別克車機為什麼開放裝app 瀏覽:583
qt做個編譯器 瀏覽:120
我的世界伺服器如何增加 瀏覽:172
電腦c盤無法刪除文件夾 瀏覽:607
源碼編程克隆與顏色判斷 瀏覽:932