- 1,JUC的簡介
- 2,volatile關鍵字與內存可見性
- 3,CAS算法
- 4,同步容器類ConcurrentHashMap
- 5,CountDownLatch閉鎖
- 6,Callable接口
- 7,同步鎖Lock
- 8,Condition 控制通信
- 9,生產者消費者案例
JUC的簡介
在Java5.0提供了java.util.concurrent(簡稱JUC)包,在此包中增加了在併發編程中很常用的實用工具類,用於定義類似於線程的自定義子系統,包括線程池、異步 IO 和輕量級任務框架。提供可調的、靈活的線程池。還提供了設計用於多線程上下文中的 Collection 實現等。
volatile關鍵字與內存可見性
-
內存可見性問題,當多個線程操作共享數據時,彼此不可見
-
volatile關鍵字:當多個線程進行操作共享數據時,可以保證內存中的數據可見(效率也低,但比synchronized的效率高,相較於synchronized是一種較爲輕量級的同步策略)jvm底層有重排序,用volatile修飾後就不可重排序了,使用代碼如下:
注意:
1,volatile不具備“互斥性”
2,volatile不能保證質量的“原子性”
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
new Thread(threadDemo).start();
//運行,發現flag=true並沒有進來,解決方法一:加鎖,效率低
while (true){
if(threadDemo.isFlag()){
System.out.println("----進來了-----");
break;
}
}
}
}
class ThreadDemo implements Runnable{
//解決方法二:加volatile關鍵詞
private volatile boolean flag = false;
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag =true;
System.out.println("flag = "+isFlag());
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
CAS算法
-
CAS (Compare-And-Swap) 是一種硬件對併發的支持,針對多處理器操作而設計的處理器中的一種特殊指令,用於管理對共享數據的併發訪問,保證數據的原子性。
-
CAS 是一種無鎖的非阻塞算法的實現。
-
CAS 包含了 3 個操作數:
1,需要讀寫的內存值 V
2,進行比較的預估值 A
3, 擬寫入的更新值 B -
當且僅當 V 的值等於 A 時,CAS 通過原子方式用新值 B 來更新 V 的值,否則不會執行任何操作。代碼實現如下:
public static void main(String[] args) {
AtomicDemo atomicDemo = new AtomicDemo();
for (int i = 0; i < 10; i++) {
new Thread(atomicDemo).start();
}
}
}
class AtomicDemo implements Runnable {
//使用volatile不可解決
// private volatile int number = 0;
//利用原子變量解決(和包裝類有點相似)
private AtomicInteger number = new AtomicInteger();
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getNumber());
}
public int getNumber() {
return number.getAndIncrement();
}
- 模擬cas算法
public class JucTest {
public static void main(String[] args) {
final CompareAndSwap cas = new CompareAndSwap();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int expectedValue = cas.get();
boolean a = cas.compareAndSet(expectedValue,(int)(Math.random()*101));
System.out.println(a);
}
}).start();
}
}
}
class CompareAndSwap{
private int value;
//獲取內存值
public synchronized int get(){
return value;
}
//比較,expectedValue預估值
public synchronized int compareAndSwap(int expectedValue, int newValue){
int oldValue = value;
if (oldValue == expectedValue){
this.value = newValue;
}
return oldValue;
}
//設置
public synchronized boolean compareAndSet(int expectedValue, int newValue){
return expectedValue == compareAndSwap(expectedValue,newValue);
}
}
同步容器類ConcurrentHashMap
-
ConcurrentHashMap 同步容器類是Java 5 增加的一個線程安全的哈希表。對與多線程的操作,介於 HashMap 與 Hashtable 之間。內部採用“鎖分段”機制替代 Hashtable 的獨佔鎖。進而提高性能
-
ConcurrentSkipListMap 通常優於同步的 TreeMap。當期望的讀數和遍歷遠遠大於列表的更新數時,CopyOnWriteArrayList 優於同步的 ArrayList
-
CopyOnWriteArrayList/CopyOnWriteArraySet:”寫入並複製“
注意:添加操作多時,效率低,因爲每次添加時都會進行復制,開銷非常的大。併發迭代操作多時可以選擇,代碼實現如下:
public static void main(String[] args) {
TestThread th = new TestThread();
for (int i = 0; i < 10; i++) {
new Thread(th).start();
}
}
class TestThread implements Runnable {
//java.util.ConcurrentModificationException
// private static List<String> list = Collections.synchronizedList(new ArrayList<>());
private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
static {
list.add("aa");
list.add("bb");
list.add("cc");
}
@Override
public void run() {
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
list.add("-aa-");
}
}
}
CountDownLatch閉鎖
- CountDownLatch:閉鎖,在完成某些運算時,只有其他所有線程運算全部完成,當前運算才能執行,代碼如下:
1, 確保某個計算在其需要的所有資源都被初始化之後才繼續執行。
2, 確保某個服務在其依賴的所有其他服務都已經啓動之後才啓動。
3,等待直到某個操作所有參與者都準備就緒再繼續執行。
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(5);
LatchDemo ld = new LatchDemo(latch);
Instant startTime = Instant.now();
for (int i = 0; i < 5; i++) {
new Thread(ld).start();
}
try {
latch.await();
} catch (InterruptedException e) {
}
Instant endTime = Instant.now();
System.out.println("耗費時間爲:" + Duration.between(startTime, endTime).toMillis() + "毫秒");
}
}
class LatchDemo implements Runnable {
private CountDownLatch latch;
public LatchDemo(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
synchronized (this) {
try {
for (int i = 0; i < 50000; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
} finally {
latch.countDown();
}
}
}
}
Callable接口
- 實現多線程的第三種方式:Callable 接口,Callable 接口類似於 Runnable,兩者都是爲那些其實例可能被另一個線程執行的類設計的。相較於實現Runnable接口的方式,方法可以有返回值,並且可以拋出異常。Callable 需要依賴FutureTask ,FutureTask 也可以用作閉鎖。
public static void main(String[] args) {
ThreadDemo td = new ThreadDemo();
//1,執行Callable方式,需要FutureTask實現類的支持,用於接收運算結果
FutureTask<Integer> result = new FutureTask<>(td);
new Thread(result).start();
Integer sum = null;
//2,接收線程運算後的結果
try {
sum = result.get(); //FutureTask可用於閉鎖
System.out.println(sum);
System.out.println("======================");
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class ThreadDemo implements Callable<Integer>{
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 1000; i++) {
sum += 1;
}
return sum;
}
}
同步鎖Lock
用於解決多線程安全問題的方式:
1,同步代碼塊(synchronized:隱式鎖)
2,同步方法(synchronized:隱式鎖)
3,同步鎖Lock(注意:是一個顯示鎖,需要通過lock()方法上鎖,必須通過unlock()方法進行釋放鎖),代碼如下:
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket,"12306").start();
new Thread(ticket,"飛豬").start();
new Thread(ticket,"攜程").start();
}
}
class Ticket implements Runnable{
private int tick = 100;//總票數
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();//加鎖
try {
if (tick > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+"完成售票,餘票爲:"+ --tick);
}else {
System.out.println("票已全部售完");
break;
}
}finally {
lock.unlock();
}
}
}
}
Condition 控制通信
-
Condition 接口描述了可能會與鎖有關聯的條件變量。這些變量在用法上與使用 Object.wait 訪問的隱式監視器類似,但提供了更強大的功能。需要特別指出的是,單個 Lock 可能與多個 Condition 對象關聯。爲了避免兼容性問題,Condition 方法的名稱與對應的 Object 版本中的不同。
-
在 Condition 對象中,與 wait、notify 和 notifyAll 方法對應的分別是await、signal 和 signalAll。
-
Condition 實例實質上被綁定到一個鎖上。要爲特定 Lock 實例獲得Condition 實例,請使用其 newCondition() 方法。代碼在下面生產者消費者案例中體現:
生產者消費者案例
- 爲了避免虛假喚醒問題,應該總是使用在循環中,代碼如下:
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk);
new Thread(productor, "生產者AA").start();
new Thread(productor, "生產者BB").start();
new Thread(consumer, "消費者C").start();
new Thread(consumer, "消費者D").start();
}
}
//店員
class Clerk {
private int product = 0;//數量
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//進貨
public void get() {
lock.lock();
try {
while (product >= 1) {
System.out.println("商品已滿!");
try {
condition.await();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + ":" + ++product);
condition.signalAll();
this.notifyAll();
} finally {
lock.unlock();
}
}
//賣貨
public void sale() {
lock.lock();
try {
while (product <= 0) {
System.out.println("暫無貨物!");
try {
condition.await();
} catch (InterruptedException e) {
}
}
System.out.println(Thread.currentThread().getName() + ":" + --product);
condition.signalAll();
} finally {
lock.unlock();
}
}
}
//生產者
class Productor implements Runnable {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.get();
}
}
}
//消費者
class Consumer implements Runnable {
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
clerk.sale();
}
}
}