Algorithm: GCD、EXGCD、Inverse Element

數論基礎

數論是純數學的一個研究分支,主要研究整數的性質。初等數論包括整除理論、同余理論、連分數理論。這一篇主要記錄的是同余相關的基礎知識。

取模

取模是一種運算,本質就是帶余除法,運算結果就是餘數。取模運算結果的符號由被模數(被除數)決定。
\[ 7\%4=3;\space7\%(-4)=3;\\ (-7)\%4=-3;\space(-7)\%(-4)=-3 \]

取模運算的性質

\[ 設a>b>0,有:\\ (a+b)\%c=(a\%c+b\%c)\%c\\ (a-b)\%c=(a\%c-b\%c+c)\%c \\ (a\times b)\%c=(a\%c\times b\%c)\%c \]

例題1

\[ 給定兩個非負整數a,b和正整數n,計算f(a^b)\%n。\\ 其中,f(0)=f(1)=1,且對於\forall i>0,f(i+2)=f(i+1)+f(i)\\ (0\le a,b\le 2^{64},1\le n\le 1000) \]

找規律,對於mod n來說,最多n²項就會出現重複,找出重複項即可得到循環周期,然後找對應的項即可。

例題2:hdu 1212

題意是給一個位數不超過1,000的正整數A和一個大小不超過100,000的正整數B,求A%B。

這裏只需要用到上面的兩條性質,把A截斷,從高到低模擬做除法即可。

#include <iostream>
string s; int mod, ans;
int main() {
    while(std::cin >> str >> mod) {
        ans = 0;
        for(int i = 0; s[i]; i++) ans = (ans * 10 + (s[i] - '0')) % mod;
        std::cout << ans << std::endl;
    }
    return 0;
}

GCD

GCD即Greatest Common Divisor,最大公約數。最大公約數即能同時整除給定兩個整數的最大正整數。特殊地:gcd(a,0)=a。求解最大公約數通常使用輾轉相除法。下面是輾轉相除法的代碼實現:

typedef long long LL;
LL gcd(LL a, LL b) {
    return b ? gcd(b, a % b) : a;
}

輾轉相除法得到的餘數序列增長速度比斐波那契數列更快,已知斐波那契的增長是指數級別,則輾轉相除法的複雜度是對數級別。

輾轉相除法的證明:

1.設兩個數a、b(a>b),他們的最大公約數為gcd(a,b),r = a % b,k = (a – a % b)/ b,那麼證明輾轉相除法,即是要證明:gcd(a,b)=gcd(b,r)。首先我們令c = gcd(a,b),那麼肯定存在互質的整數m,n,使得a = mc,b = nc。那麼有r = a – kb = mc – knc = (m – kn)c,根據這條式子,c也是r的因數。回過頭看,如果能證明m-kn和n互質,那麼就可以說明c=gcd((m-kn)c,nc)=gcd(b,r),所以把問題再次轉化為:求證m-kn和n互質。

2.反證法證明m-kn和n互質:假設m-kn和n不互質,用數學語言描述為:假設存在整數x,y,d,其中d>1,使得m-kn=xd,n=yd。那麼有m=kn+xd=kyd+xd=(ky+x)d,從而a=mc=(ky+x)cd,b=nc=ycd,則gcd(a,b)=cd≠c,與前設矛盾。故m-kn和n互質得證,也就證明了gcd(a,b)=gcd(b,r)。

唯一分解定理

對於任意大於2的正整數X,它總能寫成如下形式:
\[ X=p_1^{a_1}\times p_2^{a_2} \times ……\times p_k^{a_k},其中p是各不相同的質數 \]
即質因數分解。且這個分解唯一。

LCM

LCM即Least Common Multiple,最小公倍數。

\[ \begin{align} &根據唯一分解定理,對於給定的a,b:\\ &a=p_1^{a_1}\times p_2^{a_2} \times……\times p_k^{a_k}\\ &b=p_1^{b_1}\times p_2^{b_2} \times……\times p_k^{b_k}\\ &那麼:\\ &gcd(a,b)=p_1^{min(a_1,b_1)}\times p_2^{min(a_2,b_2)}\times ……p_1^{min(a_k,b_k)}\\ &lcm(a,b)=p_1^{max(a_1,b_1)}\times p_2^{max(a_2,b_2)}\times ……p_1^{max(a_k,b_k)}\\ &因此,gcd(a,b)\times lcm(a,b)=a\times b \end{align} \]

LL lcm(LL a, LL b) {
    return a / gcd(a, b) * b;
}

例題:hdu1788

題意是求最小的正整數N,滿足除以每一個給定的數Mi,其結果均為Mi-a。作如下轉化:
\[ N\%M_i\equiv(M_i-a)\%M_i\Rightarrow (N+a)\%M_i=0(a<M_i<100) \]
即N是Mi的倍數,那麼題意也就是求所有M的最小公倍數,注意数字範圍即可。

同余

對於兩個不同的數a,b,如果有a % p = b % p(p>1),那我們就稱a和b模p同余,記作:
\[ a\equiv b(mod\space p) \]

同余的性質

\[ \begin{aligned} &1.自反性:a\equiv a(mod\space m)\\ &2.對稱性:a\equiv b(mod\space m),則b\equiv a(mod\space m)\\ &3.傳遞性:若a\equiv b(mod\space m),b\equiv c(mod\space m),則a\equiv c(mod\space m)\\ &4.線性運算:若a\equiv b(mod\space m),c\equiv d(mod\space m)\\ &\space\space\space則:a\pm c\equiv b\pm d(mod\space m);\space a\times c\equiv b\times d(mod\space m)\\ &5.冪運算:若a\equiv b(mod\space m),那麼a^n\equiv b^n(mod\space m)\\ &6.若ac\equiv bc(mod\space m),且c≠0,那麼a\equiv b(mod\space {m\over gcd(c,m)})\\ &\space\space\space特殊地,若gcd(c,m)=1,則a\equiv b(mod\space m) \end{aligned} \]

EXGCD

貝祖定理(Bezouts Identity):若設a,b是不全為0的整數,則存在整數x,y,使得ax+by=gcd(a,b),(a,b)代表最大公因數,則設a,b是不全為零的整數,則肯定存在整數x,y,使得ax+by=(a,b)。這個式子稱為貝祖等式。

EXtend GCD 即擴展歐幾里得算法,它可以用於解關於x,y的貝祖等式。

EXGCD的可行性

當a=0時,ax+by=gcd(a,b)=gcd(0,b)=b,這時可以解出y=1而x為任意。同理可得b=0時,解得x=1而y為任意。當a,b都不為0時:
\[ \begin{aligned} &設存在一組解為x_1,y_1,即ax_1+by_1=gcd(a,b)\\ &由歐幾里得定理:gcd(a,b)=gcd(b,a\%b)得:\\ &ax_1+by_1=gcd(a,b)=gcd(b,a\%b)\\ &那麼,bx_2+(a\%b)y_2=gcd(b,a\%b)=ax_1+by_1\\ &又a\%b=a-a/b\times b\\ &\begin {align} 那麼,&ax_1+by_1\\ =&bx_2+(a-a/b\times b)y_2\\ =&bx_2+ay_2-a/b*b*y_2\\ =&ay_2+b(x_2-a/b*y_2) \end{align}\\ &故可以得到:x_1=y_2,\space y_1=x_2-a/b*y_2 \end{aligned} \]
a或b為0即迭代求解的出口,迭代過程用代碼錶示如下:

typedef long long LL;
// ver 1
// 返回值為gcd(a,b),x和y即求出的特解
LL exgcd(LL a, LL b, LL &x, LL &y) {
    if (b == 0) {
        x = 1; y = 0;
        return a;
    }
    LL ans = exgcd(b, a % b, x, y);
    //這裏通過迭代求出了一組解x,y,這組解對應上面推導過程中的x2和y2。
    LL temp = x;
    x = y;
    y = temp - a / b * y;
    return ans;
}

// ver 2
// 在熟悉了推導過程之後,我們可以利用引用的性質得到更為簡潔的ver2寫法
LL exgcd(LL a, LL b, LL &x, LL &y) {
    if (b == 0) {
        x = 1; y = 0;
        return a;
    }
    LL ans = exgcd(b, a % b, y, x);
    y -= a / b * x;
    return ans;
}

EXGCD的應用

求關於x,y的方程ax+by=c的一組解

這裏只需要對貝祖等式稍作變換即可。假設EXGCD求出的方程ax+by=gcd(a,b)的一組解為x1和y1。在方程兩邊同時乘上一個數m,使得c=m*gcd(a,b),這裏也就要求c是gcd(a,b)的倍數,即c%gcd(a,b)=0。這也是方程有解的條件。此時方程為:
\[ \begin{align} &ax_1\times m+by_1\times m=gcd(a,b)\times m=c \end{align} \]
對於ax+by=gcd(a,b),其參數解為:
\[ \begin{align} &x=x_1+{b\over gcd(a,b)}\times t,y=y_1-{a\over gcd(a,b)}\times t,t為參數\\ &這裏選用{b\over gcd(a,b)}和{a\over gcd(a,b)}是為了保證結果均為整數 \end{align} \]
那麼對比方程ax+by=c,可以得到其特解為:
\[ \begin{align} &x_0=x_1\times m=x_1\times {c\over gcd(a,b)}\\ &y_0=y_1\times m=y_1\times {c\over gcd(a,b)} \end{align} \]
那麼其通解為:
\[ \begin{align} &X=x_0+{b\over gcd(a,b)}\times t,Y=y_0-{a\over gcd(a,b)}\times t,t為參數\\ &這裏選用{b\over gcd(a,b)}和{a\over gcd(a,b)}作為係數是為了確保得到均為整數解。 \end{align} \]
對於任意一個確定的t,都有一組確定的解與之對應,只需要根據需要找出對應的解即可。

例如求滿足ax+by=c的X的最小非負整數解,那麼在得到X的通解之後只需調整t,調整過程用偽代碼錶示為:

S = b / gcd(a, b);
X = (x0 % S + S) % S;
// 由於x0可能是負數,故在模之後還要加上S在取一次模。
// 同理可得Y的最小非負整數解
T = a / gcd(a, b);
Y = (y0 % T + T) % T;

完整的exgcd求解方程ax+by=c的X和Y的最小非負整數解得代碼如下:

LL exgcd(LL a, LL b, LL c, LL& x, LL& y) {
    if (b == 0) {
        x = 1; y = 0;
        return a;
    }
    LL ans = exgcd(a, b, c, y, x);
    y -= a / b * x;
    LL S = b / ans, T = a / ans;
    x = (x % S + S) % S;
    y = (y % T + T) % T;
    return ans;
}

求關於x的方程ax≡b(mod m)的最小非負整數解

ax≡b(mod m)即ax%m=b%m,即求ax+my=b%m(取模其實就相當於減去(加上)了若干個模數)。但只有a和m互質時有唯一解,否則無解。轉化為ax+my=b(假設b<m)后,套用exgcd即可。

求a關於模數p的乘法逆元

設x是a關於p的乘法逆元,那麼有ax≡1(mod p),這是第二個應用的特殊情況,顯然也可以通過類似方法求得。

逆元

Inverse Element,逆元,推廣了加法中的加法逆元和乘法中的倒數。直觀地說,它是一個可以取消另一給定元素運算的元素。a關於模p的逆元存在的條件是gcd(a,p)=1。
\[ \begin{align} &在模p意義下,設A的逆元為A^{-1},那麼有A\times A^{-1}\equiv 1(mod\space p) \end{align} \]
為什麼需要逆元呢?
\[ \begin{align} &在模意義下,{A\over B}\%p = {A\%p\over B\%p}\%p並不成立,\\ &那麼設B在模p意義下的逆元表示為B^{-1},根據逆元的定義,有{A\over B}=A\times B^{-1}(mod \space p)。 \end{align} \]
這裏,我們把除法轉化為乘法,就可以運用取模運算的性質:(a * b) % c = (a % c * b % c) % c,優化算法。

逆元的求法

EXGCD求法

給定模數m,求a的逆元相當於求解關於x的方程ax≡1(mod m)。這個方程可以轉化為ax-my=1 。用EXGCD可以求得一組x,y和gcd。檢查gcd是否為1(因為EXGCD是把問題轉化為求解方程ax-my=gcd(a,m),顯然只有gcd(a,m)=1時才能轉化),gcd不為1則說明逆元不存在。在能夠成功求解的情況下,把解調整x到0~m-1的範圍中即可。

費馬小定理

在模p為質數的情況下,有費馬小定理:
\[ a^{p-1}\equiv 1(mod\space p) \]
那麼:
\[ a\times a^{p-2}\equiv 1(mod\space p)\\ 故a的逆元a^{-1}=a^{p-2} \]
然後用快速冪求出逆元即可。

拓展:快速冪

// a是底數,b是指數
LL pow(LL a, LL b, LL mod) {
    LL ans = 1;
    while(b) {
        if(b&1) ans = ans * a % mod;
        b >>= 1, a = a * a % mod;
    }
    return ans;
}

拓展:慢速乘,防止快速冪過程中乘法運算爆long long,只需把快速冪中的乘法替換為mul()即可,一般情況用不上。

// a為因數1,b為因數2
LL mul(LL a, LL b, LL mod) {
    LL ans = 0;
    while(b) {
        if(b&1) ans = (ans + a) % mod;
        b >>= 1, a = (a + a) % mod;
    }
    return ans;
}

歐拉定理

費馬小定理其實是歐拉定理的特殊情況。在模數p不為質數時,有:
\[ a^{\phi(p)}\equiv 1(其中\phi()是歐拉函數) \]
同理可以得到:
\[ a^{-1}\equiv a^{\phi(p)-1} \]

線性逆元表

有時我們需要快速得出1~p-1的所有逆元,這個時候我們需要一種O(n)的方法,這就是線性逆元表。

首先,1關於任意模p的逆元的逆元都是1。設p = k * i + r(r < i ,1 < i < p),那麼有:
\[ k\times i + r \equiv0(mod\space p) \]
利用逆元性質,兩邊同時乘上i的逆元以及r的逆元,得到:
\[ k\times r^{-1}+i^{-1}\equiv 0(mod\space p) \]
移項得:
\[ i^{-1}\equiv-k \times r^{-1}\equiv -⌊{p\over i}⌋\times (p\%i)^{-1}(mod\space p) \]
顯然p%i是小於i的,那麼我們就可以利用之前的結果在O(1)時間算出單獨的逆元。

用代碼錶示即:

// inv[i]對應i的逆元
LL inv[maxn];
void CalInv() {
    // 0沒有逆元,故不初始化
    inv[1] = 1;
    for (int i = 2; i < maxn; i++)
        inv[i] = (mod - mod / i) * inv[mod % i] % mod;
}

利用
\[ (a\times c)\% mod = (a\%mod\times c\%mod)\%mod;\\ 由a\times a^{-1}\equiv1(mod \space p)得,\\ inv_{fac(i)}\equiv inv_{fac(i)}\times (i+1)^{-1}\times (i+1)\equiv inv_{fac(i+1)}\times (i+1)(mod\space p) \]
我們還可以在線性時間求出1~min(n,p)的階乘的逆元,代碼如下:

LL fac[maxn], inv[maxn];
void CalFacInv() {
    // 0的階乘是1
    fac[0] = fac[1] = 1;
    for (int i = 2; i < maxn; i++)
        fac[i] = fac[i - 1] * i % mod;
    inv[maxn - 1] = QPow(fac[maxn - 1], mod - 2);
    // 注意邊界是≥
    for (int i = maxn - 2; i >= 0; i--)
        inv[i] = inv[i + 1] * (i + 1) % mod;
}

例題:hdu1576

題意即把除法轉化為逆元乘法。3種方法的代碼:

#include<iostream>
#include<algorithm>
using namespace std;

typedef long long LL;
LL n, B;
const LL mod = 9973;

LL exgcd(LL a, LL b, LL &x, LL &y) {
    if (b == 0) {
        x = 1, y = 0;
        return a;
    }
    LL ans = exgcd(b, a % b, y, x);
    y -= a/b*x;
    return ans;
}

LL QPow(LL x, LL n)
{
    LL res = 1;
    while(n)
    {
        if(n & 1) res = res * x % mod;
        x = x * x % mod;
        n >>= 1;
    }
    return res;
}

LL inv[10000];
void CalInv() {
    inv[1] = 1;
    for (LL i = 2; i < 10000; i++) {
        inv[i] = (mod * mod - mod / i * inv[mod % i]) % mod;
    }
}


int main() {
    CalInv();
    int t;
    cin >> t;
    while (t--) {
        cin >> n >> B;
        // exgcd
        // LL x, y;
        // exgcd(B, mod, x, y);
        // cout << (x + mod) * n % mod << endl;
        
        // QPow
        // cout << QPow(B, mod - 2) * n % mod << endl;
        
        // 逆元打表
        cout << inv[B % mod] * n % mod << endl;
    }
    return 0;
}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

併發編程-深入淺出AQS

AQS是併發編程中非常重要的概念,它是juc包下的許多併發工具類,如CountdownLatch,CyclicBarrier,Semaphore 和鎖, 如ReentrantLock, ReaderWriterLock的實現基礎,提供了一個基於int狀態碼和隊列來實現的併發框架。本文將對AQS框架的幾個重要組成進行簡要介紹,讀完本文你將get到以下幾個點:

  1. AQS進行併發控制的機制是什麼

  2. AQS獨佔和共享模式是如何實現的

  3. 同步隊列和條件等待隊列的區別,和數據出入隊原則

一,AQS基本概念

AQS(AbstractQueuedSynchronizer)是用來構建鎖或者其他同步組件的基礎框架,它使用了一個int成員變量來表示狀態,通過內置的FIFO(first in,first out)隊列來完成資源獲取線程的排隊工作。

隊列可分為兩種,一種是同步隊列,是程序執行入口出處的等待隊列;而另一種則是條件等待隊列,隊列中的元素是在程序執行時在某個條件上發生等待。

1.1 獨佔or共享模式

AQS支持兩種獲取同步狀態的模式既獨佔式和共享式。顧名思義,獨佔式模式同一時刻只允許一個線程獲取同步狀態,而共享模式則允許多個線程同時獲取。

1.2 同步隊列

當一個線程嘗試獲取同步狀態失敗時,同步器會將這個線程以及等待狀態等信息構造成一個節點加入到等待隊列中,同時會阻塞當前線程,當同步狀態釋放時,會把首節點中的線程喚醒,使其再次嘗試重複獲取同步隊列。

1.3 條件隊列

AQS內部類ConditionObject來實現的條件隊列,當一個線程獲取到同步狀態,但是卻通過Condition調用了await相關的方法時,會將該線程封裝成Node節點並加入到條件隊列中,它的結構和同步隊列相同。

二,獨佔or共享模式

AQS框架中,通過維護一個int類型的狀態,來進行併發控制,線程通常通過修改此狀態信息來表明當前線程持有此同步狀態。AQS則是通過保存修改狀態線程的引用來實現獨佔和共享模式的。

/**
 * 獲取同步狀態
 */
public final void acquire(int arg) {
    //嘗試獲取同步狀態, 如果嘗試獲取到同步狀態失敗,則加入到同步隊列中
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
/**
 * 嘗試獲取同步狀態【子類中實現】,因為aqs基於模板模式,僅提供基於狀態和同步隊列的實 
 * 現思路,具體的實現由子類決定
 */
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 如果當前狀態值為0,並且等待隊列中沒有元素,執行修改狀態值操作
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // 修改狀態值成功,記錄當前持有同步狀態的線程信息
            setExclusiveOwnerThread(current);
            return true;
        }
        // 如果當前線程已經持有同步狀態,繼續修改同步狀態【重入鎖實現原理】
    } else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

/**
 * 根據傳入的模式以及當前線程信息創建一個隊列的節點並加入到同步隊列尾部
 */
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
/**
 * 同步隊列中節點,嘗試獲取同步狀態
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 自旋(死循環)
        for (;;) {
            // 只有當前節點的前驅節點是頭節點時才會嘗試執行獲取同步狀態操作
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

獨佔式是如何控製得?

當修改狀態信息成功后,如果執行的是獨佔式操作,AQS的具體實現類中會保存當前線程的信息來聲明同步狀態已被當前線程佔用,此時其他線程再嘗試獲取同步狀態會返回false。

三,同步隊列

3.1 隊列中保存那些信息?

同步隊列節點中主要保存着線程的信息以及模式(共享or獨佔)。

3.2 何時執行入隊操作?

/**
 * 獲取同步狀態
 */
public final void acquire(int arg) {
    //嘗試獲取同步狀態, 如果嘗試獲取到同步狀態失敗,則加入到同步隊列中
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

復用上文中的代碼,不難看出再獲取同步狀態失敗后,會執行入隊操作。

3.3 何時執行出隊操作?

當線程獲取同步狀態失敗時,會被封裝成Node節點加入到等待隊列中,此時所有節點都回進入自旋過程,首先判斷自己prev是否時頭節點,如果是則嘗試獲取同步狀態。
被阻塞線程的喚醒主要以靠前驅節點的出隊或阻塞線程被中斷來實現。

/**
 * 同步隊列中節點,嘗試獲取同步狀態
 * 
 * 1. 當一個線程獲取到同步狀態時,會將當前線程構造程Node並設置為頭節點
 * 2. 並將原始的head節點設置為null,以便於垃圾回收
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

四,條件等待隊列

條件變量(ConidtionObject)是AQS中的一個內部類,用來實現同步隊列機制。同步隊列復用了等待隊列中Node節點,所以同步隊列到等待隊列中不需要進行額外的轉換。

4.1 什麼時候執行入隊操作?

當線程獲取到同步狀態,但是在臨界區中調用了await()方法,此時該線程會被加入到對應的條件隊列匯總。
ps: 臨界區,加鎖和釋放鎖之間的代碼區域

/**
 * ConditionObject中的await方法,調用后使得當前執行線程加入條件等待隊列
 */
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    // -----省略代碼------
}
/**
 * 添加等待線程
 */
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // -----省略代碼------
    // 將當前線程構造程條件隊列節點,並加入到隊列中
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

4.2 什麼時候執行出隊操作?

當對應的Conditioni調用signial/signalAll()方法時回選擇從條件隊列中出隊列,同步隊列是通過自旋的方式獲取同步狀態,而條件隊列中的節點則通過通知的方式出隊。條件隊列中的節點被喚醒後會加入到入口等待隊列中。

/**
 * 喚醒當前條件等到隊列中的所有等待線程
 */
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}
/**
 * 遍歷隊列,將元素從條件隊列 加入到 同步隊列
 */
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}
final boolean transferForSignal(Node node) {
    // -----省略代碼------
    // 執行入隊操作,將node添加到同步隊列中
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

五,總結

  1. 使用Node實現的FIFO隊列,可以用於構建鎖或者其他同步裝置的基礎框架
  2. 利用一個int類型的屬性表示狀態
  3. 使用模板方法模式,子類可以通過繼承它來管理狀態實現各種併發工具
  4. 可以同時實現獨佔和共享模式

本文對AQS的基本原理進行的簡要的描述,對於子類的公平性和非公平行實現,中斷,隊列中節點的等待狀態,cas等操作沒有進行探討,感興趣的小夥伴可以進行源碼閱讀或者查閱相關資料。

六,Q&A

Question1: 在java中通常使用synchronized來實現方法同步,AQS中通過CAS保證了修改同步狀態的一致性問題,那麼對比synchronized,cas有什麼優勢不同與優勢呢?你還知道其他無鎖併發的策略嗎?

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

生魚片注意!海獸胃線蟲暴增283倍

摘錄自2020年3月23日Yahoo!新聞報導

華盛頓大學研究團隊於近日指出,寄生在魚類的線蟲「海獸胃線蟲」 (Anisakis),自1978年至2015年的這段時間暴增283倍。

對此,威斯康辛大學水海洋學教授伍德(Chelsea Wood)表示,儘管對人類健康風險低,但對於海豚、鯨魚、海報等海洋哺乳動物,可能產生有害影響,內容提到若人類吃到活生生的海獸胃線蟲,蟲子會入侵腸壁,進而引發類似食物中毒的症狀,例如噁心嘔吐腹瀉等等,但蟲子幾乎會在幾天後就死亡,症狀隨即消失,因此大多數人都認為只是單純的食物中毒而很難被查覺到。

雖然海獸胃線蟲不能在人類體內存活太久,不過卻可以在海洋哺乳動物中生活並繁殖,伍德表示,他們仍不確定線蟲大量爆增的原因,氣候變遷、肥料、洋流等等所產生的養分,以及同其海洋哺乳動物的數量增加,都可能是潛在原因。

食品安全
糧食
生活環境
永續發展
國際新聞
生魚片
線蟲

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

科學家警告 武漢肺炎只是開端 更多新興傳染病將因環境破壞而起

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

日本男童「免農藥除草大作戰」 農夫大讚:非常有效

摘錄自2020年3月24日中時報導

一名日本網友在推特表示,她在家中庭院內種植秋葵,但只要一到夏季,雜草就會生長得特別快,而她就讀小學的兒子看到這種情況,便主動說要以「除草」為主題,當作暑假作業來做研究,而且要「不使用農藥」。

兒子在三年間嘗試過多種方法,都無法完全清除雜草。某一天他突然靈機一動,發現土壤的軟硬度是導致雜草生長的關鍵,於是他決定每天在土壤上跑步。從今年的1月5日起到3月1日,不論晴雨,他每天都在土壤上奔跑,每次跑30分鐘,沒想到短短2個月後,雜草就不再生長了。

也有農民認同此方法「身為一名農民,這是非常有效的除草方法,可在雜草種子發芽時挪動土壤,在雜草生根前徹底將其消滅。」

環境經濟
農林漁牧業
國際新聞
日本
除草

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

10種改變 看懂武漢肺炎正對能源、氣候變遷造成什麼影響

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

上汽集團150億定增用於投資新能源汽車、車聯網等

上汽集團4月20日晚間公告稱,中國證監會發行審核委員會於4月20日對公司非公開發行A股股票的申請進行了審核。根據會議審核結果,公司本次非公開發行A股股票的申請獲得審核通過。

根據調整後的定增預案,上汽集團擬以不低於15.56元/股非公開發行不超過9.64億股,募集資金不超過150億元用於投資新能源汽車、智慧化大規模定制、前瞻技術與車聯網、汽車服務與汽車金融領域內多個項目,其中公司控股股東上汽總公司擬認購不超過30億元,公司員工持股計畫擬認購不超過11.6745億元。

定增募集資金主要投向新興領域:其中新能源相關專案72億、智慧定制化專案20億、前瞻技術(燃料電池/智慧汽車)及車聯網19億、汽車服務與金融專案39億。

戰略定位由汽車製造轉為綜合服務:研發製造業環節,資源更多的向新能源汽車、智慧汽車傾斜;同時更加注重汽車服務領域的潛力挖掘,包括汽車金融、維修服務等。

公司為業務轉型已做大量的準備和調整:1、組織架構調整,新成立服務貿易事業部、金融事業部,用於整合內部相關業務;2、廣泛儲備技術,公司先後投入60億用於新能源產品開發,是國內少有的推出插電式車型的車企;3、積極跨界合作,公司已與阿裡巴巴、中國石油等企業在車聯網、汽車後市場方面建立戰略合作;4、激勵機制調整,本次增發實現更大額度的持股,同時在部分新業務上還實現了更大幅度的員工持股。

公司新業務發展目標:1、新能源汽車領域,完善供應鏈體系,有效降低成本,加快推出產品,2016、2020年分別實現2.6、20萬銷量;2、智慧汽車領域,2016年下半年推出與阿裡巴巴合作開發的首款互聯網汽車;3、服務貿易領域,以車享網/車享家為服務平臺,建立涵蓋(自建1500家)1萬家店的服務體系,至2020年,實現收入3000億(含物流/維修/金融)。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

大眾汽車尾氣門賠償方案出爐

據外媒報導,大眾汽車集團將準備至少100億美元,應對“尾氣門”醜聞導致的賠償。按照美國聯邦法官設定的最後期限,大眾日前簽訂框架協定,將回購50多萬輛作弊柴油車,並將向車主支付賠償金。

大眾這次將賠上百億美元

據知情人士披露,大眾汽車集團此次還同意安排至少100億美元預算,用於支付美國政府的美國民事訴訟罰款和美國車主的索賠。

此外,大眾回購的車輛將包括自2009年起售的捷達轎車、高爾夫緊湊車及奧迪A3,這些車輛均搭載2.0升EA189柴油發動機。不過,包括奧迪和保時捷SUV在內的另外8萬輛搭載3.0升柴油發動機的車輛則不在回購範圍之內,這些車輛同樣採用了作弊軟體,使得測試結果好於實際情況。

關於本次大眾如何賠償,來自路透社的消息稱,目前在個人賠償方面,大眾還未做出任何決定。而德國《世界報》(Die Welt)4月20日則報導稱,大眾“尾氣門”解決方案中將包括支付相關車主每人5,000美元,涉及58萬輛車,但不包括升級軟體的費用。

損失總金額到底是多少?

大眾汽車將總共為尾氣門醜聞蒙受多少經濟損失?迄今尚無準確說法。起初在去年9月尾氣門曝光甫始,大眾被指將被美國當局罰款180多億美元,創汽車行業最大罰單。

之後大眾宣佈,在2015年第三季度安排67億歐元(約合76億美元)用於修復相關車輛和賠償。不過由於在全球範圍內涉及超過1,100萬輛車,之後又多次曝光更多車輛涉及排放測試舞弊,業界對大眾經濟損失總額的估計值也隨之膨脹。部分機構甚至認為大眾可能最終損失900億美元之巨。

不過實際情況可能沒有極端說法那樣令人震驚。從理論上說,大眾蒙受的民事賠償可能超過420億美元,但現實中卻並未達到該數字。彭博社分析師Brandon Barnes估算,大眾回購在美國所有舞弊車輛將耗費94億美元。

Evercore ISI機構最新估算數字顯示,大眾可能最終為尾氣門埋單大約300億歐元。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

※評比前十大台北網頁設計台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

台灣海運大陸貨務運送流程

兩岸物流進出口一站式服務

三菱再爆電動車里程造假疑雲

日商三菱汽車日前自爆旗下汽車油耗測試造假,如今連電動車款也出了問題。日媒指出,三菱在海外銷售的電動車i-MiEV也涉及謊報里程耗電數據問題。

德國福斯汽車去年爆出排放廢氣造假醜聞,引發各界譁然。今年四月20日,日商三菱汽車也自爆迷你車款油耗測試造假,並開始研擬補償機制。但一波未平、一波又起,日媒產經新聞指出,三菱的外銷款電動車i-MiEV在里程耗電數據方面也有造假情事,可能與三菱迷你車款一樣,用竄改空氣與輪胎阻力、抑或是採用時間較短的美國「高速慣性行駛法」測試,讓油耗/電耗數據較實際狀況降低5~10%。

若三菱電動車確實也牽涉里程造假,則旗下有問題的車款已包括:RVR、Outlander、Pajero、Minicab MiEV、i-MiEV等。日本相關當局要求三菱在27日之前繳交報告。

自三菱自爆油耗造假醜聞已來,三菱的股票持續重挫,直到4月22日止,市值已蒸發3,600億日圓左右。

(照片來源:)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

SpringBoot基本配置詳解

SpringBoot項目有一些基本的配置,比如啟動圖案(banner),比如默認配置文件application.properties,以及相關的默認配置項。

示例項目代碼在:

一、啟動圖案banner

編寫banner.txt放入resources文件夾下,然後啟動項目即可修改默認圖案。

關於banner的生成,可以去一些專門的網站。

比如:https://www.bootschool.net/ascii

二、配置文件application

2.1 application.properties/yml

resources下通常會默認生成一個application.properties文件,這個文件包含了SpringBoot項目的全局配置文件。裏面的配置項通常是這樣的:

server.port=8080

在這個文件里我們可以添加框架支持的配置項,比如項目端口號、JDBC連接的數據源、日誌級別等等。

現在比較流行的是將properties文件改為yml文件。yml文件的格式yaml是這樣的:

server:
    port: 8080

yml和properties的作用是一樣的。而yml的好處是顯而易見的——更易寫易讀。

屬性之間互相調用使用${name}:

eknown:
    email: eknown@163.com
    uri: http://www.eknown.cn
    title: 'hello, link to ${eknown.uri} or email to ${eknown.email}'

鏈接:

2.2 多環境配置文件

通常開發一個應用會有多個環境,常見如dev/prod,也會有test,甚至其他一些自定義的環境,SpringBoot支持配置文件的靈活切換。

定義新配置文件需要遵循以下格式:application-{profile}.properties 或者application-{profile}.yml

比如現在有dev和prod兩個環境,我需要在application.yml文件之外新建兩個文件:

  1. application-dev.yml

    server:
       port: 8080
  2. application-prod.yml

    server:
      port: 8081

然後在application.yml中通過application.profiles.active={profile}指明啟用那個配置:

application:
    profiles:
      active: dev

除了在application.yml中指定配置文件外,還可以通過啟動命令指定:java -jar xxx.jar --spring.profiles.active=dev

2.2 自定義配置項並獲取它

主要介紹兩種方式,獲取單個配置項和獲取多個配置項。

舉例:

eknown:
    email: eknown@163.com
    uri: http://www.eknown.cn

2.2.1 使用@Value註解獲取單個配置項

@Value("${eknown.email}")
private String email;

@Value("${eknown.uri}")
private String url;

注意:使用@Value註解的時候,所在類必須被Spring容器管理,也就是被@Component、@Controller、@Service等註解定義的類。

2.2.2 獲取多個配置項

第一種,定義一個bean類,通過@Value獲取多個配置項:

@Component
public class MyConfigBean {
  
}

然後我們通過get方法來獲取這些值:

@RestController
public class BasicAction {
  
  @Autowired
  private MyConfigBean myConfigBean;

}

第二種,使用註解@ConfigurationProperties:

@Component
@ConfigurationProperties(perfix="eknown")
public class MyConfigBean {

  private String email;
  private String uri;
}

這裏只需要通過prefix指定前綴即可,後面的值自動匹配。

這裏我們還使用了@Component註解來讓spring容器管理這個MyConfigBean。

此外,我們可以不需要引入@Component,轉而在Application啟動類上加上@EnableConfigurationProperties({MyConfigBean.class})來啟動這個配置。

注意:我們這裡是從主配置文件,也就是SpringBoot默認的application-profile文件中獲取配置數據的。

而從自定義的配置文件,比如test.yml這種形式中獲取配置項時,情況是有點不大一樣的。

三、自定義配置文件

上面介紹的配置文件都是springboot默認的application開頭的文件。如果要自定義一個配置文件呢,比如test.yml或test.properties,怎麼獲取其中的配置項呢?

使用@PageResource註解即可。

首先我們來看一下讀取自定義的properties文件里的內容:

test.properties

hello.time=2019.11.19
hello.name=eknown

定義Configuration類:

@Configuration
@PropertySource("classpath:test.properties")
//@PropertySource("classpath:test.yml") // 注意,yml文件不能直接這樣寫,會讀不出數據
@ConfigurationProperties(prefix = "hello")
public class TestConfiguration {
    private String name;
    private String time;

    // hide get and set methods
}

測試一下:

@RestController
@RequestMapping(value = "test")
public class TestAction {

    @Autowired
    private TestConfiguration testConfiguration;

    @GetMapping(value = "config")
    public String test() {
        return testConfiguration.getName() + "<br/>" + testConfiguration.getTime();
    }
}

如果將properties文件換成yml文件呢?

我們嘗試一下,發現:

讀不出數據?

分析一下@PropertySource註解,發現其使用的PropertySourceFactory是DefaultPropertySourceFactory.

這個類的源碼如下:

public class DefaultPropertySourceFactory implements PropertySourceFactory {
    public DefaultPropertySourceFactory() {
    }

    public PropertySource<?> createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {
        return name != null ? new ResourcePropertySource(name, resource) : new ResourcePropertySource(resource);
    }
}

這個類只能處理properties文件,無法處理yml文件。所以我們需要自定義一個YmlSourceFactory。

public class YamlSourceFactory extends DefaultPropertySourceFactory {

    @Override
    public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
        return new YamlPropertySourceLoader().load(resource.getResource().getFilename()
                , resource.getResource()).get(0);
    }
}

然後定義test.yml文件的config類:

@Configuration
@PropertySource(value = "classpath:test.yml", encoding = "utf-8", factory = YamlSourceFactory.class)
@ConfigurationProperties(prefix = "yml.hello")
public class TestYamlConfiguration {
    private String name;
    private String time;

    // hide get and set methods
}

注:為了區分test.properties和test.yml,這裏的test.yml中的屬性以yml.hello開頭。

編寫一下測試:

    @Autowired
    private TestYamlConfiguration ymlConfiguration;

    @GetMapping(value = "yml")
    public String testYml() {
        return "yml config: <br/>" + ymlConfiguration.getName() + "<br/>" + ymlConfiguration.getTime();
    }

訪問:

四、補充@ConfigurationProperties

網上一些資料中,為配合使用@ConfigurationProperties,還使用了@EnableConfigurationProperties註解。

經過測試發現:

  1. 從SpringBoot默認配置文件讀取配置信息,使用@ConfigurationProperties + @Component/@Configuration,或者@ConfigurationProperties + 在啟動類添加@EnableConfigurationProperties({class})。這兩種方式都能解決問題

  2. 從非默認配置文件讀取配置信息,需要利用@PropertySource註解。同樣兩種方式:

    2.1 @PropertySource + @ConfigurationProperties + @Component/@Configuration

    2.2 @PropertySource + @ConfigurationProperties + @Component/@Configuration + @EnableConfigurationProperties,第二種方式存在一個問題,即還是必須要使用@Component註解,如果不使用,則會導致讀取配置信息為null,但程序不會報錯;而如果採用了,則會導致bean類的set方法被執行兩次(也就是生成了兩個同樣類型的bean類)。這種方式不建議!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?