当前位置:首页 » 编程语言 » executorjava

executorjava

发布时间: 2023-05-03 08:32:12

java ThreadPoolExecutor 线程池如何控制活动线程的执行时间,例如该线程执行时间超过30秒就自动终止

在线程开始的时候,用一个变量记录当燃陆前系统时间,线程执行完后再取一次系统时间,两个时间的差就是线程执行时间了皮磨顷。

这个类你参考一下
java.util.Timer

用这下面的TimeTask类(指定延时)

java里面的sleep()并不能精确定时,TimeTask可以:例下面的小程序:

import java.util.*;
public class test{
public static void main (String []args){
Timer timer=new Timer();/游哗/实例化Timer类
timer.schele(new TimerTask(){
public void run(){
System.out.println("退出");
this.cancel();}},30000);//这里百毫秒
System.out.println("本程序存在30秒后自动退出");
}
}

㈡ 在java中executor和executors的区别

1. Executor
它是"执行者"接口,它是来执行任务的。准确的说,Executor提供了execute()接口来执行已提交的 Runnable 任务的对象。Executor存在的目的是提供一种将"任务提交"与"任务如何运行"分李枯离开来梁扰键的机制。它只包含一个函数接口。

2. Executors
Executors是个静态工厂橡巧类。它通过静态工厂方法返回ExecutorService、ScheledExecutorService、ThreadFactory 和 Callable 等类的对象。

㈢ Java的ThreadPoolExecutor类是如何让线程进入timed_waiting状态的...

我简单看了一下代码,大概可能是这样的:
ThreadPoolExecutor里面是一个workQueue应该就是任务了。这个是BlockingQueue!所以等里面没有东西是,你再去取就会被挂起了。
ThreadPoolExecutor在getTask()的时候,会执行到这样的代码:
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
不管是poll()还是take()都会让当前线程等待。

㈣ 在java中executor是框架吗

不是框架,是java.util.concorrent包下的多线程调用类。。

㈤ 在java中executor和executors的区别

Executor使用线程池来管理线程,可以重复利用已经创建出来的线程而不是每次都必须桥扒宴新创建线程此链,节省了一部分的开销。
线程池也可以很方便的管理线程的大小和当前在执行的线程数量。
可以认为Executor是帮助自己管理Thread的一个前人帮自己封装好的工具。敏银

㈥ java executors有哪些方法

Java通过Executors提供四种线程池,分别为:
newCachedThreadPool创建一个可肢棚缓存线程池,如果线程池长睁饥余度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

(1) newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:

Java代码

packagetest;

importjava.util.concurrent.ExecutorService;

importjava.util.concurrent.Executors;

{

publicstaticvoidmain(String[]args){

悉滚=Executors.newCachedThreadPool();

for(inti=0;i<10;i++){

finalintindex=i;

try{

Thread.sleep(index*1000);

}catch(InterruptedExceptione){

e.printStackTrace();

}

cachedThreadPool.execute(newRunnable(){

publicvoidrun(){

System.out.println(index);

}

});

}

}

}

线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。

(2) newFixedThreadPool(项目用过)
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:

Java代码

packagetest;

importjava.util.concurrent.ExecutorService;

importjava.util.concurrent.Executors;

{

publicstaticvoidmain(String[]args){

=Executors.newFixedThreadPool(3);

for(inti=0;i<10;i++){

finalintindex=i;

fixedThreadPool.execute(newRunnable(){

publicvoidrun(){

try{

System.out.println(index);

Thread.sleep(2000);

}catch(InterruptedExceptione){

e.printStackTrace();

}

}

});

}

}

}


因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。
定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()

(3) newScheledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:

Java代码

packagetest;

importjava.util.concurrent.Executors;

importjava.util.concurrent.ScheledExecutorService;

importjava.util.concurrent.TimeUnit;

{

publicstaticvoidmain(String[]args){

=Executors.newScheledThreadPool(5);

scheledThreadPool.schele(newRunnable(){

publicvoidrun(){

System.out.println("delay3seconds");

}

},3,TimeUnit.SECONDS);

}

}


表示延迟3秒执行。

定期执行示例代码如下:

Java代码

packagetest;

importjava.util.concurrent.Executors;

importjava.util.concurrent.ScheledExecutorService;

importjava.util.concurrent.TimeUnit;

{

publicstaticvoidmain(String[]args){

=Executors.newScheledThreadPool(5);

scheledThreadPool.scheleAtFixedRate(newRunnable(){

publicvoidrun(){

System.out.println("delay1seconds,andexcuteevery3seconds");

}

},1,3,TimeUnit.SECONDS);

}

}


表示延迟1秒后每3秒执行一次。

(4) newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:

Java代码

packagetest;

importjava.util.concurrent.ExecutorService;

importjava.util.concurrent.Executors;

{

publicstaticvoidmain(String[]args){

=Executors.newSingleThreadExecutor();

for(inti=0;i<10;i++){

finalintindex=i;

singleThreadExecutor.execute(newRunnable(){

publicvoidrun(){

try{

System.out.println(index);

Thread.sleep(2000);

}catch(InterruptedExceptione){

e.printStackTrace();

}

}

});

}

}

}


结果依次输出,相当于顺序执行各个任务。

你可以使用JDK自带的监控工具来监控我们创建的线程数量,运行一个不终止的线程,创建指定量的线程,来观察:
工具目录:C:Program FilesJavajdk1.6.0_06injconsole.exe
运行程序做稍微修改:

Java代码

packagetest;

importjava.util.concurrent.ExecutorService;

importjava.util.concurrent.Executors;

{

publicstaticvoidmain(String[]args){

=Executors.newCachedThreadPool();

for(inti=0;i<100;i++){

finalintindex=i;

singleThreadExecutor.execute(newRunnable(){

publicvoidrun(){

try{

while(true){

System.out.println(index);

Thread.sleep(10*1000);

}

}catch(InterruptedExceptione){

e.printStackTrace();

}

}

});

try{

Thread.sleep(500);

}catch(InterruptedExceptione){

e.printStackTrace();

}

}

}

}

㈦ Java:Executor号称将任务的提交与执行解耦,从何看出来的呢

1、提交后,任务是放到一个Executor内部队列中的,Executor从这个队列中获取任务并执行。也就是说,它们之间是异步的关系。提交的成功或数咐梁失败,跟执行的成功或失败,没有关联。

2、只要任务的对象简袜是Runnable实现就薯运行了,提交时并不涉及其他额外参数,没有耦合情况。

㈧ ExecutorService和TaskExecutor的区别和使用

ExecutorService 和 TaskExecutor 都是使用线程池来管理多线程异步执行任务,他们有什么关联和区别,什么时候该用哪个呢?

ExecutorService 属于 java.util.concurrent 包下面的,继承于 java.util.concurrent.Executor ,一般使用 java.util.concurrent.Executors 工厂类创建。 Executor 和 ExecutorService 都是 JDK 5 才提供的。

TaskExecutor 属于 org.springframework.core.task 包下面,也是继承与 java.util.concurrent.Executor

Java 1.7 引入了一种新的并发框架,主要用于实现“分而治之”的算法,特别是分治之后递归调用的函数,例如 quick sort 等。
ForkJoinPool 最适合的是计算密集型的任务,如果存在 I/O,线程间同步,sleep() 等会造成线程长时间阻塞的情况时,最好配合使用 ManagedBlocker。
具体请移步至

用于延时扮稿陆或者定期执行的异步任务/线程

提供线程厅顷池执行任务

ThreadPoolTaskExecutor 同样是提供线程池执行任务,但是可以使用xml或者JavaBean的形式进行配置,初始化。同样, ThreadPoolTaskExecutor 是使用 ThreadPoolExecutor 。即也是 ThreadPoolExecutor 的Spring包装。

ThreadPoolTaskScheler 是 ScheledThreadPoolExecutor 一个spring形式的包装。Spring中任务的调度使敬尘用的就是这个类,比如 @Scheled

spring的 TaskExecutor 的两个常用实现类均是基于 Executor 实现类的包装,使其更加方便使用,更好的融入spring bean生态。

关于线程池的创建,拒绝策略,任务的执行状态,任务取消,等待任务完成等,网上资料很多了,将来有机会整理一下。

㈨ java的Executor类的性能,该怎么处理

public interface Executor

执行已提交的 Runnable 任务的对象。此接口提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节、调度等)分离开来的方法。通常使游蚂用 Executor 而不是显式地创建线程。例如,可能会使用以下方法,而不是为一组任务中的每个任务调用 new Thread(new(RunnableTask())).start():
Executor executor = anExecutor;
executor.execute(new RunnableTask1());
executor.execute(new RunnableTask2());
...

不过,Executor 接口并没有严格地要求执行是异步的。在最简单的情况下,执行程序可以在调用者的线程中立即运行已提交的任务:

class DirectExecutor implements Executor {
public void execute(Runnable r) {
r.run();
}
}
更常见的是,任务是在某个不是调用者线程的线程中执行的。以下执行程序将为每个任务生成一个新线程。

class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
}
}
许多 Executor 实现都对调度任务的方式和时间强加了某种限制。以下执行程序使任务提交与第二个执行程序保持连续,这说明了一个复合执行程序。

class SerialExecutor implements Executor {
final Queue<Runnable> tasks = new ArrayDeque<Runnable>();
final Executor executor;
Runnable active;

SerialExecutor(Executor executor) {
this.executor = executor;
}

public synchronized void execute(final Runnable r) {
tasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheleNext();
}
}
});
if (active == null) {
scheleNext();
}
}

protected synchronized void scheleNext() {
if ((active = tasks.poll()) != null) {
executor.execute(active);
}
}
}
此包中提供的 Executor 实现实现了 ExecutorService,这是一个使用更广泛的接口。ThreadPoolExecutor 类提供一个可扩展辩裤的线程池实现。Executors 类为这些 Executor 提供了便捷的工厂方法。
内存一致性效果:线程中将 Runnable 对象提交到 Executor 之前的操作 happen-before 其执携磨简行开始(可能在另一个线程中)。

从以下版本开始:
1.5

不知道楼主需要什么样的。。。。。。

㈩ java 线程池ThreadPoolExecutor 共同完成一个任务

线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行集合任务时使用的线程)的方法。每个ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。

为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展挂钩。但是,强烈建议程序员使用较为方便的 Executors 工厂方法 Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)和 Executors.newSingleThreadExecutor()(单个后台线程),它们均为大多数使用场景预定义了设置。否则,在手动配置和调整此类时,使用以下指导:

核心和最大池大小
ThreadPoolExecutor 将根据 corePoolSize(参见 getCorePoolSize())和 maximumPoolSize(参见getMaximumPoolSize())设置的边界自动调整池大小。当新任务在方法 execute(java.lang.Runnable) 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。如果运行的线程多于corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。如果设置的 corePoolSize 和 maximumPoolSize相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心和最大池大小仅基于构造来设置,不过也可以使用setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。

按需构造
默认情况下,即使核心线程最初只是在新任务需要时才创建和启动的,也可以使用方法 prestartCoreThread()或 prestartAllCoreThreads() 对其进行动态重写。

创建新线程
使用 ThreadFactory 创建新线程。如果没有另外说明,则在同一个 ThreadGroup 中一律使用Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的 NORM_PRIORITY 优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从 newThread返回 null 时 ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。
保持活动时间
如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止(参见getKeepAliveTime(java.util.concurrent.TimeUnit))。这提供了当池处于非活动状态时减少资源消耗的方法。如果池后来变得更为活动,则可以创建新的线程。也可以使用方法 setKeepAliveTime(long, java.util.concurrent.TimeUnit) 动态地更改此参数。使用 Long.MAX_VALUE TimeUnit.NANOSECONDS 的值在关闭前有效地从以前的终止状态禁用空闲线程。

排队
所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互:
A. 如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。
B. 如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。
C. 如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。

排队有三种通用策略:
直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集合时出现锁定。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙的情况下将新任务加入队列。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。
被拒绝的任务

当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法execute(java.lang.Runnable) 中提交的新任务将被拒绝。在以上两种情况下,execute 方法都将调用其RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。下面提供了四种预定义的处理程序策略:
A. 在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时 RejectedExecutionException。
B. 在 ThreadPoolExecutor.CallerRunsPolicy 中,线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。
C. 在 ThreadPoolExecutor.DiscardPolicy 中,不能执行的任务将被删除。
D. 在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。
定义和使用其他种类的 RejectedExecutionHandler 类也是可能的,但这样做需要非常小心,尤其是当策略仅用于特定容量或排队策略时。

挂钩方法
此类提供 protected 可重写的 beforeExecute(java.lang.Thread, java.lang.Runnable) 和 afterExecute(java.lang.Runnable, java.lang.Throwable) 方法,这两种方法分别在执行每个任务之前和之后调用。它们可用于操纵执行环境;例如,重新初始化ThreadLocal、搜集统计信息或添加日志条目。此外,还可以重写方法 terminated() 来执行 Executor 完全终止后需要完成的所有特殊处理。

如果挂钩或回调方法抛出异常,则内部辅助线程将依次失败并突然终止。

队列维护
方法 getQueue() 允许出于监控和调试目的而访问工作队列。强烈反对出于其他任何目的而使用此方法。remove(java.lang.Runnable) 和 purge() 这两种方法可用于在取消大量已排队任务时帮助进行存储回收。

一、例子

创建 TestThreadPool 类:
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThreadPool {

private static int proceTaskSleepTime = 2;

private static int proceTaskMaxNumber = 10;

public static void main(String[] args) {

// 构造一个线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
new ThreadPoolExecutor.DiscardOldestPolicy());

for (int i = 1; i <= proceTaskMaxNumber; i++) {
try {
String task = "task@ " + i;
System.out.println("创建任务并提交到线程池中:" + task);
threadPool.execute(new ThreadPoolTask(task));

Thread.sleep(proceTaskSleepTime);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
view plain
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class TestThreadPool {

private static int proceTaskSleepTime = 2;

private static int proceTaskMaxNumber = 10;

public static void main(String[] args) {

// 构造一个线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
new ThreadPoolExecutor.DiscardOldestPolicy());

for (int i = 1; i <= proceTaskMaxNumber; i++) {
try {
String task = "task@ " + i;
System.out.println("创建任务并提交到线程池中:" + task);
threadPool.execute(new ThreadPoolTask(task));

Thread.sleep(proceTaskSleepTime);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}

创建 ThreadPoolTask类:
view plain to clipboardprint?
import java.io.Serializable;

public class ThreadPoolTask implements Runnable, Serializable {

private Object attachData;

ThreadPoolTask(Object tasks) {
this.attachData = tasks;
}

public void run() {

System.out.println("开始执行任务:" + attachData);

attachData = null;
}

public Object getTask() {
return this.attachData;
}
}
view plain
import java.io.Serializable;

public class ThreadPoolTask implements Runnable, Serializable {

private Object attachData;

ThreadPoolTask(Object tasks) {
this.attachData = tasks;
}

public void run() {

System.out.println("开始执行任务:" + attachData);

attachData = null;
}

public Object getTask() {
return this.attachData;
}
}

执行结果:
创建任务并提交到线程池中:task@ 1
开始执行任务:task@ 1
创建任务并提交到线程池中:task@ 2
开始执行任务:task@ 2
创建任务并提交到线程池中:task@ 3
创建任务并提交到线程池中:task@ 4
开始执行任务:task@ 3
创建任务并提交到线程池中:task@ 5
开始执行任务:task@ 4
创建任务并提交到线程池中:task@ 6
创建任务并提交到线程池中:task@ 7
创建任务并提交到线程池中:task@ 8
开始执行任务:task@ 5
开始执行任务:task@ 6
创建任务并提交到线程池中:task@ 9
开始执行任务:task@ 7
创建任务并提交到线程池中:task@ 10
开始执行任务:task@ 8
开始执行任务:task@ 9
开始执行任务:task@ 10

ThreadPoolExecutor配置
一、ThreadPoolExcutor为一些Executor提供了基本的实现,这些Executor是由Executors中的工厂 newCahceThreadPool、newFixedThreadPool和newScheledThreadExecutor返回的。 ThreadPoolExecutor是一个灵活的健壮的池实现,允许各种各样的用户定制。
二、线程的创建与销毁
1、核心池大小、最大池大小和存活时间共同管理着线程的创建与销毁。
2、核心池的大小是目标的大小;线程池的实现试图维护池的大小;即使没有任务执行,池的大小也等于核心池的大小,并直到工作队列充满前,池都不会创建更多的线程。如果当前池的大小超过了核心池的大小,线程池就会终止它。
3、最大池的大小是可同时活动的线程数的上限。
4、如果一个线程已经闲置的时间超过了存活时间,它将成为一个被回收的候选者。
5、newFixedThreadPool工厂为请求的池设置了核心池的大小和最大池的大小,而且池永远不会超时
6、newCacheThreadPool工厂将最大池的大小设置为Integer.MAX_VALUE,核心池的大小设置为0,超时设置为一分钟。这样创建了无限扩大的线程池,会在需求量减少的情况下减少线程数量。
三、管理
1、 ThreadPoolExecutor允许你提供一个BlockingQueue来持有等待执行的任务。任务排队有3种基本方法:无限队列、有限队列和同步移交。
2、 newFixedThreadPool和newSingleThreadExectuor默认使用的是一个无限的 LinkedBlockingQueue。如果所有的工作者线程都处于忙碌状态,任务会在队列中等候。如果任务持续快速到达,超过了它们被执行的速度,队列也会无限制地增加。稳妥的策略是使用有限队列,比如ArrayBlockingQueue或有限的LinkedBlockingQueue以及 PriorityBlockingQueue。
3、对于庞大或无限的池,可以使用SynchronousQueue,完全绕开队列,直接将任务由生产者交给工作者线程
4、可以使用PriorityBlockingQueue通过优先级安排任务

热点内容
这个锁屏密码是什么 发布:2024-11-01 12:24:51 浏览:91
相机存储卡排名 发布:2024-11-01 12:24:49 浏览:958
androidxml格式化 发布:2024-11-01 12:23:14 浏览:164
Vb6编译是错误不知道错误代码 发布:2024-11-01 12:16:23 浏览:159
局域网电脑访问服务器怎么提速 发布:2024-11-01 12:14:09 浏览:322
美创数据库 发布:2024-11-01 12:05:45 浏览:916
你改爱奇艺密码什么意思 发布:2024-11-01 12:04:48 浏览:408
矩阵分解python 发布:2024-11-01 11:58:23 浏览:367
如何查询微信支付密码修改记录 发布:2024-11-01 11:51:57 浏览:206
如何运维gpu服务器 发布:2024-11-01 11:45:23 浏览:367