Java多執行緒:多執行緒基礎
多執行緒基礎
多執行緒實現-Thread和Runnable
通常使用如下程式碼啟動一個新的執行緒:
private void startNewThread1() {
new Thread() {
@Override
public void run() {
//耗時操作,此時target為空
}
}.start();
}
private void startNewThread2() {
new Thread(new Runnable() {
@Override
public void run() {
//耗時操作,此時target不為空
}
}).start();
}
Thread也是一個Runnable,它實現了Runnable介面,在Thread類中有一個Runnable型別的target欄位,代表要被執行在這個子執行緒中的任務:
public class Thread implements Runnable {
private Runnable target; //執行緒所屬的ThreadGroup
private ThreadGroup group; //要執行的目標任務
public Thread (Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
if (security != null) {
g = security.getThreadGroup();
}
if (g == null) {
g = parent.getThreadGroup(); //group引數為null時獲取當前執行緒的執行緒組
}
}
...
this.group = g;
...
this.target = target;
...
}
public synchronized void start() {
...
group.add(this); //將thread物件新增到新增到ThreadGroup中
boolean started = false;
try {
start0();
started = true;
} finally {
...
}
}
}
實際上最終被執行緒執行的任務是Runnable,Thread只是對Runnable的包裝,並且通過一些狀態對Thread進行管理與排程
Runnable介面定義了可執行的任務,它有一個無返回值的run()函式:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
當啟動一個執行緒時,如果Thread的target不為空,則會在子執行緒中執行這個target的run函式,否則虛擬機器就會執行該執行緒自身的run函式
執行緒的wait、sleep、join和yield函式
函式名 | 作用 |
---|---|
wait | 當一個執行緒執行到wait()方法時,它就進入到一個和該物件相關的等待池中,同時失去了物件的機鎖,使得其他執行緒可以訪問,使用者可以用notify、notifyAll或者指定睡眠時間來喚醒當前等待池中的執行緒,注意:wait、notify、notifyAll必須放在synchronized block中,否則會丟擲異常 |
sleep | 該函式是Thread的靜態函式,作用是使當前執行緒進入睡眠狀態,因為其是靜態方法,所以不會改變物件機鎖,即使睡眠也持有物件鎖,其他物件無法訪問這個物件 |
join | 等待目標執行緒完成後再執行 |
yield | 讓出執行許可權,讓其他執行緒得以優先執行,但其他執行緒能否優先執行是未知的 |
wait notify函式
wait與nofity機制,通常用於等待機制的實現,當條件未滿足時呼叫wait進入等待狀態,一旦條件滿足,呼叫notify或notifyAll喚醒等待的執行緒繼續執行
下面來看下wait和notify,notifyAll的運用:
private static Object sLockObject = new Object();
static void waitAndNotifyAll() {
System.out.println("主執行緒 執行");
Thread thread = new WaitThread();
thread.start();
long startTime = System.currentTimeMillis();
try {
synchronized (sLockObject) {
System.out.println("主執行緒 等待");
sLockObject.wait();
}
} catch (Exception e) {
}
System.out.println("主執行緒 繼續 --> 等待耗時 : " + (System.currentTimeMillis() - startTime) + " ms");
}
static class WaitThread extends Thread {
@Override
public void run() {
try {
synchronized (sLockObject) {
Thread.sleep(3000);
sLockObject.notifyAll();
}
} catch (Exception e) {
}
}
}
waitAndNotifyAll函式中,先建立子執行緒並休眠三秒,主執行緒呼叫wait函式後進入等待狀態,子執行緒休眠三秒結束後呼叫notifyAll函式,喚醒正在等待中的主執行緒
join函式
join函式的原始解釋為"Blocks the current Thread(Thread.currentThread()) until the receiver finishes its execution and dies",意思就是阻塞當前呼叫join函式時所在的執行緒,直到接收完畢後再繼續
static void joinDemo() {
Worker worker1 = new Worker("work-1");
Worker worker2 = new Worker("work-2");
worker1.start();
System.out.println("啟動執行緒1");
try {
worker1.join();
System.out.println("啟動執行緒2");
worker2.start();
worker2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主執行緒繼續執行");
}
static class Worker extends Thread {
public Worker(String name) {
super(name);
}
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("work in " + getName());
}
}
在join函式中,首先建立兩個子執行緒,啟動worker1後呼叫worker1的join函式,此時主執行緒進入阻塞狀態直到worker1執行完畢後繼續執行,因為Worker的run函式會睡眠兩秒,因此每次呼叫join函式都會阻塞兩秒
yield函式
yield函式官方解釋為"Causes the calling Thread to yield execution time to another Thread that is ready to run",意為使呼叫該函式的執行緒讓出執行時間給其他已就緒狀態的執行緒,由於執行緒的執行是有時間片的,每個執行緒輪流佔用CPU固定的時間,執行週期到了之後就讓處執行權給其他執行緒,而yield的功能就是主動讓出執行緒的執行權給其他執行緒,其他執行緒能否得到優先執行就得看各個執行緒的狀態了
static class YieldThread extends Thread {
public YieldThread(String name) {
super(name);
}
public synchronized void run() {
for (int i = 0; i < MAX; i++) {
System.out.printf("%s ,優先順序為 : %d ----> %d\n", this.getName(), this.getPriority(), i);
// i整除4時,呼叫yield
if (i == 2) {
Thread.yield();
}
}
}
}
static void yield() {
YieldThread t1 = new YieldThread("thread-1");
YieldThread t2 = new YieldThread("thread-2");
YieldThread t2 = new YieldThread("thread-2");
YieldThread t2 = new YieldThread("thread-2");
t1.start();
t2.start();
}
可以看到YieldThread內有一個執行5次的迴圈,當迴圈到i=2時呼叫yield函式,理論上在i=2時執行緒會讓出執行權讓其他執行緒優先執行,但有時並不一定能產生效果,因為它僅僅可以使一個執行緒從running狀態變成Runnable狀態,而不是wait或者blocked狀態,並且不能保證正在執行的執行緒立刻變成Runnable狀態
與多執行緒相關的方法 Runnable、Callable、Future和FutureTask
Runnable:既能運用在Thread中又能運用線上程池中
Callable、Future、FutureTask:只能運用線上程池中
Callable
Callable與Runnable功能大致類似,不同的是Callable是一個泛型介面,它有一個泛型引數V,該介面中有一個返回值(型別為V)的call()函式
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Future
Future為執行緒池制定了一個可管理的任務標準,提供了對Runnable或Callable任務的執行結果進行取消、查詢是否完成、獲取結果、設定結果操作,分別對應cancel、isDone、get(阻塞直到任務返回結果)、set(在FutureTesk中定義)函式
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
FutureTask
FutureTask是Future的實現類,它實現了RunnableFuture<
V>
,而RunnableFuture實現了Runnable又實現了Future<
V>
這兩個介面,因此FutureTask既可以通過Thread包裝來直接執行,也可以提交給ExecuteService來執行,並且還可以直接通過get()函式獲取執行結果,該函式會阻塞直到結果返回
public class FutureTask<V> implements RunnableFuture<V> {
...
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}
FutureTask會像Thread包裝Runnable那樣對Runnable和Callable<
V>
進行包裝,Runnbale與Callable由建構函式注入:
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
如果注入的是Runnable則會被Executors.callable()函式轉換為Callable型別,因此FutureTask最終都是執行Callable型別的任務
Runnable、Callable、FutureTask運用
// 執行緒池
static ExecutorService mExecutor = Executors.newSingleThreadExecutor();
private static void futureWithRunnable() throws InterruptedException, ExecutionException {
// 向執行緒池提交runnable則沒有返回值, future沒有資料
Future<?> result = mExecutor.submit(new Runnable() {
@Override
public void run() {
fibc(20);
}
});
System.out.println("future result from runnable : " + result.get());
}
private static void futureWithCallable() throws InterruptedException, ExecutionException {
//向執行緒池提交Callable, 有返回值, future中能夠獲取返回值
Future<Integer> result2 = mExecutor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return fibc(20);
}
});
System.out.println("future result from callable : "
+ result2.get());
}
private static void futureTask() throws InterruptedException, ExecutionException {
FutureTask<Integer> futureTask = new FutureTask<Integer>(
new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return fibc(20);
}
});
// 提交futureTask
mExecutor.submit(futureTask);
System.out.println("future result from futureTask : "
+ futureTask.get());
}
// 效率底下的斐波那契數列, 耗時的操作
private static int fibc(int num) {
if (num == 0) {
return 0;
}
if (num == 1) {
return 1;
}
return fibc(num - 1) + fibc(num - 2);
}