Java多線程并發(fā)FutureTask使用詳解
本文基于最新的 OpenJDK 代碼,預(yù)計(jì)發(fā)行版本為 19 。
Java 的多線程機(jī)制本質(zhì)上能夠完成兩件事情,異步計(jì)算和并發(fā)。并發(fā)問題通過解決線程安全的一系列 API 來解決;而異步計(jì)算,常見的使用是 Runnable 和 Callable 配合線程使用。
FutureTask 是基于 Runnable 實(shí)現(xiàn)的一個(gè)可取消的異步調(diào)用 API 。
基本使用
- Future 代表了異步計(jì)算的結(jié)果,通過 ExecutorService 的
Future<?> submit(Runnable task)方法,作為返回值使用:
interface ArchiveSearcher { String search(String target); }
class App {
ExecutorService executor = ...;
ArchiveSearcher searcher = ...;
void showSearch(String target) throws InterruptedException {
Callable<String> task = () -> searcher.search(target);
Future<String> future = executor.submit(task); // 獲取執(zhí)行結(jié)果
displayOtherThings(); // do other things while searching
try {
displayText(future.get()); // use future
} catch (ExecutionException ex) { cleanup(); return; }
}
}- FutureTask類是實(shí)現(xiàn)了Runnable的Future的實(shí)現(xiàn),因此可以由Executor執(zhí)行。例如,上述帶有submit的構(gòu)造可以替換為:
class App {
ExecutorService executor = ...;
ArchiveSearcher searcher = ...;
void showSearch(String target) throws InterruptedException {
Callable<String> task = () -> searcher.search(target);
// 關(guān)鍵兩行替換
FutureTask<String> future = new FutureTask<>(task);
executor.execute(future);
displayOtherThings(); // do other things while searching
try {
displayText(future.get()); // use future
} catch (ExecutionException ex) { cleanup(); return; }
}
}代碼分析
繼承關(guān)系

Future
Future 表示異步計(jì)算的結(jié)果。定義了用于檢查計(jì)算是否完成、等待計(jì)算完成以及檢索計(jì)算結(jié)果的能力。只有在計(jì)算完成后,才能使用 get 方法檢索結(jié)果,在必要時(shí)會阻塞線程直到 Future 計(jì)算完成。取消是由 cancel 方法執(zhí)行的。還提供了其他方法來確定任務(wù)是正常完成還是被取消。一旦計(jì)算完成,就不能取消計(jì)算。如果為了可取消性而使用 Future ,但又不想提供一個(gè)可用的結(jié)果,你可以聲明形式 Future<?> 并返回 null 作為任務(wù)的結(jié)果。
在介紹 Future 中定義的能力之前,先了解一下它的用來表示 Future 狀態(tài)內(nèi)部類,和狀態(tài)檢索方法:
public interface Future<V> {
enum State {
// The task has not completed.
RUNNING,
// The task completed with a result. @see Future#resultNow()
SUCCESS,
//The task completed with an exception. @see Future#exceptionNow()
FAILED,
// The task was cancelled. @see #cancel(boolean)
CANCELLED
}
default State state() {
if (!isDone()) // 根據(jù) isDone() 判斷運(yùn)行中
return State.RUNNING;
if (isCancelled()) // 根據(jù) isCancelled() 判斷已取消
return State.CANCELLED;
boolean interrupted = false;
try {
while (true) { // 死循環(huán)輪詢
try {
get(); // may throw InterruptedException when done
return State.SUCCESS;
} catch (InterruptedException e) {
interrupted = true;
} catch (ExecutionException e) {
return State.FAILED;
}
}
} finally {
if (interrupted) Thread.currentThread().interrupt();
}
}
}
Future 的狀態(tài)檢索的默認(rèn)實(shí)現(xiàn)是根據(jù) isDone() 、isCancelled() 和不斷輪詢 get() 方法獲取到的返回值判斷的。
當(dāng) get() 正常返回結(jié)果時(shí), state() 返回 State.SUCCESS ; 當(dāng)拋出 InterruptedException 時(shí),最終會操作所在的線程執(zhí)行嘗試中斷的方法;拋出其他異常時(shí),則返回 State.FAILED 。
Future 中定義的其他方法包括:
package java.util.concurrent;
public interface Future<V> {
// 取消操作
boolean cancel(boolean mayInterruptIfRunning);
// 檢查是否取消
boolean isCancelled();
// 檢查是否完成
boolean isDone();
// 獲取計(jì)算結(jié)果的方法
V get() throws InterruptedException, ExecutionException;
// 帶有超時(shí)限制的獲取計(jì)算結(jié)果的方法
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
// 立刻返回結(jié)果
default V resultNow()
// 立刻拋出異常
default Throwable exceptionNow()
}其中 resultNow() 和 exceptionNow() 是帶有默認(rèn)實(shí)現(xiàn)的:
default V resultNow() {
if (!isDone())
throw new IllegalStateException("Task has not completed");
boolean interrupted = false;
try {
while (true) {
try {
return get();
} catch (InterruptedException e) {
interrupted = true;
} catch (ExecutionException e) {
throw new IllegalStateException("Task completed with exception");
} catch (CancellationException e) {
throw new IllegalStateException("Task was cancelled");
}
}
} finally {
if (interrupted) Thread.currentThread().interrupt();
}
}- Future 仍在運(yùn)行中,直接拋出 IllegalStateException 。
- 執(zhí)行一個(gè)輪詢,調(diào)用
get()嘗試返回計(jì)算結(jié)果,如果get()拋出異常,則根據(jù)異常拋出不同消息的 IllegalStateException 或執(zhí)行中斷線程的操作。
default Throwable exceptionNow() {
if (!isDone())
throw new IllegalStateException("Task has not completed");
if (isCancelled())
throw new IllegalStateException("Task was cancelled");
boolean interrupted = false;
try {
while (true) {
try {
get();
throw new IllegalStateException("Task completed with a result");
} catch (InterruptedException e) {
interrupted = true;
} catch (ExecutionException e) {
return e.getCause();
}
}
} finally {
if (interrupted) Thread.currentThread().interrupt();
}
}- Future 仍在運(yùn)行中,直接拋出 IllegalStateException 。
- Future 檢查是否已取消,如果取消了拋出 IllegalStateException 。
- 執(zhí)行輪詢,調(diào)用
get()方法,如果能夠正常執(zhí)行結(jié)束,也拋出 IllegalStateException ,消息是 "Task completed with a result" ;get()若拋出 InterruptedException ,則執(zhí)行線程中斷操作;其他異常正常拋出。
這就是 Future 的全貌了。
RunnableFuture
RunnableFuture 接口同時(shí)實(shí)現(xiàn)了 Runnable 和 Future 接口 :
public interface RunnableFuture<V> extends Runnable, Future<V> {
/**
* Sets this Future to the result of its computation
* unless it has been cancelled.
* 除非已取消,否則將此Future設(shè)置為其計(jì)算的結(jié)果。
*/
void run();
}Runnable 接口是我們常用的用來實(shí)現(xiàn)線程操作的,可以說是十分熟悉也十分簡單了。
這個(gè)接口代表了一個(gè)可以 Runnable 的 Future ,run 方法的成功執(zhí)行代表著 Future 執(zhí)行完成,并可以獲取它的計(jì)算結(jié)果。
這個(gè)接口是 JDK 1.6 后續(xù)才有的。
FutureTask
FutureTask 是 RunnableFuture 的直接實(shí)現(xiàn)類,它代表了一個(gè)可取消的異步計(jì)算任務(wù)。根據(jù)我們對 Future 的分析和 Runnable 的熟悉,就可以理解它的作用了:可取消并可以檢索運(yùn)行狀態(tài)的一個(gè) Runnable ,配合線程使用可以中斷線程執(zhí)行。當(dāng)任務(wù)沒有執(zhí)行完成時(shí)會造成阻塞。并且它還可以配合 Executor 使用。
狀態(tài)
FutureTask 內(nèi)部也定義了自己的狀態(tài):
public class FutureTask<V> implements RunnableFuture<V> {
private volatile int state;
private static final int NEW = 0; // 新建
private static final int COMPLETING = 1; // 完成中
private static final int NORMAL = 2; // 正常完成
private static final int EXCEPTIONAL = 3; // 異常的
private static final int CANCELLED = 4; // 已取消
private static final int INTERRUPTING = 5; // 中斷中
private static final int INTERRUPTED = 6; // 已中斷
@Override
public State state() {
int s = state;
while (s == COMPLETING) {
// 等待過渡到 NORMAL 或 EXCEPTIONAL
Thread.yield();
s = state;
}
switch (s) {
case NORMAL:
return State.SUCCESS;
case EXCEPTIONAL:
return State.FAILED;
case CANCELLED:
case INTERRUPTING:
case INTERRUPTED:
return State.CANCELLED;
default:
return State.RUNNING;
}
}
}FutureTask 的狀態(tài)包括 7 種,最初為 NEW ,只有在 set、setException 和 cancel 方法中,運(yùn)行狀態(tài)才會轉(zhuǎn)換為最終狀態(tài)。在完成期間,狀態(tài)可能為 COMPLETING (當(dāng)結(jié)果正在設(shè)置時(shí)) 或 INTERRUPTING (僅當(dāng)中斷跑者以滿足cancel(true) )的瞬態(tài)值。
可能存在的狀態(tài)轉(zhuǎn)換是:
NEW -> COMPLETING -> NORMAL // 正常完成 NEW -> COMPLETING -> EXCEPTIONAL // 拋出異常 NEW -> CANCELLED // 取消 NEW -> INTERRUPTING -> INTERRUPTED // 中斷
屬性
下面分析一下它的屬性:
/** 底層的調(diào)用;運(yùn)行后為null */
private Callable<V> callable;
/** get()返回的結(jié)果或拋出的異常 */
private Object outcome; // non-volatile, protected by state reads/writes
/** The thread running the callable; CASed during run() */
private volatile Thread runner;
/** 等待線程的 Treiber 堆棧 */
private volatile WaitNode waiters;內(nèi)部類
先看一看這個(gè) WaitNode ,這是一個(gè) FutureTask 的內(nèi)部類:
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}一個(gè)鏈表結(jié)構(gòu),用來對等待線程進(jìn)行排序。
構(gòu)造方法
最后是方法的分析,首先是構(gòu)造方法:
// Creates a {@code FutureTask} that will, upon running, execute the given {@code Callable}.
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
/**
* Creates a {@code FutureTask} that will, upon running, execute the
* given {@code Runnable}, and arrange that {@code get} will return the
* given result on successful completion.
* Runnable 成功是返回給定的結(jié)果 result
*/
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}FutureTask 接收一個(gè) Callable 或一個(gè) Runnable 作為參數(shù),Runnable 會封裝一下都保存到屬性 callable ,然后更新 FutureTask 的狀態(tài)為 NEW 。
從 Future 接口中實(shí)現(xiàn)的方法逐個(gè)分析:
檢索 FutureTask 狀態(tài)
public boolean isCancelled() {
return state >= CANCELLED; // 大于等于 4, 已取消、中斷中、已中斷
}
public boolean isDone() {
return state != NEW; // 不是 new 就代表執(zhí)行結(jié)束了
}取消操作
// mayInterruptIfRunning 表示最終的取消是通過中斷還是通過取消。
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW && STATE.compareAndSet(this, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) // 嘗試設(shè)置 CANCELLED 或 INTERRUPTING
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt(); // 通過中斷取消任務(wù)
} finally { // final state
STATE.setRelease(this, INTERRUPTED); // 更新中斷狀態(tài)
}
}
} finally {
finishCompletion();
}
return true;
}這里的 finishCompletion() 的作用是通過 LockSupport 喚醒等待的全部線程并從等待列表中移除,然后調(diào)用done(),最后把 callable 置空。相當(dāng)于取消成功后釋放資源的操作。
private void finishCompletion() {
// assert state > COMPLETING;
for (WaitNode q; (q = waiters) != null;) {
if (WAITERS.weakCompareAndSet(this, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null; // unlink to help gc
q = next;
}
break;
}
}
done();
callable = null; // to reduce footprint
}done() 是個(gè)空實(shí)現(xiàn),供子類去自定義的。
protected void done() { }計(jì)算結(jié)果
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L); // 異步結(jié)果
return report(s);
}
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}這里涉及兩個(gè)方法:awaitDone 方法和 report 方法 。
awaitDone 方法:
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
// The code below is very delicate, to achieve these goals:
// - if nanos <= 0L, 及時(shí)返回,不需要 allocation 或 nanoTime
// - if nanos == Long.MIN_VALUE, don't underflow
// - if nanos == Long.MAX_VALUE, and nanoTime is non-monotonic
// and we suffer a spurious wakeup, we will do no worse than
// to park-spin for a while
long startTime = 0L; // Special value 0L means not yet parked
WaitNode q = null;
boolean queued = false;
for (;;) {
int s = state;
if (s > COMPLETING) { // COMPLETING = 1
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // 瞬時(shí)態(tài),完成中
// We may have already promised (via isDone) that we are done
// so never return empty-handed or throw InterruptedException
Thread.yield();
else if (Thread.interrupted()) {
removeWaiter(q); // 線程中斷,移除等待的線程
throw new InterruptedException();
}
else if (q == null) {
if (timed && nanos <= 0L)
return s;
q = new WaitNode();
}
else if (!queued)
queued = WAITERS.weakCompareAndSet(this, q.next = waiters, q);
else if (timed) { // 設(shè)置超時(shí)時(shí)間的情況
final long parkNanos;
if (startTime == 0L) { // first time
startTime = System.nanoTime();
if (startTime == 0L)
startTime = 1L;
parkNanos = nanos;
} else {
long elapsed = System.nanoTime() - startTime;
if (elapsed >= nanos) {
removeWaiter(q);
return state;
}
parkNanos = nanos - elapsed;
}
// nanoTime may be slow; recheck before parking
if (state < COMPLETING)
LockSupport.parkNanos(this, parkNanos);
}
else
LockSupport.park(this);
}
}通過 CAS 和 LockSupport 的掛起/喚醒操作配合,阻塞當(dāng)前線程,異步地等待計(jì)算結(jié)果。
這里有個(gè) removeWaiter 方法,內(nèi)部就是遍歷 waiters ,刪除超時(shí)和中斷的等待線程。
當(dāng)異步邏輯執(zhí)行完成后,調(diào)用 report 方法:
// 為完成的任務(wù)返回結(jié)果或拋出異常
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}這里用到了一個(gè) outcome ,它是一個(gè) Object 類型,作為返回結(jié)果,通過 set 方法可以對它進(jìn)行設(shè)置:
// 除非該 future 已被設(shè)置或取消,否則將該 future 的結(jié)果設(shè)置為給定值。
// 該方法在成功完成計(jì)算后由 run 方法在內(nèi)部調(diào)用。
protected void set(V v) {
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
outcome = v;
STATE.setRelease(this, NORMAL); // final state
finishCompletion();
}
}立刻獲取結(jié)果或異常
這兩個(gè)方法都是通過 outcome 預(yù)設(shè)的返回值,返回預(yù)期的結(jié)果或異常。
public V resultNow() {
switch (state()) { // Future.State
case SUCCESS:
@SuppressWarnings("unchecked")
V result = (V) outcome;
return result;
case FAILED:
throw new IllegalStateException("Task completed with exception");
case CANCELLED:
throw new IllegalStateException("Task was cancelled");
default:
throw new IllegalStateException("Task has not completed");
}
}
@Override
public Throwable exceptionNow() {
switch (state()) { // Future.State
case SUCCESS:
throw new IllegalStateException("Task completed with a result");
case FAILED:
Object x = outcome;
return (Throwable) x;
case CANCELLED:
throw new IllegalStateException("Task was cancelled");
default:
throw new IllegalStateException("Task has not completed");
}
}run 方法組
最后是實(shí)現(xiàn)了 Runnable 的 run 方法:
public void run() {
// 保證 NEW 狀態(tài)和 RUNNER 成功設(shè)置當(dāng)前線程
if (state != NEW ||
!RUNNER.compareAndSet(this, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable; // 待執(zhí)行的 Callable
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call(); // 執(zhí)行 Callable
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
// 為了防止并發(fā)調(diào)用 run ,直到 state 確定之前, runner 必須是非空的
runner = null;
// 狀態(tài)必須在 runner 置空后重新讀取,以防止泄漏中斷
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}這里涉及兩個(gè)方法,第一個(gè)是 setException(ex) :
// 導(dǎo)致該future報(bào)告一個(gè){@link ExecutionException},并將給定的可拋出對象作為其原因,除非該future已經(jīng)被設(shè)置或取消。
protected void setException(Throwable t) {
if (STATE.compareAndSet(this, NEW, COMPLETING)) {
outcome = t;
STATE.setRelease(this, EXCEPTIONAL); // final state
finishCompletion();
}
}另一個(gè)是 handlePossibleCancellationInterrupt 方法:
/**
* 確保任何來自可能的 cancel(true) 的中斷只在 run 或 runAndReset 時(shí)交付給任務(wù)。
*/
private void handlePossibleCancellationInterrupt(int s) {
// It is possible for our interrupter to stall before getting a
// chance to interrupt us. Let's spin-wait patiently.
if (s == INTERRUPTING)
while (state == INTERRUPTING)
Thread.yield(); // wait out pending interrupt
// assert state == INTERRUPTED;
// 我們想清除可能從cancel(true)接收到的所有中斷。
// 然而,允許使用中斷作為任務(wù)與其調(diào)用者通信的獨(dú)立機(jī)制,并沒有辦法只清除取消中斷。
// Thread.interrupted();
}最后是 runAndReset 方法:
protected boolean runAndReset() {
if (state != NEW || !RUNNER.compareAndSet(this, null, Thread.currentThread()))
return false;
boolean ran = false; // flag 表示正常執(zhí)行結(jié)束
int s = state;
try {
Callable<V> c = callable;
if (c != null && s == NEW) {
try {
c.call(); // don't set result
ran = true;
} catch (Throwable ex) {
setException(ex);
}
}
} 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
s = state; //
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
return ran && s == NEW; // 當(dāng)正常執(zhí)行結(jié)束,且 state 一開始就是 NEW 時(shí),表示可以運(yùn)行并重置。
}執(zhí)行計(jì)算時(shí)不設(shè)置其結(jié)果,然后將該 future 重置為初始狀態(tài),如果計(jì)算遇到異?;虮蝗∠瑒t不這樣做。這是為本質(zhì)上執(zhí)行多次的任務(wù)設(shè)計(jì)的。
run 和 runAndReset 都用到了一個(gè) RUNNER , 最后就來揭秘一下這個(gè)東西:
private static final VarHandle STATE;
private static final VarHandle RUNNER;
private static final VarHandle WAITERS;
static {
try {
MethodHandles.Lookup l = MethodHandles.lookup();
STATE = l.findVarHandle(FutureTask.class, "state", int.class);
RUNNER = l.findVarHandle(FutureTask.class, "runner", Thread.class);
WAITERS = l.findVarHandle(FutureTask.class, "waiters", WaitNode.class);
} catch (ReflectiveOperationException e) {
throw new ExceptionInInitializerError(e);
}
// Reduce the risk of rare disastrous classloading in first call to
// LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773
Class<?> ensureLoaded = LockSupport.class;
}MethodHandles.lookup()創(chuàng)建一個(gè)MethodHandles.Lookup對象,該對象可以創(chuàng)建所有訪問權(quán)限的方法,包括public,protected,private,default。
VarHandle 主要用于動態(tài)操作數(shù)組的元素或?qū)ο蟮某蓡T變量。VarHandle通過 MethodHandles 來獲取實(shí)例,然后調(diào)用 VarHandle 的方法即可動態(tài)操作指定數(shù)組的元素或指定對象的成員變量。
到此這篇關(guān)于Java 多線程并發(fā)FutureTask的文章就介紹到這了,更多相關(guān)Java 多線程并發(fā)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?@DateTimeFormat日期格式化時(shí)注解場景分析
這篇文章主要介紹了Spring?@DateTimeFormat日期格式化時(shí)注解場景分析,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05
JAVA?GUI基礎(chǔ)與MouseListener用法
這篇文章主要介紹了JAVA?GUI基礎(chǔ)與MouseListener用法,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
eclipse修改maven倉庫位置的方法實(shí)現(xiàn)
本文主要介紹了eclipse修改maven倉庫位置的方法實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
Java設(shè)計(jì)模式七大原則之接口隔離原則詳解
接口隔離原則(Interface Segregation Principle),又稱為ISP原則,就是在一個(gè)類中不要定義過多的方法,接口應(yīng)該盡量簡單細(xì)化。本文將為大家具體介紹一下Java設(shè)計(jì)模式七大原則之一的接口隔離原則,需要的可以參考一下2022-02-02
Spring MVC的文件上傳和下載以及攔截器的使用實(shí)例
這篇文章主要介紹了Spring MVC的文件上傳和下載以及攔截器的使用實(shí)例,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08
使用Lombok @Builder注解導(dǎo)致默認(rèn)值無效的問題
這篇文章主要介紹了使用Lombok @Builder注解導(dǎo)致默認(rèn)值無效的問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
詳解SpringBoot和Mybatis配置多數(shù)據(jù)源
本篇文章主要介紹了詳解SpringBoot和Mybatis配置多數(shù)據(jù)源,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05

