目錄
一、建立線程
1.1繼承Thread類
1.1.1建立ThreadTest繼承Thread類
1.1.2編寫測試類
1.2實作Runnable接口
1.2.1編寫Tickets類
1.2.2編寫RunnableTest類實作Runnable接口
1.2.3編寫測試類MainTest
1.3實作Callable接口結合FutureTask類使用
1.3.1編寫Tickets類
1.3.2編寫CallableTest類
1.3.3編寫測試類
注:線程的執行過程
注:call()方法傳回值的擷取
1.4線程池建立線程使用
二、synchronized、lock
2.1 synchronized 三種用法
2.1.1 synchronized 修飾執行個體方法
2.1.2 synchronized 修飾靜态方法
2.1.3 synchronized 修飾代碼塊
注:synchronized 總結
注:synchronized的實作原理請參考下面部落格
2.2 Lock
2.2.1 synchronized缺陷以及lock介紹
2.2.2 lock使用
三、線程的狀态
3.1狀态圖解
3.2線程的常用方法
一、建立線程
建立線程的三種方式:繼承Thread類、實作Runnable接口、實作Callable接口結合FutureTask類使用
四種方式實作多線程賣票;
話不多說,直接幹
1.1繼承Thread類
1.1.1建立ThreadTest繼承Thread類
package thread;
/**
* @author ppc
*
*/
public class ThreadTest extends Thread {
//總票數20
static Integer count = 20;
@Override
public void run() {
while(count>0) {
synchronized (count) {//對餘票加鎖
if(count>0) {
System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+--count+"票。");
}
}
try {
sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(count==0) {
System.out.println("票已賣完");
}
}
public ThreadTest() {
super();
// TODO Auto-generated constructor stub
}
public ThreadTest(String name) {
super(name);
// TODO Auto-generated constructor stub
}
}
1.1.2編寫測試類
package thread;
/**
* @author ppc
*
*/
public class MainTest {
public static void main(String[] args) {
//三個視窗同時賣票
ThreadTest tt1 = new ThreadTest("視窗一");
ThreadTest tt2 = new ThreadTest("視窗二");
ThreadTest tt3 = new ThreadTest("視窗三");
//啟動線程
tt1.start();
tt2.start();
tt3.start();
}
}
執行結果:
視窗一賣了一張票,還剩19票。
視窗三賣了一張票,還剩18票。
視窗二賣了一張票,還剩17票。
視窗一賣了一張票,還剩16票。
視窗二賣了一張票,還剩15票。
視窗三賣了一張票,還剩14票。
視窗一賣了一張票,還剩13票。
視窗二賣了一張票,還剩12票。
視窗三賣了一張票,還剩11票。
視窗一賣了一張票,還剩10票。
視窗二賣了一張票,還剩9票。
視窗三賣了一張票,還剩8票。
視窗一賣了一張票,還剩7票。
視窗二賣了一張票,還剩6票。
視窗三賣了一張票,還剩5票。
視窗一賣了一張票,還剩4票。
視窗二賣了一張票,還剩3票。
視窗三賣了一張票,還剩2票。
視窗一賣了一張票,還剩1票。
視窗二賣了一張票,還剩0票。
票已賣完
票已賣完
票已賣完
1.2實作Runnable接口
1.2.1編寫Tickets類
package thread;
/**
* @author ppc
*
*/
public class Tickets {
//總票數20張
int count = 20;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
public Tickets(int count) {
super();
this.count = count;
}
public Tickets() {
super();
// TODO Auto-generated constructor stub
}
}
1.2.2編寫RunnableTest類實作Runnable接口
package thread;
/**
* @author ppc
*
*/
public class RunnableTest implements Runnable {
//Tickets tickets;//票數對象
Integer count = 20;
synchronized void sale() {
if(count>0) {
System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+count+"票。");
}
}
@Override
public void run() {
while(count>0) {
synchronized (count) {
if(count>0) {
System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+ --count+"票。");
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(count==0){
System.out.println(Thread.currentThread().getName()+"票已賣完。");
}
}
public RunnableTest() {
super();
// TODO Auto-generated constructor stub
}
}
1.2.3編寫測試類MainTest
package thread;
/**
* @author ppc
*
*/
public class MainTest {
public static void main(String[] args) {
RunnableTest rt = new RunnableTest();
new Thread(rt,"視窗4").start();
new Thread(rt,"視窗5").start();
new Thread(rt,"視窗6").start();
}
}
執行結果:
視窗4賣了一張票,還剩19票。
視窗5賣了一張票,還剩18票。
視窗6賣了一張票,還剩17票。
視窗4賣了一張票,還剩16票。
視窗5賣了一張票,還剩15票。
視窗6賣了一張票,還剩14票。
視窗6賣了一張票,還剩13票。
視窗4賣了一張票,還剩12票。
視窗5賣了一張票,還剩11票。
視窗4賣了一張票,還剩10票。
視窗5賣了一張票,還剩9票。
視窗6賣了一張票,還剩8票。
視窗6賣了一張票,還剩7票。
視窗4賣了一張票,還剩6票。
視窗5賣了一張票,還剩5票。
視窗5賣了一張票,還剩4票。
視窗4賣了一張票,還剩3票。
視窗6賣了一張票,還剩2票。
視窗4賣了一張票,還剩1票。
視窗5賣了一張票,還剩0票。
視窗4票已賣完。
視窗6票已賣完。
視窗5票已賣完。
1.3實作Callable接口結合FutureTask類使用
1.3.1編寫Tickets類
參考1.2.1中的Tickets類
1.3.2編寫CallableTest類
package thread;
import java.util.concurrent.Callable;
/**
* @author ppc
*
*/
public class CallableTest implements Callable<Object> {
Integer count = 20;//票數對象
@Override
public Object call() throws Exception {
while(count>0) {
synchronized (count) {
if(count>0) {
System.out.println(Thread.currentThread()+"賣了一張票,還剩"+ --count+"票。");
}
}
Thread.sleep(100);
}
if(count == 0) {
System.out.println(Thread.currentThread()+"票已賣完");
}
//可以有傳回值
return "tickets";
}
public CallableTest() {
super();
// TODO Auto-generated constructor stub
}
}
1.3.3編寫測試類
public static void main(String[] args) {
CallableTest ct = new CallableTest();
FutureTask<Object> ft1 = new FutureTask<>(ct);
FutureTask<Object> ft2 = new FutureTask<>(ct);
FutureTask<Object> ft3 = new FutureTask<>(ct);
new Thread(ft1,"視窗7").start();
new Thread(ft2,"視窗8").start();
new Thread(ft3,"視窗9").start();
// FutureTask<Object> ft1 = new FutureTask<>(ct);
//
// new Thread(ft1,"視窗7").start();不能寫成這樣,如果寫成這樣隻有一個線程執行因為FutureTask中的run()方法執行的時候會判斷FutureTask的屬性state的值,隻有是0的時候才會往下執行調callable的call方法
// new Thread(ft1,"視窗8").start();
// new Thread(ft1,"視窗9").start();
try {
System.out.println( ft1.get());//可以接受call()的傳回參數
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
執行結果:
Thread[視窗7,5,main]賣了一張票,還剩19票。
Thread[視窗9,5,main]賣了一張票,還剩18票。
Thread[視窗8,5,main]賣了一張票,還剩17票。
Thread[視窗9,5,main]賣了一張票,還剩16票。
Thread[視窗8,5,main]賣了一張票,還剩15票。
Thread[視窗7,5,main]賣了一張票,還剩14票。
Thread[視窗9,5,main]賣了一張票,還剩13票。
Thread[視窗7,5,main]賣了一張票,還剩12票。
Thread[視窗8,5,main]賣了一張票,還剩11票。
Thread[視窗8,5,main]賣了一張票,還剩10票。
Thread[視窗7,5,main]賣了一張票,還剩9票。
Thread[視窗9,5,main]賣了一張票,還剩8票。
Thread[視窗8,5,main]賣了一張票,還剩7票。
Thread[視窗9,5,main]賣了一張票,還剩6票。
Thread[視窗7,5,main]賣了一張票,還剩5票。
Thread[視窗8,5,main]賣了一張票,還剩4票。
Thread[視窗7,5,main]賣了一張票,還剩3票。
Thread[視窗9,5,main]賣了一張票,還剩2票。
Thread[視窗8,5,main]賣了一張票,還剩1票。
Thread[視窗9,5,main]賣了一張票,還剩0票。
Thread[視窗7,5,main]票已賣完
tickets
Thread[視窗8,5,main]票已賣完
Thread[視窗9,5,main]票已賣完
注:線程的執行過程
start()——> start0()(本地方法)——> run()
參考源碼(截選了部分源碼)
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
/**
* If this thread was constructed using a separate
* <code>Runnable</code> run object, then that
* <code>Runnable</code> object's <code>run</code> method is called;
* otherwise, this method does nothing and returns.
* <p>
* Subclasses of <code>Thread</code> should override this method.
*
* @see #start()
* @see #stop()
* @see #Thread(ThreadGroup, Runnable, String)
*/
@Override
public void run() {
if (target != null) {
target.run();
}
}
注:call()方法傳回值的擷取
執行run()方法,調用call()方法,設定outcome參數
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;//構造方法傳進的Callable對象
if (c != null && state == NEW) {//1.3.3的原因
V result;
boolean ran;
try {
result = c.call();//調用call()方法
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);//設定outcome參數
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
設定outcome的set()方法
/**
* Sets the result of this future to the given value unless
* this future has already been set or has been cancelled.
*
* <p>This method is invoked internally by the {@link #run} method
* upon successful completion of the computation.
*
* @param v the value
*/
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion();
}
}
FutureTask類的report()方法
/**
* Returns result or throws exception for completed task.
*
* @param s completed state value
*/
@SuppressWarnings("unchecked")
private V report(int s) throws ExecutionException {
Object x = outcome;//将call()方法的傳回值賦給x
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
FutureTask類的get()方法
/**
* @throws CancellationException {@inheritDoc}
*/
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);//傳回report()方法中的傳回值
}
1.4線程池建立線程使用
package thread;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* @author ppc
*
*/
public class MainTest {
//線程池使用
public static void main(String[] args) {
ExecutorService es = Executors.newCachedThreadPool();
Tickets t = new Tickets();
List<Future> lf = new ArrayList<Future>();//用來存放call方法的傳回值
for(int i=0;i<5;i++) {
//es.execute(new RunnableTest(t)); //實作runnable接口的參數,用execute方法執行
Future f = es.submit(new CallableTest(t));//實作Callable接口的參數,用execute方法執行
lf.add(f);
}
es.shutdown();
try {
for (Future future : lf) {
System.out.println(future.get());
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
執行結果:
Thread[pool-1-thread-1,5,main]賣了一張票,還剩19票。
Thread[pool-1-thread-5,5,main]賣了一張票,還剩18票。
Thread[pool-1-thread-4,5,main]賣了一張票,還剩17票。
Thread[pool-1-thread-3,5,main]賣了一張票,還剩16票。
Thread[pool-1-thread-2,5,main]賣了一張票,還剩15票。
Thread[pool-1-thread-1,5,main]賣了一張票,還剩14票。
Thread[pool-1-thread-5,5,main]賣了一張票,還剩13票。
Thread[pool-1-thread-4,5,main]賣了一張票,還剩12票。
Thread[pool-1-thread-3,5,main]賣了一張票,還剩11票。
Thread[pool-1-thread-2,5,main]賣了一張票,還剩10票。
Thread[pool-1-thread-4,5,main]賣了一張票,還剩9票。
Thread[pool-1-thread-5,5,main]賣了一張票,還剩8票。
Thread[pool-1-thread-1,5,main]賣了一張票,還剩7票。
Thread[pool-1-thread-3,5,main]賣了一張票,還剩6票。
Thread[pool-1-thread-2,5,main]賣了一張票,還剩5票。
Thread[pool-1-thread-5,5,main]賣了一張票,還剩4票。
Thread[pool-1-thread-1,5,main]賣了一張票,還剩3票。
Thread[pool-1-thread-4,5,main]賣了一張票,還剩2票。
Thread[pool-1-thread-3,5,main]賣了一張票,還剩1票。
Thread[pool-1-thread-2,5,main]賣了一張票,還剩0票。
Thread[pool-1-thread-5,5,main]票已賣完
Thread[pool-1-thread-4,5,main]票已賣完
Thread[pool-1-thread-1,5,main]票已賣完
tickets
Thread[pool-1-thread-3,5,main]票已賣完
Thread[pool-1-thread-2,5,main]票已賣完
tickets
tickets
tickets
tickets
二、synchronized、lock
2.1 synchronized 三種用法
2.1.1 synchronized 修飾執行個體方法
package thread;
/**
* @author ppc
*
*/
public class RunnableTest implements Runnable {
Integer count = 20;
synchronized void sale() {
if(count>0) {
System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+--count+"票。");
}
}
@Override
public void run() {
while(count>0) {
sale();//鎖住的是目前對象
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(count==0){
System.out.println(Thread.currentThread().getName()+"票已賣完。");
}
}
public RunnableTest() {
super();
// TODO Auto-generated constructor stub
}
}
測試類:
package thread;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* @author ppc
*
*/
public class MainTest {
//RunnableTest
public static void main(String[] args) {
RunnableTest rt = new RunnableTest();//線程執行的時候,鎖住的是rt對象,一條線程擷取rt對象的鎖,執行完sale方法,釋放鎖,其他線程才可以擷取此對象的鎖執行sale方法
new Thread(rt,"視窗4").start();
new Thread(rt,"視窗5").start();
new Thread(rt,"視窗6").start();
}
}
執行結果:
視窗4賣了一張票,還剩19票。
視窗6賣了一張票,還剩18票。
視窗5賣了一張票,還剩17票。
視窗5賣了一張票,還剩16票。
視窗4賣了一張票,還剩15票。
視窗6賣了一張票,還剩14票。
視窗4賣了一張票,還剩13票。
視窗5賣了一張票,還剩12票。
視窗6賣了一張票,還剩11票。
視窗5賣了一張票,還剩10票。
視窗4賣了一張票,還剩9票。
視窗6賣了一張票,還剩8票。
視窗4賣了一張票,還剩7票。
視窗6賣了一張票,還剩6票。
視窗5賣了一張票,還剩5票。
視窗6賣了一張票,還剩4票。
視窗5賣了一張票,還剩3票。
視窗4賣了一張票,還剩2票。
視窗5賣了一張票,還剩1票。
視窗6賣了一張票,還剩0票。
視窗6票已賣完。
視窗5票已賣完。
視窗4票已賣完。
2.1.2 synchronized 修飾靜态方法
編寫線程類
package thread;
/**
* @author ppc
*
*/
public class ThreadTest extends Thread {
//總票數20
static Integer count = 20;
synchronized static void sale() {//鎖住的是目前類
if(count>0) {
System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+--count+"票。");
}
}
@Override
public void run() {
while(count>0) {
sale();
try {
sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(count==0) {
System.out.println("票已賣完");
}
}
public ThreadTest() {
super();
// TODO Auto-generated constructor stub
}
public ThreadTest(String name) {
super(name);
// TODO Auto-generated constructor stub
}
}
測試方法參考:
package thread;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* @author ppc
*
*/
public class MainTest {
//ThreadTest
public static void main(String[] args) {
//三個視窗同時賣票
ThreadTest tt1 = new ThreadTest("視窗一");
ThreadTest tt2 = new ThreadTest("視窗二");
ThreadTest tt3 = new ThreadTest("視窗三");
//啟動線程
tt1.start();
tt2.start();
tt3.start();
}
}
2.1.3 synchronized 修飾代碼塊
編寫線程類
package thread;
/**
* @author ppc
*
*/
public class ThreadTest extends Thread {
//總票數20
static Integer count = 20;
@Override
public void run() {
while(count>0) {
synchronized (count) {//對餘票加鎖
//這個鎖住的是靜态變量count,當線程操作count時必須取得count鎖,線程操作完此代碼塊會釋放count鎖
//也可以用this(synchronized (this) {...}) 鎖住的是目前對象(本案例就是ThreadTest類的目前對象)
if(count>0) {
System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+--count+"票。");
}
}
try {
sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(count==0) {
System.out.println("票已賣完");
}
}
public ThreadTest() {
super();
// TODO Auto-generated constructor stub
}
public ThreadTest(String name) {
super(name);
// TODO Auto-generated constructor stub
}
}
測試方法參考:1.1.2
注:synchronized 總結
1.當synchronized 注釋執行個體方法時,一個線程通路一個對象其中一個執行個體synchronized方法,其他線程不得通路該對象的
synchronized的執行個體方法,可以通路該對象的靜态synchronized 的方法或者非synchronized方法;
2.多個線程通路同一類多個執行個體對象的同一synchronized靜态方法,是互斥的,因為synchronized修飾靜态方法時鎖的是目前類的class對象鎖,必須一個一個線程執行該synchronized靜态方法(參考2.1.2)
3.synchronized修飾代碼塊時,synchronized(object){....} 當線程通路此代碼塊時必須持有object對象鎖,否則等待,
synchronized(this){....} 鎖的是本身對象,如果線程通路此代碼塊必須持有本對象的對象鎖,否則等待,synchronized(Object.class) {...}鎖的是Object類,如果線程通路此代碼塊必須持有Object類的對象鎖
注:synchronized的實作原理請參考下面部落格
https://blog.csdn.net/javazejian/article/details/72828483
2.2 Lock
2.2.1 synchronized缺陷以及lock介紹
1.synchronized的缺陷
synchronized是java中的一個關鍵字,也就是說是Java語言内置的特性。那麼為什麼會出現Lock呢?
在上面一篇文章中,我們了解到如果一個代碼塊被synchronized修飾了,當一個線程擷取了對應的鎖,并執行該代碼塊時,其他線程便隻能一直等待,等待擷取鎖的線程釋放鎖,而這裡擷取鎖的線程釋放鎖隻會有兩種情況:
1)擷取鎖的線程執行完了該代碼塊,然後線程釋放對鎖的占有;
2)線程執行發生異常,此時JVM會讓線程自動釋放鎖。
那麼如果這個擷取鎖的線程由于要等待IO或者其他原因(比如調用sleep方法)被阻塞了,但是又沒有釋放鎖,其他線程便隻能幹巴巴地等待,試想一下,這多麼影響程式執行效率。
是以就需要有一種機制可以不讓等待的線程一直無期限地等待下去(比如隻等待一定的時間或者能夠響應中斷),通過Lock就可以辦到。
再舉個例子:當有多個線程讀寫檔案時,讀操作和寫操作會發生沖突現象,寫操作和寫操作會發生沖突現象,但是讀操作和讀操作不會發生沖突現象。
但是采用synchronized關鍵字來實作同步的話,就會導緻一個問題:
如果多個線程都隻是進行讀操作,是以當一個線程在進行讀操作時,其他線程隻能等待無法進行讀操作。
是以就需要一種機制來使得多個線程都隻是進行讀操作時,線程之間不會發生沖突,通過Lock就可以辦到。
另外,通過Lock可以知道線程有沒有成功擷取到鎖。這個是synchronized無法辦到的。
總結一下,也就是說Lock提供了比synchronized更多的功能。但是要注意以下幾點:
1)Lock不是Java語言内置的,synchronized是Java語言的關鍵字,是以是内置特性。Lock是一個類,通過這個類可以實作同步通路;
2)Lock和synchronized有一點非常大的不同,采用synchronized不需要使用者去手動釋放鎖,當synchronized方法或者synchronized代碼塊執行完之後,系統會自動讓線程釋放對鎖的占用;而Lock則必須要使用者去手動釋放鎖,如果沒有主動釋放鎖,就有可能導緻出現死鎖現象。
2.lock方法介紹
下面我們就來探讨一下java.util.concurrent.locks包中常用的類和接口。
1.Lock
首先要說明的就是Lock,通過檢視Lock的源碼可知,Lock是一個接口:
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
下面來逐個講述Lock接口中每個方法的使用,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用來擷取鎖的。unLock()方法是用來釋放鎖的。newCondition()這個方法暫且不在此講述,會在後面的線程協作一文中講述。
在Lock中聲明了四個方法來擷取鎖,那麼這四個方法有何差別呢?
1)lock()
首先lock()方法是平常使用得最多的一個方法,就是用來擷取鎖。如果鎖已被其他線程擷取,則進行等待。
由于在前面講到如果采用Lock,必須主動去釋放鎖,并且在發生異常時,不會自動釋放鎖。是以一般來說,使用Lock必須在try{}catch{}塊中進行,并且将釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。通常使用Lock來進行同步的話,是以下面這種形式去使用的:
Lock lock = ...;
lock.lock();
try{
//處理任務
}catch(Exception ex){
}finally{
lock.unlock(); //釋放鎖
}
2)tryLock()
tryLock()方法是有傳回值的,它表示用來嘗試擷取鎖,如果擷取成功,則傳回true,如果擷取失敗(即鎖已被其他線程擷取),則傳回false,也就說這個方法無論如何都會立即傳回。在拿不到鎖時不會一直在那等待。
3)tryLock(long time, TimeUnit unit)
tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,隻不過差別在于這個方法在拿不到鎖時會等待一定的時間,在時間期限之内如果還拿不到鎖,就傳回false。如果如果一開始拿到鎖或者在等待期間内拿到了鎖,則傳回true。
是以,一般情況下通過tryLock來擷取鎖時是這樣使用的:
Lock lock = ...;
if(lock.tryLock()) {
try{
//處理任務
}catch(Exception ex){
}finally{
lock.unlock(); //釋放鎖
}
}else {
//如果不能擷取鎖,則直接做其他事情
}
lockInterruptibly()方法比較特殊,當通過這個方法去擷取鎖時,如果線程正在等待擷取鎖,則這個線程能夠響應中斷,即中斷線程的等待狀态。也就使說,當兩個線程同時通過lock.lockInterruptibly()想擷取某個鎖時,假若此時線程A擷取到了鎖,而線程B隻有在等待,那麼對線程B調用threadB.interrupt()方法能夠中斷線程B的等待過程。
由于lockInterruptibly()的聲明中抛出了異常,是以lock.lockInterruptibly()必須放在try塊中或者在調用lockInterruptibly()的方法外聲明抛出InterruptedException。
是以lockInterruptibly()一般的使用形式如下:
public void method() throws InterruptedException {
lock.lockInterruptibly();
try {
//.....
}
finally {
lock.unlock();
}
}
注意,當一個線程擷取了鎖之後,是不會被interrupt()方法中斷的。因為本身在前面的文章中講過單獨調用interrupt()方法不能中斷正在運作過程中的線程,隻能中斷阻塞過程中的線程。
是以當通過lockInterruptibly()方法擷取某個鎖時,如果不能擷取到,隻有進行等待的情況下,是可以響應中斷的。
而用synchronized修飾的話,當一個線程處于等待某個鎖的狀态,是無法被中斷的,隻有一直等待下去。
2.ReentrantLock
ReentrantLock,意思是“可重入鎖”,關于可重入鎖的概念在下一節講述。ReentrantLock是唯一實作了Lock接口的類,并且ReentrantLock提供了更多的方法。下面通過一些執行個體看具體看一下如何使用ReentrantLock。
2.2.2 lock使用
加個鎖沒有解鎖
package thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest extends Thread{
private Lock lock = new ReentrantLock();
//總票數20
static Integer count = 20;
@Override
public void run() {
while(count>0) {
lock.lock();
try {
System.out.println(Thread.currentThread()+"獲得對象鎖");
if(count>0) {
System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+--count+"票。");
}
sleep(100);
} catch (InterruptedException e) {
System.out.println("報錯了");
}finally {
//lock.unlock();
System.out.println(Thread.currentThread()+"釋放對象鎖");
}
}
if(count==0) {
System.out.println("票已賣完");
}
}
public LockTest() {
super();
// TODO Auto-generated constructor stub
}
public LockTest(String name) {
super(name);
// TODO Auto-generated constructor stub
}
}
測試類
package thread;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/**
* @author ppc
*
*/
public class MainTest {
public static void main(String[] args) {
LockTest lt1 =new LockTest();
new Thread(lt1).start();
new Thread(lt1).start();
new Thread(lt1).start();
}
}
執行結果:
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩19票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩18票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩17票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩16票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩15票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩14票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩13票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩12票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩11票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩10票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩9票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩8票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩7票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩6票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩5票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩4票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩3票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩2票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩1票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩0票。
Thread[Thread-1,5,main]釋放對象鎖
票已賣完
從結果可以看出目前線程獲得了鎖,沒有解鎖,是以隻有這個線程執行,其他線程沒有獲得鎖,無法執行
加上解鎖
package thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest extends Thread{
private Lock lock = new ReentrantLock();
//總票數20
Integer count = 20;
@Override
public void run() {
while(count>0) {
lock.lock();
try {
System.out.println(Thread.currentThread()+"獲得對象鎖");
if(count>0) {
System.out.println(Thread.currentThread().getName()+"賣了一張票,還剩"+--count+"票。");
}
sleep(100);
} catch (InterruptedException e) {
System.out.println("報錯了");
}finally {
lock.unlock();
System.out.println(Thread.currentThread()+"釋放對象鎖");
}
}
if(count==0) {
System.out.println("票已賣完");
}
}
public LockTest() {
super();
// TODO Auto-generated constructor stub
}
public LockTest(String name) {
super(name);
// TODO Auto-generated constructor stub
}
}
測試類如上
執行結果:
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩19票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-3,5,main]獲得對象鎖
Thread-3賣了一張票,還剩18票。
Thread[Thread-3,5,main]釋放對象鎖
Thread[Thread-2,5,main]獲得對象鎖
Thread-2賣了一張票,還剩17票。
Thread[Thread-2,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩16票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-3,5,main]獲得對象鎖
Thread-3賣了一張票,還剩15票。
Thread[Thread-3,5,main]釋放對象鎖
Thread[Thread-2,5,main]獲得對象鎖
Thread-2賣了一張票,還剩14票。
Thread[Thread-2,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩13票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-3,5,main]獲得對象鎖
Thread-3賣了一張票,還剩12票。
Thread[Thread-3,5,main]釋放對象鎖
Thread[Thread-2,5,main]獲得對象鎖
Thread-2賣了一張票,還剩11票。
Thread[Thread-2,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩10票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-3,5,main]獲得對象鎖
Thread-3賣了一張票,還剩9票。
Thread[Thread-3,5,main]釋放對象鎖
Thread[Thread-2,5,main]獲得對象鎖
Thread-2賣了一張票,還剩8票。
Thread[Thread-2,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩7票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-3,5,main]獲得對象鎖
Thread-3賣了一張票,還剩6票。
Thread[Thread-3,5,main]釋放對象鎖
Thread[Thread-2,5,main]獲得對象鎖
Thread-2賣了一張票,還剩5票。
Thread[Thread-2,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩4票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-3,5,main]獲得對象鎖
Thread-3賣了一張票,還剩3票。
Thread[Thread-3,5,main]釋放對象鎖
Thread[Thread-2,5,main]獲得對象鎖
Thread-2賣了一張票,還剩2票。
Thread[Thread-2,5,main]釋放對象鎖
Thread[Thread-1,5,main]獲得對象鎖
Thread-1賣了一張票,還剩1票。
Thread[Thread-1,5,main]釋放對象鎖
Thread[Thread-3,5,main]獲得對象鎖
Thread-3賣了一張票,還剩0票。
Thread[Thread-3,5,main]釋放對象鎖
Thread[Thread-2,5,main]獲得對象鎖
票已賣完
Thread[Thread-2,5,main]釋放對象鎖
票已賣完
Thread[Thread-1,5,main]獲得對象鎖
Thread[Thread-1,5,main]釋放對象鎖
票已賣完
以上看出一個線程得到鎖,執行代碼,解鎖,另一個線程獲得鎖,執行...,這個是正确使用
參考文獻:https://www.cnblogs.com/baizhanshi/p/6419268.html
三、線程的狀态
3.1狀态圖解

1.建立(new):當線程被建立的時候隻會短暫的處于這種狀态。
2.就緒(runnable):在這種狀态下,隻要排程器把時間碎片配置設定給線程,線程就可以運作,進入運作狀态
3.運作(running):線程得到cpu時間碎片,執行任務,此時除非此線程自動放棄CPU資源或者有優先級更高的線程進入,線程将一直運作到結束。
4.阻塞(blocked):線程能夠運作,但是有某個條件阻止它的運作,當線程處于阻塞狀态的時候,排程器将忽略線程,不會配置設定給線程任何CPU時間碎片,直到線程重新進入就緒狀态,等待CPU配置設定時間碎片
5.死亡(dead)處于死亡或終止狀态下的線程是不可以排程的,并且不會再得到時間碎片,它的任務已結束,或不再是可運作的
自然終止:正常運作run()方法後終止
異常終止:調用stop()方法讓一個線程終止運作
3.2線程的常用方法
void run() 建立該類的子類時必須實作的方法,在這個方法裡添加任務的實作
void start() 開啟線程的方法,調用此方法,線程開始運作
static void sleep(long t) 釋放CPU的執行權,不釋放鎖,等待時間t以後重新進入就緒狀态,其他線程與目前線程存在資源競争關系不會獲得CPU的時間排程,那些線程依然處在阻塞狀态
static void sleep(long millis,int nanos)
final void wait() 釋放CPU的執行權,釋放鎖,進入等待池,等待喚醒進入阻塞狀态
final void notify() 喚醒等待池中的線程,線程進入阻塞狀态,等待其他任務執行完,擷取CPU時間碎片
final void notifyAll
()
喚醒正在等待對象螢幕的所有線程。
static void yied()可以對目前線程進行臨時暫停(釋放CPU的執行權),直接進入就緒狀态,其他線程與目前線程存在資源競争關系不會獲得CPU的時間排程,那些線程依然處在阻塞狀态
void join() 等待這個線程死亡,在a線程中調用線程b.join() 要等線程b死亡 線程a才能繼續執行
sleep()方法 之前用了,這裡不再示範了,yied()方法和sleep()差不多也不做示範
示範一下wait(),notifyAll(),join()
wait(),notifyAll()測試
package thread;
import java.util.LinkedList;
import java.util.Queue;
public class WaitTest {
public static void main(String[] args) {
Queue queue = new LinkedList<>();
Producer p1 = new Producer(queue, 2, "Pro 1");
Producer p2 = new Producer(queue, 2, "Pro 2");
Consumer c1 = new Consumer(queue, 2, "Con 1");
Consumer c2 = new Consumer(queue, 2, "Con 2");
p1.start();
p2.start();
c1.start();
c2.start();
}
}
class Producer extends Thread{
private Queue queue;
private int maxSize;
@Override
public void run() {
while(true) {
synchronized(queue) {
try
{
Thread.sleep(200);
if(queue.size() == maxSize) {
System.out.println(Thread.currentThread().getName()+"queue已滿");
queue.notifyAll();
queue.wait();//釋放對象鎖,其他線程可以擷取對象鎖,操作該對象
}else {
double d = Math.random();
queue.offer(d);
System.out.println(Thread.currentThread().getName()+"queue添加隊員:"+d);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public Producer(Queue queue, int maxSize,String name) {
super();
this.queue = queue;
this.maxSize = maxSize;
this.setName(name);
}
}
class Consumer extends Thread{
private Queue queue;
private int maxSize;
@Override
public void run() {
while(true) {
synchronized(queue) {
try
{
Thread.sleep(200);
if(queue.isEmpty()) {
System.out.println(Thread.currentThread().getName()+"queue已空");
queue.notifyAll();
queue.wait();//釋放對象鎖,其他線程可以擷取對象鎖,操作該對象
}else {
Object b = queue.poll();
System.out.println(Thread.currentThread().getName()+"queue删除隊員:"+b);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public Consumer(Queue queue, int maxSize,String name) {
super();
this.queue = queue;
this.maxSize = maxSize;
this.setName(name);
}
}
測試結果:
Pro 1queue添加隊員:0.23488318726417456
Pro 1queue添加隊員:0.15521115372976324
Pro 1queue已滿
Con 2queue删除隊員:0.23488318726417456
Con 2queue删除隊員:0.15521115372976324
Con 2queue已空
Con 1queue已空
Pro 2queue添加隊員:0.9193677478296202
Pro 2queue添加隊員:0.5024437264449279
Pro 2queue已滿
Con 2queue删除隊員:0.9193677478296202
Con 2queue删除隊員:0.5024437264449279
Con 2queue已空
Pro 1queue添加隊員:0.5026723249672198
Pro 1queue添加隊員:0.6084867830243549
Pro 1queue已滿
Pro 2queue已滿
Con 1queue删除隊員:0.5026723249672198
Con 1queue删除隊員:0.6084867830243549
Con 1queue已空
Pro 1queue添加隊員:0.7631470001577331
Pro 1queue添加隊員:0.07019169173384032
Pro 1queue已滿
Con 2queue删除隊員:0.7631470001577331
Con 2queue删除隊員:0.07019169173384032
Con 2queue已空
Con 1queue已空
Pro 2queue添加隊員:0.1655827464569286
Pro 2queue添加隊員:0.6605956773527866
Pro 2queue已滿
Con 2queue删除隊員:0.1655827464569286
Con 2queue删除隊員:0.6605956773527866
Con 2queue已空
.......
join 測試:
LockTest類 :參考2.2.2中加上解鎖的LockTest類
lt1未join到線程main中
public static void main(String[] args) {
LockTest lt1 =new LockTest();
lt1.start();
System.out.println("main end");
}
測試結果:
main end
Thread[Thread-0,5,main]獲得對象鎖
Thread-0賣了一張票,還剩4票。
Thread[Thread-0,5,main]釋放對象鎖
Thread[Thread-0,5,main]獲得對象鎖
Thread-0賣了一張票,還剩3票。
Thread[Thread-0,5,main]釋放對象鎖
Thread[Thread-0,5,main]獲得對象鎖
Thread-0賣了一張票,還剩2票。
Thread[Thread-0,5,main]釋放對象鎖
Thread[Thread-0,5,main]獲得對象鎖
Thread-0賣了一張票,還剩1票。
Thread[Thread-0,5,main]釋放對象鎖
Thread[Thread-0,5,main]獲得對象鎖
Thread-0賣了一張票,還剩0票。
Thread[Thread-0,5,main]釋放對象鎖
票已賣完
由結果可以看出main 方法先結束
lt1線程join main線程中
public static void main(String[] args) {
LockTest lt1 =new LockTest();
lt1.start();
try {
lt1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("main end");
}
測試結果:
Thread[Thread-0,5,main]獲得對象鎖
Thread-0賣了一張票,還剩4票。
Thread[Thread-0,5,main]釋放對象鎖
Thread[Thread-0,5,main]獲得對象鎖
Thread-0賣了一張票,還剩3票。
Thread[Thread-0,5,main]釋放對象鎖
Thread[Thread-0,5,main]獲得對象鎖
Thread-0賣了一張票,還剩2票。
Thread[Thread-0,5,main]釋放對象鎖
Thread[Thread-0,5,main]獲得對象鎖
Thread-0賣了一張票,還剩1票。
Thread[Thread-0,5,main]釋放對象鎖
Thread[Thread-0,5,main]獲得對象鎖
Thread-0賣了一張票,還剩0票。
Thread[Thread-0,5,main]釋放對象鎖
票已賣完
main end
線程lt1 先結束 main線程再結束