Java学习之路JAVA基础知识

JAVA基础系列(八) 多线程

2017-06-03  本文已影响32人  Acamy丶

假如一个程序有多条执行流程,那么,该程序就是多线程程序。

1.多线程概述

1.1 进程与线程

进程:
正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
线程:
是进程中的单个顺序控制流,是一条执行路径
一个进程如果只有一条执行路径,则称为单线程程序。
一个进程如果有多条执行路径,则称为多线程程序。

1.2 线程调度

CPU分配使用权的机制

线程有两种调度模型:
分时调度模型: 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
抢占式调度模型 : 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
Java使用的是抢占式调度模型。

1.3 Java程序运行原理

java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以 main方法运行在主线程中。在此之前的所有程序都是单线程的。

思考题:jvm虚拟机的启动是单线程的还是多线程的?
     答:多线程的。原因是垃圾回收线程也要先启动,否则很容易会出现内存溢出。现在的垃圾回收线程加上前面的主线程,最低启动了两个线程,所以,jvm的启动其实是多线程的。

2.Thread类

public class Thread extends Object implements Runnable
继承了Object类实现了Runnable接口

2.1 构造方法

public Thread();
public Thread(String name);
public Thread(Runnable target);
public Thread(Runnable target, String name);
public Thread(ThreadGroup group, Runnable target);
...

2.2 成员方法即线程控制

//线程休眠
public static void sleep(long millis)

//线程加入,等待该线程终止。 
public final void join()

//线程礼让,暂停当前正在执行的线程对象,并执行其他线程。
public static void yield()

//后台线程,将该线程标记为守护线程或用户线程。
//当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用
public final void setDaemon(boolean on)

//中断线程
//让线程停止,过时了,但是还可以使用。
public final void stop()
中断线程。 把线程的状态终止,并抛出一个InterruptedException。
public void interrupt()

// 设置线程优先级,范围为1-10,默认为5
// 线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。
public final void setPriority(int newPriority)

注意:
run与Start 的区别:
1.start方法
用 start方法来启动线程,是真正实现了多线程, 通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法。但要注意的是,此时无需等待run()方法执行完毕,即可继续执行下面的代码。所以run()方法并没有实现多线程。
2.run方法
run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码。

2.3 Thread.State

枚举类,表示线程运行周期中的几种状态。

**NEW **
状态是指线程刚创建, 尚未启动

**RUNNABLE **
状态是线程正在正常运行中, 当然可能会有某种耗时计算/IO等待的操作/CPU时间片切换等, 这个状态下发生的等待一般是其他系统资源, 而不是锁, Sleep等

**BLOCKED **
这个状态下, 是在多个线程有同步操作的场景, 比如正在等待另一个线程的synchronized 块的执行释放, 或者可重入的 synchronized块里别人调用wait() 方法, 也就是这里是线程在等待进入临界区

**WAITING **
这个状态下是指线程拥有了某个锁之后, 调用了他的wait方法, 等待其他线程/锁拥有者调用 notify / notifyAll 一遍该线程可以继续下一步操作, 这里要区分 BLOCKED 和 WATING 的区别, 一个是在临界点外面等待进入, 一个是在临界点里面wait等待别人notify, 线程调用了join方法 join了另外的线程的时候, 也会进入WAITING状态, 等待被他join的线程执行结束

**TIMED_WAITING **
这个状态就是有限的(时间限制)的WAITING, 一般出现在调用wait(long), join(long)等情况下, 另外一个线程sleep后, 也会进入TIMED_WAITING状态

**TERMINATED **
这个状态下表示该线程的run方法已经执行完毕了, 基本上就等于死亡了(当时如果线程被持久持有, 可能不会被回收)

几种状态之间的关系图解如下:


注意:在JDK8的API中 Thread.State 并没有RUNNING这个状态,这里是为了便于理解画在图中。

3.多线程实现的几种方式

JAVA多线程实现方式主要有三种:

3.1、继承Thread类,并重写run()方法

** Demo:**

public class SellTicketDemo {
    public static void main(String[] args) {
        // 创建三个线程对象
        SellTicket st1 = new SellTicket();
        SellTicket st2 = new SellTicket();
        SellTicket st3 = new SellTicket();

        // 给线程对象起名字
        st1.setName("窗口1");
        st2.setName("窗口2");
        st3.setName("窗口3");

        // 启动线程
        st1.start();
        st2.start();
        st3.start();
    }
}

class SellTicket extends Thread {

    // 定义100张票
    // 为了让多个线程对象共享这100张票,我们其实应该用静态修饰
    private static int tickets = 100;

    @Override
    public void run() {
        // 是为了模拟一直有票
        while (tickets > 0) {
            System.out.println(getName() + "正在出售第" + (tickets--) + "张票");
        }
    }
}

3.2、 实现Runnable接口,实现run()方法

** Demo:**

/*
 * 实现Runnable接口的方式实现
 */
public class SellTicketDemo {
    public static void main(String[] args) {
        // 创建资源对象
        SellTicket st = new SellTicket();

        // 创建三个线程对象
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

class SellTicket implements Runnable {
    // 定义100张票
    private int tickets = 100;

    @Override
    public void run() {
        while (tickets > 0) {
            System.out.println(Thread.currentThread().getName() + "正在出售第"
                    + (tickets--) + "张票");
        }
        System.out.println(Thread.currentThread().getName() + ":没票了哦!");
    }
}

3.3、使用ExecutorService、Callable、Future实现有返回结果的多线程。

其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。

** Demo:**

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableDemo {
    public static void main(String[] args) throws InterruptedException,
            ExecutionException {
        FutureTask<Integer> f1 = new FutureTask<Integer>(new MyCallable(100));
        FutureTask<Integer> f2 = new FutureTask<Integer>(new MyCallable(200));

        // FutureTask实现了两个接口,Runnable和Future
        new Thread(f1).start();
        new Thread(f2).start();

        Integer i1 = f1.get();
        Integer i2 = f2.get();

        System.out.println(i1);
        System.out.println(i2);
    }
}

class MyCallable implements Callable<Integer> {

    private int number;

    public MyCallable(int number) {
        this.number = number;
    }

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int x = 1; x <= number; x++) {
            sum += x;
        }
        return sum;
    }

}

** Demo:**使用线程池

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableDemo {
    public static void main(String[] args) throws InterruptedException,
            ExecutionException {
        // 创建线程池对象
        ExecutorService pool = Executors.newFixedThreadPool(2);

        // 可以执行Runnable对象或者Callable对象代表的线程
        Future<Integer> f1 = pool.submit(new MyCallable(100));
        Future<Integer> f2 = pool.submit(new MyCallable(200));

        // V get()
        Integer i1 = f1.get();
        Integer i2 = f2.get();

        System.out.println(i1);
        System.out.println(i2);

        // 结束
        pool.shutdown();
    }
}

class MyCallable implements Callable<Integer> {

    private int number;

    public MyCallable(int number) {
        this.number = number;
    }

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int x = 1; x <= number; x++) {
            sum += x;
        }
        return sum;
    }

}

3.4 匿名内部类方式使用多线程

new Thread(){代码…}.start();
New Thread(new Runnable(){代码…}).start();

** Demo:**

/*
 * 匿名内部类的格式:
 *      new 类名或者接口名() {
 *          重写方法;
 *      };
 *      本质:是该类或者接口的子类对象。
 */
public class ThreadDemo {
    public static void main(String[] args) {
        // 继承Thread类来实现多线程
        new Thread() {
            public void run() {
                for (int x = 0; x < 100; x++) {
                    System.out.println(Thread.currentThread().getName() + ":"
                            + x);
                }
            }
        }.start();

        // 实现Runnable接口来实现多线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int x = 0; x < 100; x++) {
                    System.out.println(Thread.currentThread().getName() + ":"
                            + x);
                }
            }
        }) .start();

        // 更有难度的
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int x = 0; x < 100; x++) {
                    System.out.println("hello" + ":" + x);
                }
            }
        }) {
            public void run() {
                for (int x = 0; x < 100; x++) {
                    System.out.println("world" + ":" + x);
                }
            }
        }.start();
    }
}

4.线程同步

4.1 同步代码块

** Demo:**

public class SellTicketDemo {
    public static void main(String[] args) {
        // 创建资源对象
        SellTicket st = new SellTicket();

        // 创建三个线程对象
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

class SellTicket implements Runnable {
    // 定义100张票
    private int tickets = 100;
    // 创建锁对象
    private Object obj = new Object();

    @Override
    public void run() {
        while (tickets > 0) {
            synchronized (obj) {
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "张票");
                }
            }

        }
        System.out.println(Thread.currentThread().getName() + ":没票了哦!");
    }
}

4.2 同步方法

** Demo:**

public class SellTicketDemo {
    public static void main(String[] args) {
        // 创建资源对象
        SellTicket st = new SellTicket();

        // 创建三个线程对象
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

class SellTicket implements Runnable {

    // 定义100张票
    private static int tickets = 100;

    private int x = 0;

    @Override
    public void run() {
        while (tickets > 0) {
            if (x % 2 == 0) {
                // 当同步方法为静态时锁对象应该为SellTicket.class
                synchronized (this) {
                    if (tickets > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()
                                + "正在出售第" + (tickets--) + "张票 ");
                    }
                }
            } else {
                sellTicket();
            }
            x++;
        }
        System.out.println(Thread.currentThread().getName() + ":没票了哦!");
    }

    // 当同步方法为静态时锁对象应该为SellTicket.class
    private synchronized void sellTicket() {
        if (tickets > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第"
                    + (tickets--) + "张票 ");
        }
    }
}

** 注意:**
1.同步代码块的锁对象是任意对象
2.同步方法的锁对象是this
3.静态同步方法的锁对象是类的字节码文件对象

4.3 Lock锁的使用

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Demo:

/*
 * Lock:
 *      void lock(): 获取锁。
 *      void unlock():释放锁。  
 * ReentrantLock是Lock的实现类.
 */

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicketDemo {
    public static void main(String[] args) {
        // 创建资源对象
        SellTicket st = new SellTicket();

        // 创建三个窗口
        Thread t1 = new Thread(st, "窗口1");
        Thread t2 = new Thread(st, "窗口2");
        Thread t3 = new Thread(st, "窗口3");

        // 启动线程
        t1.start();
        t2.start();
        t3.start();
    }
}

class SellTicket implements Runnable {

    // 定义票
    private int tickets = 100;

    // 定义锁对象
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (tickets > 0) {
            try {
                // 加锁
                lock.lock();
                if (tickets > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()
                            + "正在出售第" + (tickets--) + "张票");
                }
            } finally {
                // 释放锁
                lock.unlock();
            }
        }
        System.out.println(Thread.currentThread().getName() + ":没票了哦!");
    }

}

4.4 死锁

是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象.
如果出现了同步嵌套,就容易产生死锁问题

Demo:

/*
 * 同步的弊端:
 *      A:效率低
 *      B:容易产生死锁
 * 
 * 
 * 举例:
 *      中国人,美国人吃饭案例。
 *      正常情况:
 *          中国人:筷子两支
 *          美国人:刀和叉
 *      现在:
 *          中国人:筷子1支,刀一把
 *          美国人:筷子1支,叉一把
 */
public class DieLockDemo {
    public static void main(String[] args) {
        DieLock dl1 = new DieLock(true);
        DieLock dl2 = new DieLock(false);

        dl1.start();
        dl2.start();
    }
}

class DieLock extends Thread {

    private boolean flag;

    public DieLock(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            synchronized (MyLock.objA) {
                System.out.println("if objA");
                synchronized (MyLock.objB) {
                    System.out.println("if objB");
                }
            }
        } else {
            synchronized (MyLock.objB) {
                System.out.println("else objB");
                synchronized (MyLock.objA) {
                    System.out.println("else objA");
                }
            }
        }
    }
}

class MyLock {
    // 创建两把锁对象
    public static final Object objA = new Object();
    public static final Object objB = new Object();
}

5. 线程通信

针对同一个资源的操作有不同种类的线程

Demo:生产者消费者模型

import java.util.Vector;

/*
 * 篮子容量为5,生产者每1秒钟生产一个产品,消费者每2秒钟消费一个产品
 * 当篮子为空时消费者会等待生产者生产,当篮子装满时生产者等消费者消费
 */

public class ProcucerAndConsumerDemo {
    public static void main(String[] args) {
        // 用Vector来模拟篮子
        Vector obj = new Vector();
       // 通过构造函数来共享一个对象
        Thread consumer = new Thread(new Consumer(obj));
        Thread producter = new Thread(new Producer(obj));
        consumer.start();
        producter.start();
    }
}

class Producer implements Runnable {
    private Vector obj;

    public Producer(Vector v) {
        this.obj = v;
    }

    public void run() {
        while (true) {
            synchronized (this.obj) {
                try {
                    if (this.obj.size() > 4) {
                        System.out.println("Producter:the basked has full!");
                        this.obj.wait();
                    }
                    this.obj.add(new String("apples"));
                    System.out.println("Producter:I have produced one");
                    this.obj.notify();
                    Thread.sleep(1000);

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

class Consumer implements Runnable {
    private Vector obj;

    public Consumer(Vector v) {
        this.obj = v;
    }

    public void run() {
        while (true) {
            synchronized (this.obj) {
                try {
                    if (this.obj.size() == 0) {
                        System.out.println("Consumer:the basked is null!");
                        this.obj.wait();
                    }
                    this.obj.remove(0);
                    System.out.println("Consumer:I have taken one");
                    System.out.println("obj size: " + this.obj.size());
                    this.obj.notify();
                    Thread.sleep(2000);

                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Result:

Consumer:the basked is null!
Producter:I have produced one
Producter:I have produced one
Consumer:I have taken one
obj size: 1
Consumer:I have taken one
obj size: 0
Consumer:the basked is null!
Producter:I have produced one
Producter:I have produced one
Producter:I have produced one
Producter:I have produced one
Producter:I have produced one
Consumer:I have taken one
obj size: 4
Consumer:I have taken one
obj size: 3
Consumer:I have taken one
obj size: 2
Consumer:I have taken one
obj size: 1
Consumer:I have taken one
obj size: 0
Consumer:the basked is null!
Producter:I have produced one
Producter:I have produced one

6.线程组

把多个线程组合到一起。它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

构造方法

private ThreadGroup();
public ThreadGroup(String name);
public ThreadGroup(ThreadGroup parent, String name);

成员方法

public final void setDaemon(boolean daemon) ;
public final void interrupt();
void add(Thread t);
...

Demo:

public class ThreadGroupDemo {
    public static void main(String[] args) {
        method1();

        // method2();
    }

    private static void method2() {
        // ThreadGroup(String name)
        ThreadGroup tg = new ThreadGroup("这是一个新的组");

        MyRunnable my = new MyRunnable();
        // Thread(ThreadGroup group, Runnable target, String name)
        Thread t1 = new Thread(tg, my, "林青霞");
        Thread t2 = new Thread(tg, my, "刘意");

        System.out.println(t1.getThreadGroup().getName());
        System.out.println(t2.getThreadGroup().getName());

        t1.start();
        t2.start();

        // 通过组名称设置后台线程,表示该组的线程都是后台线程
        tg.setDaemon(true);
    }

    private static void method1() {
        MyRunnable my = new MyRunnable();
        Thread t1 = new Thread(my, "林青霞");
        Thread t2 = new Thread(my, "刘意");
        // 我不知道他们属于那个线程组,我想知道,怎么办
        // 线程类里面的方法:public final ThreadGroup getThreadGroup()
        ThreadGroup tg1 = t1.getThreadGroup();
        ThreadGroup tg2 = t2.getThreadGroup();
        // 线程组里面的方法:public final String getName()
        String name1 = tg1.getName();
        String name2 = tg2.getName();
        System.out.println(name1);
        System.out.println(name2);
        // 通过结果我们知道了:线程默认情况下属于main线程组
        // 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
        System.out.println(Thread.currentThread().getThreadGroup().getName());

        t1.start();
        t2.start();

        // 通过组名称设置后台线程,表示该组的线程都是后台线程
        Thread.currentThread().getThreadGroup().setDaemon(true);

    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + x);
        }
    }

}

7.线程池

程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法

public static ExecutorService newCachedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()

这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。

Demo:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
 * 
 * 如何实现线程池的代码呢?
 *      A:创建一个线程池对象,控制要创建几个线程对象。
 *          public static ExecutorService newFixedThreadPool(int nThreads)
 *      B:这种线程池的线程可以执行:
 *          可以执行Runnable对象或者Callable对象代表的线程
 *          做一个类实现Runnable接口。
 *      C:调用如下方法即可
 *          Future<?> submit(Runnable task)
 *          <T> Future<T> submit(Callable<T> task)
 *      D:我就要结束,可以吗?
 *          可以。
 */
public class ExecutorsDemo {
    public static void main(String[] args) {
        // 创建一个线程池对象,控制要创建几个线程对象。
        // public static ExecutorService newFixedThreadPool(int nThreads)
        ExecutorService pool = Executors.newFixedThreadPool(2);

        // 可以执行Runnable对象或者Callable对象代表的线程
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());

        // 结束线程池
        pool.shutdown();
    }
}

class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int x = 0; x < 100; x++) {
            System.out.println(Thread.currentThread().getName() + ":" + x);
        }
    }

}

8.定时器

定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。

在Java中,可以通过Timer和TimerTask类来实现定义调度的功能

//Timer
public Timer()
//在指定延迟(delay)后执行任务
public void schedule(TimerTask task, long delay)

//在指定延迟(delay)后间隔(period)执行任务
public void schedule(TimerTask task,long delay,long period)

//在指定时间(time)执行任务
public void schedule(TimerTask task, Date time)

//在指定时间( firstTime)开始间隔(period)执行任务
public void schedule(TimerTask task, Date firstTime, long period)

//TimerTask
public abstract void run()
public boolean cancel()

Demo1:

import java.util.Timer;
import java.util.TimerTask;

public class TimerDemo2 {
    public static void main(String[] args) {
        // 创建定时器对象
        Timer t = new Timer();
        // 3秒后执行爆炸任务第一次,如果不成功,每隔2秒再继续炸
        t.schedule(new MyTask2(), 3000, 2000);
    }
}

// 做一个任务
class MyTask2 extends TimerTask {
    @Override
    public void run() {
        System.out.println("beng,爆炸了");
    }
}

Demo2:

import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

/*
 * 需求:在指定的时间删除我们的指定目录(你可以指定c盘,但是我不建议,我使用项目路径下的demo)
 */


public class TimerTest {
    public static void main(String[] args) throws ParseException {
        Timer t = new Timer();
        String s = "2014-11-27 15:45:00";
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d = sdf.parse(s);

        t.schedule(new DeleteFolder(), d);
    }
}


class DeleteFolder extends TimerTask {

    @Override
    public void run() {
        File srcFolder = new File("demo");
        deleteFolder(srcFolder);
    }

    // 递归删除目录
    public void deleteFolder(File srcFolder) {
        File[] fileArray = srcFolder.listFiles();
        if (fileArray != null) {
            for (File file : fileArray) {
                if (file.isDirectory()) {
                    deleteFolder(file);
                } else {
                    System.out.println(file.getName() + ":" + file.delete());
                }
            }
            System.out.println(srcFolder.getName() + ":" + srcFolder.delete());
        }
    }
}
上一篇下一篇

猜你喜欢

热点阅读