1、多線程的實現(xiàn)
多線程的實現(xiàn)方案一:繼承Thread類,重寫run()方法
java是單繼承,也就是說繼承本身是很寶貴。
多線程的實現(xiàn)方案二:實現(xiàn)Runnable接口
多線程程序?qū)崿F(xiàn)方案三:實現(xiàn)Callable接口
2、synchronized關鍵字
https://segmentfault.com/a/1190000003810166
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadTest implements Runnable{
public static Integer num = 0;
@Override
public synchronized void run(){
for(int i = 0;i<100;i++)
System.out.println("hello" + num++);
}
public static void main(String[] args) throws{
ExecutorService pool = Executors.newFixedThreadPool(2);
pool.submit(new ThreadTest());
pool.submit(new ThreadTest());
pool.shutdown();
}
}
上面使用了同步方法,可以控制線程獨占執(zhí)行體對象,這樣在執(zhí)行的過程中就可以使得線程將執(zhí)行體上的任務一次性執(zhí)行完后退出鎖定狀態(tài),JVM再調(diào)度另一個線程進來一次性運行執(zhí)行體內(nèi)的任務。實際執(zhí)行時可以發(fā)現(xiàn),沒有synchronized關鍵字運行出來的數(shù)據(jù)會少很多。
3、yield關鍵字
https://blog.csdn.net/dabing69221/article/details/17426953
使當前線程從執(zhí)行狀態(tài)(運行狀態(tài))變?yōu)榭蓤?zhí)行態(tài)(就緒狀態(tài))。cpu會從眾多的可執(zhí)行態(tài)里選擇,也就是說,當前也就是剛剛的那個線程還是有可能會被再次執(zhí)行到的,并不是說一定會執(zhí)行其他線程而該線程在下一次中不會執(zhí)行到了。
程序示例:
public class ThreadTest extends Thread{
public static Integer num = 0;
@Override
public void run(){
for(int i = 0;i<50;i++) {
System.out.println("hello" + i);
if(i==30){
this.yield();
}
}
}
public static void main(String[] args) {
ThreadTest t1 = new ThreadTest();
ThreadTest t2 = new ThreadTest();
t1.start();
t2.start();
}
}
需要注意的是yield是 Thread的方法
4、wait()和notify()方法
http://www.itdecent.cn/p/f4454164c017
http://www.itdecent.cn/p/f7d4819b7b24
下面是一個簡單的示例程序,說明了wait和notify的基本用法,
package com.company;
import java.util.concurrent.TimeUnit;
public class WaitNotifyCase {
public static void main(String[] args){
final Object lock = new Object();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread a is waiting to get lock");
synchronized (lock){
try {
System.out.println("thread A get lock");
TimeUnit.SECONDS.sleep(1);
lock.wait();
System.out.println("wait end");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread b is waiting to get lock");
synchronized (lock){
try {
System.out.println("thread b get lock");
TimeUnit.SECONDS.sleep(5);
System.out.println("wait end");
}catch (InterruptedException e){
e.printStackTrace();
}
lock.notify();
System.out.println("notify method");
}
}
}).start();
}
}
注意的是需要調(diào)用同一個對象的wait和notify,線程執(zhí)行到wait時會被掛起,直到notify通知繼續(xù)運行。
上面兩個線程都有synchronized關鍵字,為什么需要?還得從wait和notify本質(zhì)出發(fā),線程執(zhí)行l(wèi)ock.wait()方法時,必須持有該lock對象的monitor,如果wait方法在synchronized代碼中執(zhí)行,該線程很顯然已經(jīng)持有了monitor。在執(zhí)行wait時需要獲得monitor,但是執(zhí)行完之后就會釋放,進入等待狀態(tài)。從這個角度看,這也是為什么這邊synchronized 代碼沒有執(zhí)行完而那邊卻可以進入notify函數(shù),notify在synchronized代碼中執(zhí)行,先獲取到對象monitor,然后通知,但wait并不會立刻就執(zhí)行,因為還需要獲得monitor,所以只有synchronized 代碼塊執(zhí)行完畢之后wait才會繼續(xù)執(zhí)行。
使用wait、notify有一個需要特別注意,那就是不能讓notify先執(zhí)行,這樣wait就會一直阻塞。需要嚴格控制兩者執(zhí)行順序。
5、錯誤加鎖
public class ThreadTest implements Runnable{
public static Integer i = 0;
static ThreadTest instace = new ThreadTest();
@Override
public void run(){
for(int j=0;j<100000;j++){
synchronized (i) { i++;}
}
}
public static void main(String[] args) throws InterruptedException{
Thread t1 = new Thread(instace);
Thread t2 = new Thread(instace);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
上面程序看起來沒有問題,但是打印出來的i確不是200000,得到了一個小很多的數(shù)字。涉及到一個自動裝包和拆包的問題。
執(zhí)行i++時,實際上是 i = Integer.valueOf(i.intValue()+1)
Integer.valueOf()實際上是一個工廠方法,會傾向于返回一個代表指定數(shù)值的Integer實例,創(chuàng)建一個新的對象(大于127),因此兩個線程每次加鎖可能加在不同的對象實例上。
6、volatile關鍵字
http://www.infoq.com/cn/articles/java-multi-thread-volatile
http://www.techug.com/post/java-volatile-keyword.html
被volatile修飾的共享變量,就具有了以下兩點特性:
- 保證了不同線程對該變量操作的內(nèi)存可見性;
- 禁止指令重排序
7、ReentrantLock
參考:https://my.oschina.net/hosee/blog/607677
ReentrantLock可以看作是synchronized的加強版,之前版本,ReentrantLock的性能要好于synchronized,由于對JVM進行了優(yōu)化,現(xiàn)在的JDK版本中,兩者性能是不相上下的。如果是簡單的實現(xiàn),不要刻意去使用ReentrantLock。
相比于synchronized,ReentrantLock在功能上更加豐富,它具有可重入、可中斷、可限時、公平鎖等特點。
簡單示例:
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);
}
}
ReentrantLock是重入鎖,可以反復得到相同的一把鎖,它有一個與鎖相關的獲取計數(shù)器,如果擁有鎖的某個線程再次得到鎖,那么獲取計數(shù)器就加1,然后鎖需要被釋放兩次才能獲得真正釋放。如下:
lock.lock();
lock.lock();
try
{
i++;
}
finally
{
lock.unlock();
lock.unlock();
}
如果只是簡單的互斥鎖,上面這種情況就會發(fā)生死鎖,因為鎖只允許依次進入。
同樣,synchronize也是可重入鎖。
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(); // 調(diào)用自己類中其他的synchronized方法
}
private synchronized void doAnotherThing() {
super.doSomething(); // 調(diào)用父類的synchronized方法
System.out.println("3child.doAnotherThing()");
}
@Override
public void run() {
child.doSomething();
}
}
class Father {
public synchronized void doSomething() {
System.out.println("2father.doSomething()");
}
}
可以看到一個線程進入不同的 synchronized方法,是不會釋放之前得到的鎖的。
ReentrantLock還支持可中斷,定時和公平方式,功能比較豐富。
8、條件變量
package com.company;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest implements Runnable{
public static ReentrantLock lock = new ReentrantLock();
public static Condition condition = lock.newCondition();
@Override
public void run(){
try{
lock.lock();
System.out.println("locked");
condition.await();
System.out.println("thread is going");
}catch (InterruptedException e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException{
ThreadTest t1 = new ThreadTest();
Thread th = new Thread(t1);
th.start();
Thread.sleep(2000);
System.out.println("after sleep");
lock.lock();
condition.signal();
Thread.sleep(1000);
System.out.println("before unlock");
lock.unlock();
}
}
9、信號量
對于鎖來說,它是互斥的排他的。意思就是,只要我獲得了鎖,沒人能再獲得了。
而對于Semaphore來說,它允許多個線程同時進入臨界區(qū)。可以認為它是一個共享鎖,但是共享的額度是有限制的,額度用完了,其他沒有拿到額度的線程還是要阻塞在臨界區(qū)外。當額度為1時,就相等于lock。
package com.company;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class semaphoreTest implements Runnable{
final Semaphore semp = new Semaphore(5);
@Override
public void run(){
try{
semp.acquire();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getId()+":done");
semp.release();
}catch (InterruptedException e){
e.printStackTrace();
}
}
public static void main(String[] args){
ExecutorService exec = Executors.newFixedThreadPool(20);
final semaphoreTest demo = new semaphoreTest();
for(int i=0;i<20;i++){
exec.submit(demo);
}
}
}
11、讀寫鎖
ReadWriteLock是區(qū)分功能的鎖。讀和寫是兩種不同的功能,讀-讀不互斥,讀-寫互斥,寫-寫互斥。適用于讀頻繁的場景。
這樣的設計是并發(fā)量提高了,又保證了數(shù)據(jù)安全。
private static ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
private static Lock readLock = readWriteLock.readLock();
private static Lock writeLock = readWriteLock.writeLock();
12、CountDownLatch和CyclicBarrier
倒數(shù)計數(shù)器CountDownLatch
表示執(zhí)行某任務前,其他指定任務必須完成。典型的場景就是火箭發(fā)射。在火箭發(fā)射前,為了保證萬無一失,往往還要進行各項設備、儀器的檢查。 只有等所有檢查完畢后,引擎才能點火。這種場景就非常適合使用CountDownLatch。它可以使得點火線程,等待所有檢查線程全部完工后,再執(zhí)行。
static final CountDownLatch end = new CountDownLatch(10);
end.countDown();
end.await();
示例
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test implements Runnable
{
static final CountDownLatch countDownLatch = new CountDownLatch(10);
static final Test t = new Test();
@Override
public void run()
{
try
{
Thread.sleep(2000);
System.out.println("complete");
countDownLatch.countDown();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException
{
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++)
{
executorService.execute(t);
}
countDownLatch.await();
System.out.println("end");
executorService.shutdown();
}
}
CyclicBarrier
http://www.itdecent.cn/p/424374d71b67
和CountDownLatch相似,也是等待某些線程都做完以后再執(zhí)行。與CountDownLatch區(qū)別在于這個計數(shù)器可以反復使用。比如,假設我們將計數(shù)器設置為10。那么湊齊第一批1 0個線程后,計數(shù)器就會歸零,然后接著湊齊下一批10個線程
public CyclicBarrier(int parties, Runnable barrierAction)
barrierAction就是當計數(shù)器一次計數(shù)完成后,系統(tǒng)會執(zhí)行的動作
await()
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();
}
}
}
上面每個線程有兩個await,線程遇到await會阻塞,當指定數(shù)量的線程就緒,所有線程繼續(xù)運行。同時會觸發(fā) CyclicBarrier(n, new BarrierRun(flag, n))中第二個參數(shù)指定的線程。
CyclicBarrier比CountDownLatch更復雜,功能更強大。
13、LockSupport
LockSupport 提供線程阻塞原語
用法如下:
LockSupport.park();
LockSupport.unpark(t1);
回顧Thread中suspend,resume,stop方法,
suspend,使線程暫停,但是不會釋放類似鎖這樣的資源。
resume,使線程恢復,如果之前沒有使用suspend暫停線程,則不起作用。
stop,停止當前線程。不會保證釋放當前線程占有的資源。
suspend和resume也能提供暫停和繼續(xù)的,但是如果resume發(fā)生在suspend之前就會發(fā)生暫停線程得不到繼續(xù),而這種情況在多線程環(huán)境下很容易發(fā)生。
但LockSupport下的park和unpark就不會發(fā)生這樣的情況。
import java.util.concurrent.locks.LockSupport;
public class Test
{
static Object u = new Object();
static TestSuspendThread t1 = new TestSuspendThread("t1");
static TestSuspendThread t2 = new TestSuspendThread("t2");
public static class TestSuspendThread extends Thread
{
public TestSuspendThread(String name)
{
setName(name);
}
@Override
public void run()
{
synchronized (u)
{
System.out.println("in " + getName());
//Thread.currentThread().suspend();
LockSupport.park();
}
}
}
public static void main(String[] args) throws InterruptedException
{
t1.start();
Thread.sleep(100);
t2.start();
// t1.resume();
// t2.resume();
LockSupport.unpark(t1);
LockSupport.unpark(t2);
t1.join();
t2.join();
}
}