JAVA併發編程學習筆記

最近一直在學習和實踐JAVA併發編程,也從書中總結了一些經驗,在這裏書寫一下可以馬上上手利用的內容,日後再慢慢補充完善。


1.在構建守護線程時,不能依靠finally塊中的內容,來確保執行關閉或清理資源的邏輯。


有如下示例代碼:

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("理論上,是執行不了這個方法的,因爲線程是守護線程");
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
        System.out.println("Main線程執行完畢");
    }
}
運行結果是隻會打印出“Main線程執行完畢這一句話”


2.java.util.concurrent.atomic包提供了高效的,線程安全地更新一個變量方式。


2.1原子更新基本類型類:原子方式更新基本數據類型

AtomicBoolean,AtomicInteger,AtomicLong

2.2原子更新數組:通過原子的方式更新數組裏某個元素
AtomicBoolean,AtomicInteger,AtomicLong
2.3原子更新引用類型:原子更新多個變量
AtomicReference,AtomicReferenceFieldUpdater等
2.4原子更新字段類:如果需原子地更新某個類裏的某個字段時,就需要用到原子更新字段類
AtomicIntegerFieldUpdater等
當然,有人可能會問,什麼是原子方式更新呢?這裏不展開討論(因爲要涉及到內存模型,以及一個變量經歷加載和寫入等過程),僅貼兩段代碼,表示一下差異:
public class Main {
	// 哪怕這裏加了volatile也是沒用的,因爲volatile可以保證線程之間的可見性,並不能保證原子性
	static volatile int n = 0;
	static AtomicInteger m = new AtomicInteger(0);

	public static void main(String[] args) throws Exception {
		Thread t1 = new Thread() {
			@Override
			public void run() {
				// 這裏結束的數值不能設置得太小,因爲太小的話,速度太快,無法體現出效果
				for (int i = 0; i < 10000; i++) {
					// 你這裏寫成n = n + 1或者n +=1,也是一樣基本得不到正確結果的
					n++;
					m.incrementAndGet();
				}
			}
		};
		Thread t2 = new Thread() {
			@Override
			public void run() {
				for (int i = 0; i < 10000; i++) {
					n++;
					m.incrementAndGet();
				}
			}
		};

		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println("n" + n);
		System.out.println("m" + m.get());
	}
}


運行結果:

毫無疑問的,基本上你再怎麼運行,n都是得不到正確答案(偶爾會有正確答案,但是隨着結束值越大,出現的機率就越小),而m總是能得到正確答案,這就是原子性的一個體現,具體使用請看API。

3.等待多線程完成的CountDownLatch(更巧妙的Join)
可以指定,等待N個點(線程)完成(到達指定位置)後,阻塞的線程才繼續運行下去。也可以指定只阻塞多長一段時間。
TIP:需要注意的是,這個玩意只能使用一次。也就是說,下次還要使用時,必須重新new一個新的。
示例代碼(可與上面的用了join的例子對比一下,仔細想一想會發現,比使用Join要靈活和方便很多):
public class Main {
	static volatile CountDownLatch latch = new CountDownLatch(2);

	public static void main(String[] args) throws Exception {
		Thread t1 = new Thread() {
			@Override
			public void run() {
				System.out.println("線程1準備睡眠");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
				}
				System.out.println("線程1睡眠結束");
				latch.countDown();
			}
		};
		Thread t2 = new Thread() {
			@Override
			public void run() {
				System.out.println("線程2準備睡眠");
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
				}
				System.out.println("線程2睡眠結束");
				latch.countDown();
			}
		};

		t1.start();
		t2.start();
		System.out.println("main線程要準備被阻塞了");
		latch.await();
		System.out.println("main線程得以繼續執行");
	}
}
運行結果:

4.同步屏障CyclicBarrier
可以實現,直到N個線程都到達屏障,就是每個線程都調用await()的之後,所有調用了await()的線程纔不會被阻塞,纔會繼續運行下去,但是這個時候,並不能保證這N個線程的執行順序。
TIP:這個玩意有reset()方法,而且還能設置一個回調,在阻塞線程重新執行下去之前,可以先執行一下回調函數的裏面的方法,所以可以處理更復雜的業務場景。
可能說得不是很清楚,所以請看示例代碼:
// 爲了顯示時間方便,直接使用了new Date().toLocaleString(),但是這是一個被廢棄的方法,而且實際開發中,不應該這樣寫
public class Main {
	static CyclicBarrier cyclicBarrier = new CyclicBarrier(2, new Runnable() {
		public void run() {
			System.out.println("已經有兩個線程執行了await()方法了"
					+ new Date().toLocaleString());
		}
	});

	public static void main(String[] args) throws Exception {
		Thread t1 = new Thread() {
			@Override
			public void run() {
				System.out.println("線程1準備睡眠" + new Date().toLocaleString());
				try {
					Thread.sleep(1000);
					System.out.println("線程1睡眠結束" + new Date().toLocaleString());
					cyclicBarrier.await();
				} catch (Exception e) {
				}
				System.out.println("線程1await結束" + new Date().toLocaleString());

			}
		};
		Thread t2 = new Thread() {
			@Override
			public void run() {
				System.out.println("線程2準備睡眠" + new Date().toLocaleString());
				try {
					Thread.sleep(2000);
					System.out.println("線程2睡眠結束" + new Date().toLocaleString());
					cyclicBarrier.await();
				} catch (Exception e) {
				}
				System.out.println("線程2await結束" + new Date().toLocaleString());
			}
		};

		t1.start();
		t2.start();
		System.out.println("main線程要準備被阻塞了" + new Date().toLocaleString());
		System.out.println("main線程得以繼續執行" + new Date().toLocaleString());
	}
}

5.控制併發線程數Semaphore
簡單來說就是,最多同時只允許多少個線程獲取許可證,只有獲取到了許可證的,才能執行接下來的方法,其他的都在等待獲取許可證。
6.線程之間交換數據Exchanger
它提供了一個同步點,在這個同步點,兩個線程可以交換彼此的數據。如果第一個線程先執行exchange()方法,它會一直等待等到第二個線程也執行exchange()方法。
7.CachedThreadPool
這個線程池,使用的是一個沒有容量的SynchronousQueue,這是一個沒有容量的阻塞隊列,每個插入操作必須等待另一個線程的對應移除操作。而且,這個線程池的maximumPool是無界的,這就意味着,如果主線程提交任務的速度非常快的話,這個線程池是會一直不斷的創建新線程,最終有可能耗盡CPU和資源。
8.合理配置線程池
cpu密集型任務,應該配置儘可能小的線程,推薦配置cpu個數+1個線程數的線程池。由於IO密集型任務並不是一直在執行任務,則應配置儘可能多的線程,比如2*cpu個數。
9.避免死鎖的常見方法
  1. 避免一個線程同時獲取多把鎖
  2. 避免一個線程在鎖內同時佔用多個資源,儘量保證每個鎖只佔用一個資源
  3. 嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內部鎖機制。
  4. 對於數據庫鎖,加鎖和解鎖必須在一個數據庫連接裏,否則會出現解鎖失敗的情況。
下面給出一個死鎖的例子(當然還有更常見情況,比如一個線程裏獲取了多把鎖的):
public class Main {
	static Object object = new Object();
	public static void main(String[] args) throws Exception {
		methodA();
	}
	public static void methodA() {
		System.out.println("methodA想鎖住了object");
		synchronized (object) {
			System.out.println("methodA鎖住了object,並且準備調用methodB");
			methodB();
		}
	}
	public static void methodB() {
		// 因爲JAVA的鎖是可重入的,所以這裏另開一個線程
		Thread thread = new Thread() {
			@Override
			public void run() {
				System.out.println("methodB想鎖住了object");
				synchronized (object) {
					System.out.println("methodB鎖住了object,並且準備調用methodA");
					methodA();
				}
			}
		};
		thread.start();
		try {
			// methodB想等待線程執行完之後,才結束,但是它不知道methodA調用methodB的時候,已經鎖上了object,而methodB又在線程中企圖鎖object,於是就發送了死鎖。這是一種容易被忽略的死鎖情況
			thread.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
10.隊列同步器AbstractQueuedSynchronizer
這個玩意是用來構建鎖或者其他同步組件的基礎框架,用了一個int成員表示同步狀態,通過內置的先進先出隊列完成資源獲取線程的排隊工作。它是實現鎖的關鍵,簡化了鎖的實現方式。
//一個獨佔鎖的簡易實現(不可重入)
class Mutex implements Lock {
    private static class Sync extends AbstractQueuedSynchronizer {
        /**
         * 
         */
        private static final long serialVersionUID = -642099341499014369L;
        /**
         * 是否處於佔用狀態
         */
        @Override
        protected boolean isHeldExclusively() {
            // 1表示鎖被佔用
            return getState() == 1;
        }
        /**
         * 當狀態爲0的時候獲取鎖
         */
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }
        /**
         * 釋放鎖並將狀態設置爲0
         */
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        Condition newCondition() {
            return new ConditionObject();
        }
    }
    private final Sync sync = new Sync();
    public void lock() {
        sync.acquire(1);
    }
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }
    public boolean tryLock(long time, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }
    public void unlock() {
        sync.release(1);
    }
    public Condition newCondition() {
        return sync.newCondition();
    }
}
支持多個線程同時訪問的鎖(不可重入)
// 支持共享式訪問(即同一時刻支持多個線程訪問)
class ArbitraryCountLock implements Lock {

    private int lockCount = 2;
    private final Sync sync;

    public ArbitraryCountLock(int lockCount) {
        super();
        this.lockCount = lockCount;
        sync = new Sync(lockCount);
    }

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

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean tryLock() {
        return sync.tryAcquireShared(1) > 0 ? true : false;
    }

    public boolean tryLock(long time, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(time));
    }

    public void unlock() {
        sync.releaseShared(1);
    }

    public Condition newCondition() {
        return sync.newCondition();
    }

    public int getLockCount() {
        return lockCount;
    }

    private static class Sync extends AbstractQueuedSynchronizer {
        /**
         * 
         */
        private static final long serialVersionUID = -642099341499014369L;

        Sync(int count) {
            if (count < 1) {
                throw new RuntimeException("鎖的數量最小爲1");
            }
            setState(count);
        }

        // 在失敗時返回負值;如果共享模式下的獲取成功但其後續共享模式下的獲取不能成功,則返回
        // 0;如果共享模式下的獲取成功並且其後續共享模式下的獲取可能夠成功,則返回正值
        @Override
        protected int tryAcquireShared(int reduceCount) {
            while (true) {
                int current = getState();
                int newCount = current - reduceCount;
                if (newCount < 0 || compareAndSetState(current, newCount)) {
                    return newCount;
                }
            }
        }

        @Override
        protected boolean tryReleaseShared(int returnCount) {
            while (true) {
                int current = getState();
                int newCount = current + returnCount;
                if (compareAndSetState(current, newCount)) {
                    return true;
                }
            }
        }

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

11.LockSupport工具

static Object getBlocker(Thread t)
          返回提供給最近一次尚未解除阻塞的 park 方法調用的 blocker 對象,如果該調用不受阻塞,則返回 null。
static void park()
          爲了線程調度,禁用當前線程,除非許可可用。
static void park(Object blocker)
          爲了線程調度,在許可可用之前禁用當前線程。
static void parkNanos(long nanos)
          爲了線程調度禁用當前線程,最多等待指定的等待時間,除非許可可用。
static void parkNanos(Object blocker, long nanos)
          爲了線程調度,在許可可用前禁用當前線程,並最多等待指定的等待時間。
static void parkUntil(long deadline)
          爲了線程調度,在指定的時限前禁用當前線程,除非許可可用。
static void parkUntil(Object blocker, long deadline)
          爲了線程調度,在指定的時限前禁用當前線程,除非許可可用。
static void unpark(Thread thread)
          如果給定線程的許可尚不可用,則使其可用。
12.適合做緩存和定時任務調度的隊列DelayQueue
Delayed 元素的一個無界阻塞隊列,只有在延遲期滿時才能從中提取元素。該隊列的頭部 是延遲期滿後保存時間最長的 Delayed 元素。如果延遲都還沒有期滿,則隊列沒有頭部,並且 poll 將返回 null。當一個元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一個小於等於 0 的值時,將發生到期。即使無法使用 takepoll 移除未到期的元素,也不會將這些元素作爲正常元素對待。例如,size 方法同時返回到期和未到期元素的計數。此隊列不允許使用 null 元素。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章