第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {// Worker本身就是Runnable的实现类
public void run() {
runWorker(this);
}
}
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this); // 新建一个线程,里面的Runnable是work本身
}
private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
public void run() {
runWorker(this);
}
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run(); // 执行run方法
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JgY6ENo9-1578021304331)(4695305B9CD44E3E84F879EA1F8DEC9A)]
如果当前运行的线程少于 corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
如果运行的线程等于或多于 corePoolSize,则将任务加入 BlockingQueue。
如果无法将任务加入 BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。
如果创建新线程将使当前运行的线程超出 maximumPoolSize,任务将被拒绝,并调用 RejectedExecutionHandler.rejectedExecution() 方法。
public class ThreadPoolTest {
public static void main(String[] args) {
int corePoolSize = 10;
int maximumPoolSize = 20;
long keepAliveTime = 10;
TimeUnit unit = TimeUnit.MINUTES;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue(100);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue);
for (int i = 0; i < 15; i++) {
int t = i;
threadPoolExecutor.execute(()->{
System.out.println("Thread :" + t);
});
}
}
}
参数说明:
ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按 FIFO 排序元素,吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool() 使用了这个队列。
SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于 Linked-BlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。
PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
用于初始化线程,可以设置线程名等一些信息
队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是 AbortPolicy,表示无法处理新任务时抛出异常
当线程空闲超过门限值,就会被回收,我们知道线程池里的线程是获取任务,获取成功就执行。
while (task != null || (task = getTask()) != null)
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
获取任务调用了getTask()
方法,该方法会阻塞住获取任务,如果返回null 循环结束
超时退出的机制只要超过时间获取不到任务 结束循环就行了,所以具体实现都是在getTask里的
里面有个很重要的内容就是 是否需要回收核心线程,默认不会收,可以通过方法设置
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
使用超时获取的方式,如果超过时间无法获取到任务,那么 timedOut = true
;
当 timedOut = true
return null;
private Runnable getTask() {
boolean timedOut = false; // 默认没有超时
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 如果核心线程也允许回收,那么全部进行回收,否则只有线程数大于核心数的时候进行回收
if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { // 如果线程数量大于最大线程数量或者超时了 进行回收
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); // 如果需要回收 使用超时获取方式 否则使用 无限等待获取方式
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
可以使用两个方法向线程池提交任务,分别为 execute() 和 submit() 方法。
execute() 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。通过以下代码可知 execute() 方法输入的任务是一个 Runnable 类的实例。
threadsPool.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
});
submit() 方法用于提交需要返回值的任务。线程池会返回一个 future 类型的对象,通过这个 future 对象可以判断任务是否执行成功,并且可以通过 future 的 get() 方法来获取返回值,get() 方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
Future<Object> future = executor.submit(harReturnValuetask);
try {
Object s = future.get();
} catch (InterruptedException e) {
// 处理中断异常
} catch (ExecutionException e) {
// 处理无法执行任务异常
} finally {
// 关闭线程池
executor.shutdown();
}
可以通过调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池
它们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程,所以无法响应中断的任务可能永远无法终止
只要调用了这两个关闭方法中的任意一个,isShutdown 方法就会返回 true
当所有的任务都已关闭后,才表示线程池关闭成功,这时调用 isTerminaed 方法会返回 true
shutdownNow与shutdown的区别
public void shutdown() { public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock; final ReentrantLock mainLock = this.mainLock;
mainLock.lock(); mainLock.lock();
try { try {
checkShutdownAccess(); checkShutdownAccess();
advanceRunState(SHUTDOWN); advanceRunState(STOP);
interruptIdleWorkers(); interruptWorkers();
onShutdown(); tasks = drainQueue();
} finally { } finally {
mainLock.unlock(); mainLock.unlock();
} }
tryTerminate(); tryTerminate();
return tasks;
} }
shutdownNow 将线程池的状态设置成 STOP 状态 停止所有的正在执行或暂停任务的线程
shutdown 将线程池的状态设置成 SHUTDOWN 状态 中断所有没有正在执行任务的线程
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 高三位 为 状态 其余位为线程数量 为什么这么些 是因为不同系统下 integer 的位数不同
private static final int COUNT_BITS = Integer.SIZE - 3;
// 线程最大个数
// 1 << COUNT_BITS 100000000000000000
// 1 << COUNT_BITS -1 100000000000000000-1 = 11111111111111111
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 111 000000000000
private static final int RUNNING = -1 << COUNT_BITS;
// 000 000000000000
private static final int SHUTDOWN = 0 << COUNT_BITS;
// 001 000000000000
private static final int STOP = 1 << COUNT_BITS;
// 010 000000000000
private static final int TIDYING = 2 << COUNT_BITS;
// 011 000000000000
private static final int TERMINATED = 3 << COUNT_BITS;
// 获取高三位 后面带着0
//xxx 0000000000000
private static int runStateOf(int c) {
return c & ~CAPACITY;
}
// 获取低29位
private static int workerCountOf(int c) {
return c & CAPACITY;
}
// 计算ctl新值, 将状态码和数量拼起来
private static int ctlOf(int rs, int wc) {
return rs | wc;
}
ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor。它主要用来在给定的延迟之后运行任务,或者定期执行任务
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize, RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), handler);
}
public ScheduledThreadPoolExecutor(int corePoolSize, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue(), threadFactory, handler);
}
我们从代码可以看出,ScheduledThreadPoolExecutor 的实现重点是 DelayedWorkQueue 队列,DelayedWorkQueue 是一个无界队列,所以 ThreadPoolExecutor 的 maximumPoolSize 在 ScheduledThreadPoolExecutor 中没有什么意义
public void execute(Runnable command) {
schedule(command, 0, NANOSECONDS);
}
public Future<?> submit(Runnable task) {
return schedule(task, 0, NANOSECONDS);
}
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
if (command == null || unit == null) {
throw new NullPointerException();
}
RunnableScheduledFuture<?> t = decorateTask(command, new ScheduledFutureTask<Void>(command, null, triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
if (callable == null || unit == null) {
throw new NullPointerException();
}
RunnableScheduledFuture<V> t = decorateTask(callable, new ScheduledFutureTask<V>(callable, triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
if (command == null || unit == null) {
throw new NullPointerException();
}
if (period <= 0) {
throw new IllegalArgumentException();
}
ScheduledFutureTask<Void> sft = new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(period));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
if (command == null || unit == null) {
throw new NullPointerException();
}
if (delay <= 0) {
throw new IllegalArgumentException();
}
ScheduledFutureTask<Void> sft = new ScheduledFutureTask<Void>(command, null, triggerTime(initialDelay, unit), unit.toNanos(-delay));
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
delayedExecute(t);
return t;
}
参数:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(), threadFactory);
}
大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器,没有核心池,同时可能会有无数个线程在执行
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory){
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory);
}
使用固定线程数的线程池,最多会有nThreads个任务执行
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory));
}
创建使用单个线程的线程池,提交的各个任务会顺序的执行,并且永远不会有两个任务同时执行
定时调度线程池执行器
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService(new ScheduledThreadPoolExecutor(1, threadFactory));
}
ForkJoinPool使用时需要构建自己特有的任务类ForkJoinTask
,而不是使用Runnable接口
ForkJoinTask不是Runnable的子类
也可以传入Runnable接口实现类任务 但是会被封装成 ForkJoinTask
public class ForkJoinPoolTest {
static int[] nums = new int[1000000];
static final int MAX_NUM = 50000;
static Random r = new Random();
static {
for(int i=0; i {
private static final long serialVersionUID = 1L;
int start, end;
AddTaskRet(int s, int e) {
start = s;
end = e;
}
@Override
protected Long compute() {
if(end-start <= MAX_NUM) {
long sum = 0L;
for(int i=start; i
从上面的代码看,forkjoin池最大的特点就是fork
,也就是会不断将任务拆分成子任务,子任务的子任务也就是分而治之的思想
第二个重点就是join
,每个子任务计算完成之后将结果合并给父任务
private ForkJoinPool(int parallelism, ForkJoinWorkerThreadFactory factory, UncaughtExceptionHandler handler, int mode, String workerNamePrefix) {
this.workerNamePrefix = workerNamePrefix;
this.factory = factory;
this.ueh = handler;
this.config = (parallelism & SMASK) | mode;
long np = (long)(-parallelism); // offset ctl counts
this.ctl = ((np << AC_SHIFT) & AC_MASK) | ((np << TC_SHIFT) & TC_MASK);
}
WorkQueue是一个双端队列,它定义在ForkJoinPool类里。
scanState描述WorkQueue当前状态:
操作WorkQueue前需要锁定,记录在字段qlock:
WorkQueue也有config,不要和ForkJoinPool的config混淆了。WorkQueue的config记录了在WorkQueue[]的下标和当前mode
不管是submit
还是execute
最后调用的都是externalPush
,这种任务我们成为提交任务
还有一种是fork出来的子任务,两种任务都会存放在 WorkQueue 数组中
线程池里的线程也不再是Thread
,而是Thread
的子类 ForkJoinWorkerThread
ForkJoinWorkerThread
队列中的任务 final void runWorker(WorkQueue w) {
w.growArray(); // allocate queue
int seed = w.hint; // initially holds randomization hint
int r = (seed == 0) ? 1 : seed; // avoid 0 for xorShift
for (ForkJoinTask> t;;) { // 循环获取任务
if ((t = scan(w, r)) != null)
w.runTask(t); // 执行任务
else if (!awaitWork(w, r))
break;
r ^= r << 13;
r ^= r >>> 17;
r ^= r << 5; // xorshift
}
}
循环获取任务,然后使用WorkQueue去执行自己的任务,如果已经扫描不到任务,那么该线程会在awaitWork中park
4. WorkQueue.runTask(ForkJoinTask task)
final void runTask(ForkJoinTask> task) {
if (task != null) {
scanState &= ~SCANNING; // mark as busy
(currentSteal = task).doExec(); // 执行任务逻辑
U.putOrderedObject(this, QCURRENTSTEAL, null); // release for GC
execLocalTasks(); // 执行本地任务
ForkJoinWorkerThread thread = owner;
if (++nsteals < 0) // collect on overflow
transferStealCount(pool);
scanState |= SCANNING;
if (thread != null)
thread.afterTopLevelExec();
}
}
final int doExec() {
int s; boolean completed;
if ((s = status) >= 0) {
try {
completed = exec(); // 执行并得到是否完成
} catch (Throwable rex) {
return setExceptionalCompletion(rex);
}
if (completed)
s = setCompletion(NORMAL);
}
return s;
}
private ForkJoinTask<?> scan(WorkQueue w, int r) {
WorkQueue[] ws; int m;
if ((ws = workQueues) != null && (m = ws.length - 1) > 0 && w != null) {
int ss = w.scanState; // initially non-negative
for (int origin = r & m, k = origin, oldSum = 0, checkSum = 0;;) {
WorkQueue q;
ForkJoinTask<?>[] a;
ForkJoinTask<?> t;
int b, n;
long c;
if ((q = ws[k]) != null) {
if ((n = (b = q.base) - q.top) < 0 && (a = q.array) != null) { // non-empty
long i = (((a.length - 1) & b) << ASHIFT) + ABASE;
if ((t = ((ForkJoinTask<?>) U.getObjectVolatile(a, i))) != null && q.base == b) {
if (ss >= 0) {
if (U.compareAndSwapObject(a, i, t, null)) {
q.base = b + 1;
if (n < -1) { // signal others
signalWork(ws, q);
}
return t;
}
} else if (oldSum == 0 && w.scanState < 0) {
tryRelease(c = ctl, ws[m & (int)c], AC_UNIT);
}
}
if (ss < 0) // 如果状态小于0 // refresh
ss = w.scanState;
r ^= r << 1; r ^= r >>> 3; r ^= r << 10;
origin = k = r & m; // move and rescan
oldSum = checkSum = 0;
continue;
}
checkSum += b;
}
if ((k = (k + 1) & m) == origin) { // continue until stable
if ((ss >= 0 || (ss == (ss = w.scanState))) &&
oldSum == (oldSum = checkSum)) {
if (ss < 0 || w.qlock < 0) // already inactive
break;
int ns = ss | INACTIVE; // try to inactivate
long nc = ((SP_MASK & ns) |
(UC_MASK & ((c = ctl) - AC_UNIT)));
w.stackPred = (int)c; // hold prev stack top
U.putInt(w, QSCANSTATE, ns);
if (U.compareAndSwapLong(this, CTL, c, nc))
ss = ns;
else
w.scanState = ss; // back out
}
checkSum = 0;
}
}
}
return null;
}
该方法会优先获取本身队列里的任务去执行,如果队列取空之后就会尝试去其他人的队列里偷取任务
public final ForkJoinTask fork() {
Thread t;
if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
((ForkJoinWorkerThread)t).workQueue.push(this);
else
ForkJoinPool.common.externalPush(this);
return this;
}
public final V join() {
int s;
if ((s = doJoin() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
private int doJoin() {
int s; Thread t; ForkJoinWorkerThread wt; ForkJoinPool.WorkQueue w;
return (s = status) < 0 ? s :
((t = Thread.currentThread()) instanceof ForkJoinWorkerThread) ?
(w = (wt = (ForkJoinWorkerThread)t).workQueue).
tryUnpush(this) && (s = doExec()) < 0 ? s :
wt.pool.awaitJoin(w, this, 0L) :
externalAwaitDone();
}
任务需要join的时候 如果子任务还在执行,那么会调用wt.pool.awaitJoin(w, this, 0L)进行等待阻塞,然后一层层的往上级任务jion
将异常处理器注册到Thread中,使用Thread的UncaughtExceptionHandler进行处理
if ((handler = ueh) != null)
wt.setUncaughtExceptionHandler(handler);
在执行过程中抛出的异常都会被捕获并存储到弱引用队列中,然后在join的时候来报告抛出异常
然后就会被UncaughtExceptionHandler捕获并处理
final int doExec() {
int s; boolean completed;
if ((s = status) >= 0) {
try {
completed = exec();
} catch (Throwable rex) {
return setExceptionalCompletion(rex);
}
if (completed)
s = setCompletion(NORMAL);
}
return s;
}
public final V join() {
int s;
if ((s = doJoin() & DONE_MASK) != NORMAL)
reportException(s);
return getRawResult();
}
private void reportException(int s) {
if (s == CANCELLED)
throw new CancellationException();
if (s == EXCEPTIONAL)
rethrow(getThrowableException());
}
static void rethrow(Throwable ex) {
if (ex != null)
ForkJoinTask.<RuntimeException>uncheckedThrow(ex);
}
static <T extends Throwable> void uncheckedThrow(Throwable t) throws T {
throw (T)t; // rely on vacuous cast
}
public static ExecutorService newWorkStealingPool() {
return new ForkJoinPool(Runtime.getRuntime().availableProcessors(), ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);
}
public static ExecutorService newWorkStealingPool(int parallelism) {
return new ForkJoinPool(parallelism, ForkJoinPool.defaultForkJoinWorkerThreadFactory, null, true);
}
WorkStealingPool 任务偷取池就是使用ForkJoinPool来实现并给一些默认参数