Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Sunday, January 17, 2021

Java Generic Index

泛型的出現,解決了類型安全問題

泛型具有以下優點:

編譯時的強類型檢查

避免了類型轉換

泛型編程可以實現通用算法

通過使用泛型,程序員可以實現通用算法,這些算法可以處理不同類型的集合,可以自定義,並且類型安全且易於閱讀

 

泛型,即「參數化型態」,在不創建新型態的情況下,透過泛型指定的不同型態來控制形式參數具體限制的型態。

這種參數型態可以用在類別、介面和方法。

Generic Class

Generic Interface

Generic Method

1. 沒有泛型

分別設置 Integer 型態的點座標和 Float 類型的點座標

=====================================================================

// 設置 Integer 型態的點座標
class IntegerPoint{  
    private Integer x ;       // 表示 X 座標 
    private Integer y ;       // 表示 Y 座標  
    public void setX(Integer x){  
        this.x = x ;  
    }  
    public void setY(Integer y){  
        this.y = y ;  
    }  
    public Integer getX(){  
        return this.x ;  
    }  
    public Integer getY(){  
        return this.y ;  
    }  
}
// 設置 Float 型態的點座標
class FloatPoint{  
    private Float x ;       // 表示 X 座標 
    private Float y ;       // 表示 Y 座標  
    public void setX(Float x){  
        this.x = x ;  
    }  
    public void setY(Float y){  
        this.y = y ;  
    }  
    public Float getX(){  
        return this.x ;  
    }  
    public Float getY(){  
        return this.y ;  
    }  
}

=====================================================================

如果我們可以用 Object 來取代

=====================================================================

class ObjectPoint {  
    private Object x ;  
    private Object y ;  
    public void setX(Object x){  
        this.x = x ;  
    }  
    public void setY(Object y){  
        this.y = y ;  
    }  
    public Object getX(){  
        return this.x ;  
    }  
    public Object getY(){  
        return this.y ;  
    }  
}

=====================================================================

使用時候是這樣

=====================================================================

ObjectPoint integerPoint = new ObjectPoint();
integerPoint.setX(new Integer(100));
Integer integerX = (Integer)integerPoint.getX();
ObjectPoint floatPoint = new ObjectPoint();
floatPoint.setX(new Float(100.12f));
Float floatX = (Float)floatPoint.getX();

=====================================================================

但如果改成這樣

ObjectPoint floatPoint = new ObjectPoint();
floatPoint.setX(new Float(100.12f));
String floatX = (String)floatPoint.getX();

編譯時不會報錯,執行時,就會報錯,透過泛型的方法,就可以在編譯時檢查出


=====================================================================

2. 泛型類別

=====================================================================
//定義
class Point<T> {//此處可以隨便寫標識符號T
    private T x;
    
    //作為參數傳入
    public void setX(T x){
        this.x = x;
    }
    
    //作為返回值
    public T getX(){
        return this.x;
    }
}

class TestGeneric1 {
    public static void main(String[] args){
        //IntegerPoint使用
        Point<Integer> p1 = new Point<Integer>();
        p1.setX(new Integer(100));
        System.out.println(p1.getX());

        //FloatPoint使用
        Point<Float> p2 = new Point<Float>();
        p2.setX(new Float(100.12f));
        System.out.println(p2.getX());
    }
}

=====================================================================

優點:

不用強制轉型

使用型態不對時,在編譯時間會報錯

=====================================================================

3. 多泛型參數型態及字母規範

=====================================================================
//定義
class MorePoint<T, U> { //多泛型參數型態 T, U
    private T x;
    private U name;
    
    //作為參數傳入
    public void setX(T x){
        this.x = x;
    }
    public void setName(U name){
        this.name = name;
    }
    
    //作為返回值
    public T getX(){
        return this.x;
    }
    public U getName(){
        return this.name;
    }
}

class TestGeneric2 {
    public static void main(String[] args){
        MorePoint<Integer, String> morePoint = new MorePoint<Integer, String>();
        morePoint.setX(new Integer(1));
        morePoint.setName("Java generic class.");
        System.out.println(morePoint.getX() + ", " + morePoint.getName());
    }
}

=====================================================================

字母規範


=====================================================================

參考

Java Generic

 JAVA泛型通配符T,E,K,V区别,T以及Class<T>,Class<?>的区别

Java Index






















Apache POI

Apache POI 教學

          Java POI 4.0

          HSSF、XSSF和SXSSF區別以及Excel導出優化




 

Sunday, January 10, 2021

ReentrantLock

 =====================================================================

public class RenntrantLockActivity extends AppCompatActivity {

    Lock lock;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_renntrant_lock);

        lock = new ReentrantLock();

        doSth();

    }

    public void doSth() {

        lock.lock();

        try {

            //这里执行线程同步操作

        } finally {

            lock.unlock();

        }

    }

}

 =====================================================================

使用 ReentrantLock 很好理解,就好比我们现实的锁头是一样道理的。

使用 ReentrantLock 的一般组合是 lock 与 unlock 成对出现的,需要注意的是,千万不要忘记调用 unlock 来释放锁,否则可能会引发死锁等问题。如果忘记了在finally块中释放锁,可能会在程序中留下一个定时炸弹,随时都会炸了,而是用 synchronized,JVM 将确保锁会获得自动释放,这也是为什么 Lock 没有完全替代掉 synchronized 的原因


对象锁

 ============================================================

public class SynchronizedDemo {

    //同步方法,对象锁

    public synchronized void syncMethod() {

    }

    //同步块,对象锁

    public void syncThis() {

        synchronized (this) {

        }

    }

}

============================================================

類鎖

============================================================

public class SynchronizedDemo {

    //同步class对象,类锁

    public void syncClassMethod() {

        synchronized (SynchronizedDemo.class) {

        }

    }

    //同步静态方法,类锁

    public static synchronized void syncStaticMethod(){

    }

============================================================

类锁和对象锁的概念

1、不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。

2、而且类锁和对象锁互相不干扰。

  物件鎖

  類鎖

类锁和对象锁的概念

ReentrantLock

Friday, January 8, 2021

Program,Process,Thread

 Program,Process,Thread

在介紹Thread之前,我們必須先把Program和Process這兩個觀念作一個釐清。

  • Program:一群程式碼的集合,用以解決特定的問題。以物件導向的觀念來類比,相當於Class。
  • Process:由Program所產生的執行個體,一個Program可以同時執行多次,產生多個Process。以物件導向的觀念來類比,相當於Object。每一個Process又由以下兩個東西組成
    • 一個Memory Space。相當於Object的variable,不同Process的Memory Space也不同,彼此看不到對方的Memory Space。
    • 一個以上的Thread。Thread代表從某個起始點開始(例如main),到目前為止所有函數的呼叫路徑,以及這些呼叫路徑上所用到的區域變數。當然程式的執行狀態,除了紀錄在主記憶體外,CPU內部的暫存器(如Program Counter, Stack Pointer, Program Status Word等)也需要一起紀錄。所以Thread又由下面兩項組成
      • Stack:紀錄函數呼叫路徑,以及這些函數所用到的區域變數
      • 目前CPU的狀態

如何產生Thread

Java以java.lang.Thread這個類別來表示Thread。Class Thread有兩個Constructor:

  1. Thread()
  2. Thread(Runnable)

第一個Constrctor沒有參數,第二個需要一個Runnable物件當參數。Runnable是一個interface,定義於java.lang內,其宣告為

public interface Runnable {
    public void run();
}

使用Thread()產生的Thread,其進入點為Thread裡的run();使用Thread(Runnable)產生的Thread,其進入點為Runnable物件裡的run()。當run()結束時,這個Thread也就結束了;這和main()結束有相同的效果。其用法以下面範例說明:

public class ThreadExample1 extends Thread {
    public void run() { // override Thread's run()
        System.out.println("Here is the starting point of Thread.");
        for (;;) { // infinite loop to print message
            System.out.println("User Created Thread");
        }
    }
    public static void main(String[] argv) {
        Thread t = new ThreadExample1(); // 產生Thread物件
        t.start(); // 開始執行t.run()
        for (;;) {
            System.out.println("Main Thread");
        }
    }
}

以上程式執行後,螢幕上會持續印出"User Created Thread"或"Main Thread"的字樣。利用Runnable的寫法如下

public class ThreadExample2 implements Runnable {
    public void run() { // implements Runnable run()
        System.out.println("Here is the starting point of Thread.");
        for (;;) { // infinite loop to print message
            System.out.println("User Created Thread");
        }
    }
    public static void main(String[] argv) {
        Thread t = new Thread(new ThreadExample2()); // 產生Thread物件
        t.start(); // 開始執行Runnable.run();
        for (;;) {
            System.out.println("Main Thread");
        }
    }
}

Thread的優先權與影響資源的相關方法

Thread.setPriority(int)可以設定Thread的優先權,數字越大優先權越高。Thread定義了3個相關的static final variable

public static final int MAX_PRIORITY 10
public static final int MIN_PRIORITY 1
public static final int NORM_PRIORITY 5 

要提醒讀者的是,優先權高的Thread其佔有CPU的機會比較高,但優先權低的也都會有機會執行到。其他有關Thread執行的方法有:

  • yield():先讓給別的Thread執行
  • sleep(int time):休息time mini second(1/1000秒)
  • join():呼叫ThreadA.join()的執行緒會等到ThreadA結束後,才能繼續執行

你可以執行下面的程式,看看yield()的效果

public class ThreadExample1 extends Thread {
    public void run() { // overwrite Thread's run()
        System.out.println("Here is the starting point of Thread.");
        for (int i =0; i < 20; i++) { // infinite loop to print message
            System.out.println("User Created Thread");
            yield();
        }
    }
    public static void main(String[] argv) {
        Thread t = new ThreadExample1(); // 產生Thread物件
        t.start(); // 開始執行t.run()
        for (int i =0; i < 20; i++) {
            System.out.println("Main Thread");
            yield();
        }
    }
}
===================================================================================
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
Here is the starting point of Thread.
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
User Created Thread
===================================================================================

觀看join的效果

public class JoinExample extends Thread {
    String myId;
    public JoinExample(String id) {
        myId = id;
    }
    public void run() { // overwrite Thread's run()
	for (int i=0; i < 20; i++) {
            System.out.println(myId+" Thread");
        }
    }
    public static void main(String[] argv) {
        Thread t1 = new JoinExample("T1"); // 產生Thread物件
        Thread t2 = new JoinExample("T2"); // 產生Thread物件
        t1.start(); // 開始執行t1.run()
        t2.start();
        try {
            t1.join(); // 等待t1結束
            t2.join(); // 等待t2結束
        } catch (InterruptedException e) {}
        for (int i=0;i < 5; i++) {
            System.out.println("Main Thread");
        }
    }
}
==================================================================================
T1 Thread
T1 Thread
T1 Thread
T1 Thread
T1 Thread
T1 Thread
T2 Thread
T2 Thread
T2 Thread
T2 Thread
T2 Thread
T2 Thread
T2 Thread
T2 Thread
T2 Thread
T2 Thread
T1 Thread
T1 Thread
T1 Thread
T2 Thread
T2 Thread
T2 Thread
T1 Thread
T1 Thread
T2 Thread
T1 Thread
T1 Thread
T1 Thread
T2 Thread
T2 Thread
T2 Thread
T2 Thread
T2 Thread
T1 Thread
T1 Thread
T1 Thread
T1 Thread
T1 Thread
T2 Thread
T1 Thread
Main Thread
Main Thread
Main Thread
Main Thread
Main Thread
======================================================================================

觀看sleep的效果

public class SleepExample extends Thread {
    String myId;
    public SleepExample(String id) {
        myId = id;
    }
    public void run() { // overwrite Thread's run()
        for (int i=0; i < 20; i++) {
            System.out.println(myId+" Thread");
            try {
                sleep(100);
            } catch (InterruptedException e) {}
        }
    }
    public static void main(String[] argv) {
        Thread t1 = new SleepExample("T1"); // 產生Thread物件
        Thread t2 = new SleepExample("T2"); // 產生Thread物件
        t1.start(); // 開始執行t1.run()
        t2.start();
    }
}
=======================================================================================
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
T1 Thread
T2 Thread
===================================================================

Critical Section(關鍵時刻)的保護措施

如果設計者沒有提供保護機制的話,Thread取得和失去CPU控制權的時機是由作業系統來決定。也就是說Thread可能在執行任何一個機器指令時,被作業系統取走CPU控制權,並交給另一個Thread。由於某些真實世界的動作是不可分割的,例如跨行轉帳X圓由A帳戶到B帳戶,轉帳前後這兩個帳戶的總金額必須相同,但以程式來實作時,卻無法用一個指令就完成,如轉帳可能要寫成下面的這一段程式碼

if (A >= X) {
    A = A - X; // 翻譯成3個機器指令LOAD A, SUB X, STORE A
    B = B +X;
}

如果兩個Thread同時要存取A,B兩帳戶進行轉帳,假設當Thread one執行到SUBX後被中斷,Threadtwo接手執行完成另一個轉帳要求,然後Threadone繼續執行未完成的動作,請問這兩個轉帳動作正確嗎?我們以A=1000,B=0,分別轉帳100,200圓來說明此結果

    LOAD A // Thread 1, 現在A還是1000
    SUB 100 // Thread 1
    LOAD A // 假設此時Thread 1被中斷,Thread 2接手, 因為Thread 1 還沒有執行STORE A, 所以變數A還是1000
    SUB 200 // Thread 2
    STORE A // Thread 2, A = 800
    LOAD B // Thread 2, B現在是0
    ADD 200 // Thread 2
    STORE B // B=200
    STORE A // Thread 1拿回控制權, A = 900
    LOAD B // Thread 1, B = 200
    ADD 100 // Thread 1
    STORE B // B = 300

你會發現執行完成後A=900,B=300,也就是說銀行平白損失了200圓。當然另外的執行順序可能造成其他不正確的結果。我們把這問題再整理一下:

  1. 寫程式時假設指令會循序執行
  2. 某些不可分割的動作,需要以多個機器指令來完成
  3. Thread執行時可能在某個機器指令被中斷
  4. 兩個Thread可能執行同一段程式碼,存取同一個資料結構
  5. 這樣就破壞了第1點的假設

因此在撰寫多執行緒的程式時,必須特別考慮這種狀況(又稱為race condition)。Java的解決辦法是,JVM會在每個物件上擺一把鎖(lock),然後程式設計者可以宣告執行某一段程式(通常是用來存取共同資料結構的程式碼, 又稱為Critical Section)時,必須拿到某物件的鎖才行,這個鎖同時間最多只有一個執行緒可以擁有它。

public class Transfer extends Thread {
    public static Object lock = new Object();
    public static int A = 1000;
    public static int B = 0;
    private int amount;
    public Transfer(int x) {
        amount = x;
    }
    public void run() {
        synchronized(lock) { // 取得lock,如果別的thread A已取得,則目前這個thread會等到thread A釋放該lock
            if (A >= amount) {
                A = A - amount;
                B = B + amount;
            }
        } // 離開synchronized區塊後,此thread會自動釋放lock
    }
    public static void main(String[] argv) {
        Thread t1 = new Transfer(100);
        Thread t2 = new Transfer(200);
        t1.start();
        t2.start();
    }
}

除了synchronized(ref)的語法可以鎖定ref指到的物件外,synchronized也可以用在object method前面,表示要鎖定this物件才能執行該方法。以下是Queue結構的範例

public class Queue {
    private Object[] data;
    private int size;
    private int head;
    private int tail;
    public Queue(int maxLen) {
        data = new Object[maxLen];
    }
    public synchronized Object deQueue() {
        Object tmp = data[head];
        data[head] = null;
        head = (head+1)%data.length;
        size--;
        return tmp;
    }
    public synchronized void enQueue(Object c) {
        data[tail++] = c;
        tail %= data.length;
        size++;
    }
}

雖然上面的程式正確無誤,但並未考慮資源不足時該如何處理。例如Queue已經沒有資料了,卻還想拿出來;或是Queue裡已經塞滿了資料,使用者卻還要放進去?我們當然可以使用Exception Handling的機制:

public class Queue {
    private Object[] data;
    private int size;
    private int head;
    private int tail;
    public Queue(int maxLen) {
        data = new Object[maxLen];
    }
    public synchronized Object deQueue() throws Exception {
        if (size == 0) {
            throw new Exception();
        }
        Object tmp = data[head];
        data[head] = null;
        head = (head+1)%data.length;
        size--;
        return tmp;
    }
    public synchronized void enQueue(Object c) throws Exception {
        if (size >= maxLen) {
            throw new Exception();
        }
        data[tail++] = c;
        tail %= data.length;
        size++;
    }
}

但假設我們的執行環境是,某些Thread專門負責讀取使用者的需求,並把工作放到Queue裡面,某些Thread則專門由Queue裡抓取工作需求做進一步處理。這種架構的好處是,可以把慢速或不定速的輸入(如透過網路讀資料,連線速度可能差很多),和快速的處理分開,可使系統的反應速度更快,更節省資源。那麼以Exceptoin來處理Queue空掉或爆掉的情況並不合適,因為使用Queue的人必須處理例外狀況,並不斷的消耗CPU資源:

public class Getter extends Thread {
    Queue q;
    public Getter(Queue q) {
        this.q = q;
    }
    public void run() {
        for (;;) {
            try {
                Object data = q.deQueue();
                // processing
            } catch(Exception e) {
                // if we try to sleep here, user may feel slow response
                // if we do not sleep, CPU will be wasted
            }
        }
    }
}
public class Putter extends Thread {
    Queue q;
    public Putter(Queue q) {
        this.q = q;
    }
    public void run() {
        for (;;) {
            try {
                Object data = null;
                // get user request
                 q.enQueue(data);
            } catch(Exception e) {
                // if we try to sleep here, user may feel slow response
                // if we do not sleep, CPU will be wasted
            }
        }
    }
}
public class Main {
    public static void main(String[] argv) {
        Queue q = new Queue(10);
        Getter r1 = new Getter(q);
        Getter r2 = new Getter(q);
        Putter w1 = new Putter(q);
        Putter w2 = new Putter(q);
        r1.start();
        r2.start();
        w1.start();
        w2.start();
    }
}

為了解決這類資源分配的問題,Java Object提供了下面三個method:

  • wait():使呼叫此方法的Thread進入Blocking Mode,並設為等待該Object, 呼叫wait()時, 該Thread必須擁有該物件的lock。Blocking Mode下的Thread必須釋放所有手中的lock,並且無法使用CPU。
  • notifyAll():讓等待該Object的所有Thread進入Runnable Mode。
  • notify():讓等待該Object的某一個Thread進入Runnable Mode。

所謂Runnable Mode是指該Thread隨時可由作業系統分配CPU資源。Blocking Mode表示該Thread正在等待某個事件發生,作業系統不會讓這種Thread取得CPU資源。前一個Queue的範例就可以寫成:

public class Queue {
    private Object[] data;
    private int size;
    private int head;
    private int tail;
    public Queue(int maxLen) {
        data = new Object[maxLen];
    }
    public synchronized Object deQueue() {
        while (size==0) { // When executing here, Thread must have got lock and be in running mode
            // Let current Thread wait this object(to sleeping mode)
            try {
                wait(); // to sleeping mode, and release all lock
            } catch(Exception ex) {};
        }
        Object tmp = data[head];
        data[head] = null;
        head = (head+1)%data.length;
        if (size==data.length) {
            // wake up all Threads waiting this object
            notifyAll();
        }
        size--;
        return tmp;
    } // release lock
    public synchronized void enQueue(Object c) {
        while (size==data.length) {  // When executing here, Thread must have got lock and be in running mode
            // Let current thread wait this object(to sleeping mode)
            try {
                wait(); // to sleeping mode, and release all lock
            } catch(Exception ex) {};
        }
        data[tail++] = c;
        tail %= data.length;
        size++;
        if (size==1) {
            // wake up all Threads waiting this object
            notifyAll();
        }
    }
}


public class ReaderWriter extends Thread {
    public static final int READER = 1;
    public static final int WRITER = 2;
    private Queue q;
    private int mode;
    public void run() {
        for (int i=0; i < 1000; i++) {
            if (mode==READER) {
                q.deQueue();
            } else if (mode==WRITER) {
                q.enQueue(new Integer(i));
            }
        }
    }
    public ReaderWriter(Queue q, int mode) {
        this.q = q;
        this.mode = mode;
    }
    public static void main(String[] args) {
        Queue q = new Queue(5);
        ReaderWriter r1, r2, w1, w2;
        (w1 = new ReaderWriter(q, WRITER)).start();
        (w2 = new ReaderWriter(q, WRITER)).start();
        (r1 = new ReaderWriter(q, READER)).start();
        (r2 = new ReaderWriter(q, READER)).start();
        try {
            w1.join(); // wait until w1 complete
            w2.join(); // wait until w2 complete
            r1.join(); // wait until r1 complete
            r2.join(); // wait until r2 complete
        } catch(InterruptedException epp) {
        }
    }
}

Multiple Reader-Writer Monitors

上一節的Queue資料結構,不論是enQueue()或deQueue()都會更動到Queue的內容。而在許多應用裡,資料結構可以允許同時多個讀一個寫。本節舉出幾個不同的例子,說明多個Reader-Writer時的可能排程法。

Single Reader-Writer, 只同時允許一個執行緒存取

public class SingleReaderWriter {
    int n; // number of reader and write, 0 or 1
    public synchronized void startReading() throws InterruptedException {
        while (n != 0) {
            wait();
        }
        n = 1;
    }
    public synchronized void stopReading() {
        n = 0;
        notify();
    }
    public synchronized void startWriting() throws InterruptedException {
        while (n != 0) {
            wait();
        }
        n = 1;
    }
    public synchronized void stopWriting() {
        n = 0;
        notify();
    }
}
// 這是一個使用範例, 程式能否正確執行要靠呼叫正確的start和stop
public class WriterThread extends Thread {
    SingleReaderWriter srw;
    public WriterThread(SingleReaderWriter srw) {
        this.srw = srw;
    }
    public void run() {
        startWring();
        // insert real job here
        stopWriting();
    }
}
public class ReaderThread extends Thread {
    SingleReaderWriter srw;
    public ReaderThread(SingleReaderWriter srw) {
        this.srw = srw;
    }
    public void run() {
        startReading();
        // insert real job here
        stopReading();
    }
}
public class Test {
    public static void main(String[] argv) {
        SingleReaderWriter srw = new SingleReaderWriter;
        // create four threads
        (new WriterThread(srw)).start();
        (new WriterThread(srw)).start();
        (new ReaderThread(srw)).start();
        (new ReaderThread(srw)).start();
    }
}

其他可能的策略實作如下:

Reader優先:

public class ReadersPreferredMonitor {
    int nr; // The number of threads currently reading, nr > = 0
    int nw; // The number of threads currently writing, 0 or 1
    int nrtotal; // The number of threads either reading or waiting to read, nrtotal > = nr
    int nwtotal; // The number of threads either writing or waiting to write
    public synchronized void startReading() throws InterruptedException {
        nrtotal++; // 想要read的thread又多了一個
        while (nw != 0) { // 還有write thread正在write
            wait();
        }
        nr++; // 正在讀的thread多了一個
    }
    public synchronized void startWriting() throws InterruptedException {
        nwtotal++; // 想要寫的thread又多了一個
        while (nrtotal+nw != 0) { // 只要有thread想要讀,或是有thread正在寫,禮讓
            wait();
        }
        nw = 1;
    }
    public synchronized void stopReading() {
        nr--; // 正在讀的少一個
        nrtotal--; // 想要讀的少一個
        if (nrtotal == 0) { // 如果沒有要讀的,叫醒想寫的
            notify();
        }
    }
    public synchronized void stopWriting() {
        nw = 0; // 沒有thread正在寫
        nwtotal--; // 想寫的少一個
        notifyAll(); // 叫醒所有想讀和想寫的
    }
}

Writer優先:

public class WritersPreferredMonitor {
    int nr; // The number of threads currently reading, nr > = 0
    int nw; // The number of threads currently writing, 0 or 1
    int nrtotal; // The number of threads either reading or waiting to read, nrtotal > = nr
    int nwtotal; // The number of threads either writing or waiting to write
    public synchronized void startReading() throws InterruptedException {
        nrtotal++; // 想要read的thread又多了一個
        while (nwtotal != 0) { // 還有thread想要write
            wait();
        }
        nr++; // 正在讀的thread多了一個
    }
    public synchronized void startWriting() throws InterruptedException {
        nwtotal++; // 想要寫的thread又多了一個
        while (nr+nw != 0) { // 有thread正在讀,或是有thread正在寫
            wait();
        }
        nw = 1;
    }
    public synchronized void stopReading() {
        nr--; // 正在讀的少一個
        nrtotal--; // 想要讀的少一個
        if (nr == 0) { // 如果沒有正在讀的,叫醒所有的(包括想寫的)
            notifyAll();
        }
    }
    public synchronized void stopWriting() {
        nw = 0; // 沒有thread正在寫
        nwtotal--; // 想寫的少一個
        notifyAll(); // 叫醒所有想讀和想寫的
    }
}

Reader和Writer交互執行:

public class AlternatingReadersWritersMonitor {
    int[] nr = new int[2]; // The number of threads currently reading
    int thisBatch; // Index in nr of the batch of readers currently reading(0 or 1)
    int nextBatch = 1; // Index in nr of the batch of readers waitin to read(always 1-thisBatch)
    int nw; // The number of threads currently writing(0 or 1)
    int nwtotal; // The number of threads either writing or waiting to write
    public synchronized void startReading() throws InterruptedException {
        if (nwtotal == 0) { // 沒有thread要write, 將reader都放到目前要處理的這一批
            nr[thisBatch]++;
        } else {
            nr[nextBatch]++;
            int myBatch = nextBatch;
            while (thisBatch != myBatch) {
                wait();
            }
        }
    }
    public synchronized void stopReading() {
        nr[thisBatch]--;
        if (nr[thisBatch] == 0) { // 目前這批的reader都讀完了,找下一個writer
            notifyAll();
        }
    }
    public synchronized void startWriting() throws InterruptedException {
        nwtotal++;
        while (nr[thisBatch]+nw != 0) { // 目前這批還沒完,或有thread正在寫
            wait();
        }
        nw = 1;
    }
    public synchronized void stopWriting() {
        nw = 0;
        nwtotal--;
        int tmp = thisBatch; // 交換下一批要讀的
        thisBatch = nextBatch;
        nextBatch = tmp;
        notifyAll();
    }
}

給號依序執行

public class TakeANumberMonitor {
    int nr; // The number of threads currently reading
    int nextNumber; // The number to be taken by the next thread to arrive
    int nowServing; // The number of the thread to be served next
    public synchronized void startReading() throws InterruptedException {
        int myNumber = nextNumber++;
        while (nowServing != myNumber) { // 還沒輪到我
            wait();
        }
        nr++; // 多了一個Reader
        nowServing++; // 準備檢查下一個
        notifyAll();
    }
    public synchronized void startWriting() throws InterruptedException {
        int myNumber = nextNumber++;
        while (nowServing != myNumber) { // 還沒輪到我
            wait();
        }
        while (nr >  0) { // 要等所有的Reader結束
            wait();
        }
    }
    public synchronized void stopReading() {
        nr--; // 少了一個Reader
        if (nr == 0) {
            notifyAll();
        }
    }
    public synchronized void stopWriting() {
        nowServing++; // 準備檢查下一個
        notifyAll();
    }
}
Reference:
https://programming.im.ncnu.edu.tw/J_Chapter9.htm

Java Thread

一個 Process 可以有多個 Thread

同一個 Process 內的 Thread 使用相同的 Memory Space,但這些 Thread 各自擁有其 Stack。

換句話說,Thread 能透過reference存取到相同的Object,但是local variable卻是各自獨立的。

作業系統會根據 Thread 的優先權以及已經用掉的 CPU 時間,在不同的 Thread 作切換,以讓各個Thread 都有機會執行。

对于线程常用的操作有:wait(等待)、notify(唤醒)、notifyAll、sleep(睡眠)、join(阻塞)、yield(礼让)

wait、notify、notifyAll都必须在synchronized中执行,否则会抛出异常

synchronized 关键字和 ReentrantLock 锁都是辅助线程同步使用的

初学者常犯的误区:一个对象只有一个锁(正确的)

类锁和对象锁的概念

Program,Process,Thread 

Thread order

30 天介紹 Java 的 Thread


Thread order

 startmain.java

=======================================================================

import java.io.*;

public class startmain {

  public static void main(String[] args) throws Exception {

   VolatileThread vt = new VolatileThread();

   Thread t1 = new Thread(vt);

   t1.start();

   System.out.println("main code end.");

  }

}

=======================================================================

VolatileThread.java

=======================================================================

public class VolatileThread implements Runnable {

  @Override

  public void run() {

    try {

      System.out.println("Inner Run before Thread:"+System.currentTimeMillis());

      Thread.sleep(2000);

      System.out.println("Inner Run after Thread:"+System.currentTimeMillis());

    } catch (Exception e) {

      e.printStackTrace();

    }

    System.out.println("Out Run Thread:"+System.currentTimeMillis());

  }

}

=======================================================================

Result

=======================================================================

main code end.

Inner Run before Thread:1610100064035

Inner Run after Thread:1610100066036

Out Run Thread:1610100066036

=======================================================================

 startmain.java

=======================================================================

import java.io.*;

public class startmain {

  public static void main(String[] args) throws Exception {

   VolatileThread vt = new VolatileThread();

   Thread t1 = new Thread(vt);

   t1.start();

   try {

     t1.join();    

   } catch(InterruptedException e) {

     System.out.println("inte");

  }

   System.out.println("main code end.");

  }

}

=======================================================================

Result

=======================================================================

Inner Run before Thread:1610100064035

Inner Run after Thread:1610100066036

Out Run Thread:1610100066036

main code end.

=======================================================================

Sunday, January 3, 2021

Java reference

每種編程語言都有自己操作內存中元素的方式,例如在 C 和 C++ 裏是通過指針,而在 Java 中則是通過“引用”。

在 Java 中一切都被視為了對象,但是我們操作的標識符實際上是對象的一個引用(reference)

//創建一個引用,引用可以獨立存在,並不一定需要與一個對象關聯 String s;


通過將這個叫“引用”的標識符指向某個對象,之後便可以通過這個引用來實現操作對象了。

String str = new String("abc");

System.out.println(str.toString());


JDK1.2 之前,Java中的定義很傳統:如果 reference 類型的數據中存儲的數值代表的是另外一塊內存的起始地址,就稱為這塊內存代表著一個引用。

Java 中的垃圾回收機制在判斷是否回收某個對象的時候,都需要依據“引用”這個概念。 在不同垃圾回收算法中,對引用的判斷方式有所不同:

引用計數法:為每個對象添加一個引用計數器,每當有一個引用指向它時,計數器就加1,當引用失效時,計數器就減1,當計數器為0時,則認為該對象可以被回收(目前在Java中已經棄用這種方式了)。

可達性分析算法:從一個被稱為 GC Roots 的對象開始向下搜索,如果一個對象到GC Roots沒有任何引用鏈相連時,則說明此對象不可用。 

 JDK1.2 之前,一個對象只有“已被引用”和"未被引用"兩種狀態,這將無法描述某些特殊情況下的對象,比如,當內存充足時需要保留,而內存緊張時才需要被拋棄的一類對象。

四種引用類型

所以在 JDK.1.2 之後,Java 對引用的概念進行了擴充,將引用分為了:

強引用(Strong Reference)

軟引用(Soft Reference)

弱引用(Weak Reference)

虛引用(Phantom Reference)4 種,這 4 種引用的強度依次減弱。

一,強引用

Java中默認聲明的就是強引用,比如:

===============================================================

Object obj = new Object(); //只要obj還指向Object對象,Object對象就不會被回收 

obj = null;  //手動置null

===============================================================

只要強引用存在,垃圾回收器將永遠不會回收被引用的對象,哪怕內存不足時,JVM也會直接拋出OutOfMemoryError,不會去回收。如果想中斷強引用與對象之間的聯系,可以顯示的將強引用賦值為null,這樣一來,JVM就可以適時的回收對象了

二,軟引用

軟引用是用來描述一些非必需但仍有用的對象。在內存足夠的時候,軟引用對象不會被回收,只有在內存不足時,系統則會回收軟引用對象,如果回收了軟引用對象之後仍然沒有足夠的內存,才會拋出內存溢出異常。

這種特性常常被用來實現緩存技術,比如網頁緩存,圖片緩存等。

在 JDK1.2 之後,用java.lang.ref.SoftReference類來表示軟引用。

下面以一個例子來進一步說明強引用和軟引用的區別: 在運行下面的Java代碼之前,需要先配置參數 -Xms2M -Xmx3M,將 JVM 的初始內存設為2M,最大可用內存為 3M。

首先先來測試一下強引用,在限制了 JVM 內存的前提下,下面的代碼運行正常

========================================================================

public class TestOOM {

    public static void main(String[] args) {

         testStrongReference();

    }

    private static void testStrongReference() {

        // 當 new byte為 1M 時,程序運行正常

        byte[] buff = new byte[1024 * 1024 * 1];

    }

}

========================================================================

但是如果我們將

byte[] buff = new byte[1024 * 1024 * 1];

換為創建一個大小為 2M 的字節數組

byte[] buff = new byte[1024 * 1024 * 2];

則內存不夠使用,程序直接報錯,強引用並不會被回收


接著來看一下軟引用會有什麽不一樣,在下面的示例中連續創建了 10 個大小為 1M 的字節數組,並賦值給了軟引用,然後循環遍歷將這些對象打印出來。

========================================================================

public class TestOOM {

    private static List<Object> list = new ArrayList<>();

    public static void main(String[] args) {

         testSoftReference();

    }

    private static void testSoftReference() {

        for (int i = 0; i < 10; i++) {

            byte[] buff = new byte[1024 * 1024];

            SoftReference<byte[]> sr = new SoftReference<>(buff);

            list.add(sr);

        }

        System.gc(); //主動通知垃圾回收

        for(int i=0; i < list.size(); i++){

            Object obj = ((SoftReference) list.get(i)).get();

            System.out.println(obj);

        }

    }

}

========================================================================


我們發現無論循環創建多少個軟引用對象,打印結果總是只有最後一個對象被保留,其他的obj全都被置空回收了。

這裏就說明了在內存不足的情況下,軟引用將會被自動回收。

值得註意的一點 , 即使有 byte[] buff 引用指向對象, 且 buff 是一個strong reference, 但是 SoftReference sr 指向的對象仍然被回收了,這是因為Java的編譯器發現了在之後的代碼中, buff 已經沒有被使用了, 所以自動進行了優化。

如果我們將上面示例稍微修改一下

========================================================================

    private static void testSoftReference() {

        byte[] buff = null;

        for (int i = 0; i < 10; i++) {

            buff = new byte[1024 * 1024];

            SoftReference<byte[]> sr = new SoftReference<>(buff);

            list.add(sr);

        }

        System.gc(); //主動通知垃圾回收

        for(int i=0; i < list.size(); i++){

            Object obj = ((SoftReference) list.get(i)).get();

            System.out.println(obj);

        }

        System.out.println("buff: " + buff.toString());

    }

========================================================================

則 buff 會因為強引用的存在,而無法被垃圾回收,從而拋出OOM的錯誤。


如果一個對象惟一剩下的引用是軟引用,那麽該對象是軟可及的(softly reachable)。垃圾收集器並不像其收集弱可及的對象一樣盡量地收集軟可及的對象,相反,它只在真正 “需要” 內存時才收集軟可及的對象。

三,弱引用

弱引用的引用強度比軟引用要更弱一些,無論內存是否足夠,只要 JVM 開始進行垃圾回收,那些被弱引用關聯的對象都會被回收。

在 JDK1.2 之後,用 java.lang.ref.WeakReference 來表示弱引用。 我們以與軟引用同樣的方式來測試一下弱引用:

========================================================================

    private static void testWeakReference() {

        for (int i = 0; i < 10; i++) {

            byte[] buff = new byte[1024 * 1024];

            WeakReference<byte[]> sr = new WeakReference<>(buff);

            list.add(sr);

        }

        System.gc(); //主動通知垃圾回收

        for(int i=0; i < list.size(); i++){

            Object obj = ((WeakReference) list.get(i)).get();

            System.out.println(obj);

        }

    }

========================================================================


可以發現所有被弱引用關聯的對象都被垃圾回收了。

四,虛引用

虛引用是最弱的一種引用關系,如果一個對象僅持有虛引用,那麽它就和沒有任何引用一樣,它隨時可能會被回收,在 JDK1.2 之後,用 PhantomReference 類來表示,通過查看這個類的源碼,發現它只有一個構造函數和一個 get() 方法,而且它的 get() 方法僅僅是返回一個null,也就是說將永遠無法通過虛引用來獲取對象,虛引用必須要和 ReferenceQueue 引用隊列一起使用。

========================================================================

public class PhantomReference<T> extends Reference<T> {

    /**

     * Returns this reference object‘s referent.  Because the referent of a

     * phantom reference is always inaccessible, this method always returns

     * <code>null</code>.

     *

     * @return  <code>null</code>

     */

    public T get() {

        return null;

    }

    public PhantomReference(T referent, ReferenceQueue<? super T> q) {

        super(referent, q);

    }

}

========================================================================

那麽傳入它的構造方法中的 ReferenceQueue 又是如何使用的呢?

引用隊列可以與軟引用、弱引用以及虛引用一起配合使用,當垃圾回收器準備回收一個對象時,如果發現它還有引用,那麽就會在回收對象之前,把這個引用加入到與之關聯的引用隊列中去。程序可以通過判斷引用隊列中是否已經加入了引用,來判斷被引用的對象是否將要被垃圾回收,這樣就可以在對象被回收之前采取一些必要的措施。與軟引用、弱引用不同,虛引用必須和引用隊列一起使用。

參考

https://www.geeksforgeeks.org/types-references-java/

https://www.itread01.com/content/1537628537.html

Wednesday, December 30, 2020

JAVA泛型通配符T,E,K,V区别,T以及Class,Class的区别

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型

在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

泛型概念

操作的数据类型被指定为一个参数

参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

Java语言引入泛型的好处是安全简单


Class<T>在实例化的时候,T要替换成具体类

Class<?>它是个通配泛型,?可以代表任何类型   

<? extends T>受限统配,表示T的一个未知子类。

<? super T>下限统配,表示T的一个未知父类。


Reference:

https://www.jianshu.com/p/95f349258afb

https://github.com/LucienYang/FanxingDemo/tree/master/src/com/lyang/demo/fanxing

Monday, December 21, 2020

java static

 static variable are stored in class common memory

 引用 (reference)










From: https://openhome.cc/Gossip/Java/Static.html

Thursday, October 1, 2020

public, private, protected 區分

作用域   當前類 同一package 子孫類 其他package 


public     √    √      √    √ 


protected   √    √     √    × 


friendly    √    √     ×      × 


private    √    ×     ×     × 

 
不寫時默認為 friendly 

OO Index

 public, private, protected 區分

抽象類別 (Abstract Class) vs 介面 (Interface)

類別(Class)、抽象類別(Abstract Class)與介面(Interface)比較

n8n index

 【n8n免費本地端部署】Windows版|程式安裝x指令大補帖  【一鍵安裝 n8n】圖文教學,獲得無限額度自動化工具&限時免費升級企業版功能