1.JVM运行程序原理
- 由Java命令会启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个“主线程”,然后主线程去调用某个类的main方法。所以main方法运行在主线程中。在此之前的所有程序都是单线程的。
- JVM的启动是单线程还是多线程的?
- 多线程:最低启动了两个线程,用户线程 + 垃圾回收线程(先启动)
- 垃圾回收线程:先启动,斗则很容易内存溢出。
2.如何实现多线程的程序
- 线程是依赖进程存在的,应该先创建一个进程出来。
- 进程是由系统创建的,所以应该去调用系统功能创建一个进程。
- Java不能直接调用系统功能,所以没有办法直接实现多线程程序。
- 但是,可以调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后提供一些类供Java去调用。
3.创建多线程的方式(3种)
(1)继承Thread
- 步骤
- (1)自定义类MyThread继承Thread类
- (2)重写run()方法
- 为什么要重写run()方法?
- 不是类中多有代码都需要被线程执行。所以将需要被线程执行的代码放在run()中。
- 调用run()为什么是单线程的?
- 因为run()直接调用就相当于普通的方法调用,所以看到的是单线程的效果。
- 如何获取线程对象的名称? getName()
- 线程名称为什么是Thread-X编号?
- Thread类构造器中有init()方法,方法中传入参数:同步方法nextThreadNum()。
- 同步方法nextThreadNum():return threadInitNumber++;int类型,初始化从0开始。
- Thread类构造器中有init()方法,方法中传入参数:同步方法nextThreadNum()。
- 如何设置线程对象的名称呢?
- t.setName("名称");
- 如何获取main()方法所在线程对象的名称?
-
System.out.println("Thread.currentThread().getName()");
-
- 线程名称为什么是Thread-X编号?
- 为什么要重写run()方法?
- (3)启动线程
// MyThread.java
public class MyThread extends Thread {
@Override
public void run() {
// 一般来说,被线程执行的代码都是很好使的。用循环模拟
for(int i = 0; i < 100; ++i){
System.out.println(getName() + "------" + i);
}
}
}
// MyThreadTest.java
public class MyThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
(2)实现Runnable接口
- 步骤
- (1)自定义类MyRunnable实现Runnable接口
- (2)重写run()
- (3)创建MyRunnable类的对象
- (4)创建Thread类的对象,把(3)步骤的对象作为构造参数传递
- 实现接口方式的优点
- 可以避免由于Java单继承带来的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码、数据有效分离,较好的体现了面向对象的设计思想
// MyRunnable.java
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0; i < 10; ++i){
// 实现接口的方式不能直接使用Thread类的方法,但是可以间接使用
System.out.println(Thread.currentThread().getName() + "------" + i);
}
}
}
// MyRunnableTest.java
public class MyRunnableTest {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
// 方式1
// Thread t1 = new Thread(r);
// Thread t2 = new Thread(r);
// t1.setName("线程1");
// t2.setName("线程2");
// 方式2
Thread t1 = new Thread(r,"线程1");
Thread t2 = new Thread(r,"线程2");
t1.start();
t2.start();
}
}
(3)实现Callable接口(带泛型)
- 这里的泛型指的是call()的返回值类型。
- 如果需要调用线程后返回结果:用Callable方式
- 依赖于线程池而存在,故掌握前两种最重要。
/**
* 线程求和案例
*/
public 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 i = 1; i <= number; ++i){
sum += i;
}
return sum;
}
}
public class MyCallableDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
Integer i1 = f1.get();
Integer i2 = f2.get();
System.out.println(i1);
System.out.println(i2);
pool.shutdown();
}
}
4.线程调度的两种模型
-
线程调度的两种模型
-
分时调度模型:公平,无饥饿
- 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
- 抢占式调度模式(Java使用)
- 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个。优先级高的线程获取CPU时间片相对多一些。
-
- 如何设置和获取线程优先级?
- 线程默认优先级:5.
- 线程优先级范围:1~10
- java.lang.IllegalArgumentException非法参数异常:当设置优先级输入参数不在1~10范围内时,抛出此异常。
- 线程优先级别高仅仅表示线程获取CPU时间片的几率高,但是要在次数比较多时,或者多次运行时才能看到比较好的效果。
// ThreadPriority.java
public class ThreadPriority extends Thread{
@Override
public void run() {
for(int i = 0; i < 100; ++i){
System.out.println(getName() + "------" + i);
}
}
}
// ThreadPriorityTest.java
public class ThreadPriorityTest {
public static void main(String[] args) {
ThreadPriority t1 = new ThreadPriority();
ThreadPriority t2 = new ThreadPriority();
t1.setName("线程1");
t2.setName("线程2");
t1.setPriority(10);
t2.setPriority(2);
t1.start();
t2.start();
}
}
5.线程控制
- 线程休眠:静态方法 public static void sleep()
// MyThread.java
public class MyThread extends Thread {
@Override
public void run() {
for(int i = 0; i < 100; ++i){
System.out.println(getName() + "------" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// MyThreadTest.java
public class MyThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
t2.start();
t3.start();
}
}
- 等该线程执行完了,再执行其他的线程:final方法 public final void join()
// MyThreadTest.java
public class MyThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t3.setName("线程3");
t1.start();
try {
// 等线程1全部执行完了,再执行其他的线程。
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t3.start();
}
}
// 运行结果
线程1------0
线程1------1
线程1------2
线程1------3
线程1------4
线程1------5
线程1------6
线程1------7
线程1------8
线程1------9
线程2------0
线程3------0
线程2------1
线程2------2
线程2------3
线程2------4
线程2------5
线程2------6
线程3------1
线程3------2
线程3------3
线程3------4
线程3------5
线程2------7
线程3------6
线程2------8
线程3------7
线程2------9
线程3------8
线程3------9
- 礼让线程
- public static void yield()
- 暂停当前正在执行的线程对象,并执行其他的线程。
- 让多个线程的执行更加和谐了,但是不能靠他保证一人一次。
// MyThread.java
public class MyThread extends Thread {
@Override
public void run() {
for(int i = 0; i < 10; ++i){
System.out.println(getName() + "------" + i);
Thread.yield();
}
}
}
- 守护线程
- public final void setDaemon()
- 将该线程标记为守护线程或用户线程
- 当正在运行的线程都是守护线程时,JVM退出。
- 该方法必须在被守护线程前调用。
- 被守护线程死时,守护线程获取到CPU的时间片,会执行完再死掉。
// MyThreadTest.java
public class MyThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("关羽");
t2.setName("张飞");
// 设置守护线程
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
Thread.currentThread().setName("刘备");
for(int i = 0; i < 5; ++i){
System.out.println(Thread.currentThread().getName() + "------" + i);
}
}
}
// 运行结果
关羽------0
刘备------0
张飞------0
刘备------1
关羽------1
刘备------2
张飞------1
刘备------3
关羽------2
刘备------4
张飞------2
关羽------3
张飞------3
关羽------4
张飞------4
关羽------5
张飞------5
关羽------6
张飞------6
关羽------7
张飞------7
关羽------8
关羽------9
- 停止线程
- public final void stop()
- 让线程停止,后面的代码都不执行。
- 该方法已经横线划掉了,但是还可以用。
- 不建议使用,太暴力了。
// MyThread.java
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("开始执行:" + new Date());
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
System.out.println("线程被终止了!");
}
System.out.println("结束执行:" + new Date());
}
}
// MyThreadTest.java
public class MyThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
// 超过3s,就中断线程
try {
Thread.sleep(3000);
t1.stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 运行结果
开始执行:Wed Apr 28 15:06:51 CST 2021
- 中断线程
- public void interrupt()
- 把线程的状态终止,并抛出一个InterruptedException异常。
// MyThreadTest.java
public class MyThreadTest {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start();
// 超过3s,就中断线程
try {
Thread.sleep(3000);
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 运行结果
开始执行:Wed Apr 28 15:07:46 CST 2021
线程被终止了!
结束执行:Wed Apr 28 15:07:49 CST 2021
6.线程生命周期图5个 线程状态转换图7个
- 新建:创建线程对象
- 就绪:有执行资格,没有执行权
- 运行:有执行资格,有执行权
- 阻塞:没有执行资格,没有执行权。(同步阻塞/等待阻塞/其他阻塞)由于一些操作让线程处于了该状态。另外一些操作可以把它激活,激活后处于就绪状态。
- 死亡:线程对象变成垃圾,等待被回收
7.多线程练习
卖电影票
- 某电影院正在上映一部电影,共有100张票,有3个售票窗口,请设计一个程序模拟该电影院售票。
- 继承Thread类方式:不合理
- 使用Runnable接口的方式:更好的将数据与代码分离
线程安全问题
- 出现线程安全的原因
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
- 解决思想:
- 针对第三条,前两条不能处理
- 把多条语句操作共享数据的代码给包成一个整体,让某个线程执行时,别的线程不能执行——同步机制
- 同步机制
- 特点:多个线程使用的是同一个锁对象。
- 优点:解决了多线程的安全问题。
- 缺点:当线程特别多时,每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
同步机制实现1
- 同步的两种方式
- 同步代码块
- 同步方法
- synchronized(对象){需要同步的代码;}
- 对象:锁,多个线程必须是同一把锁
- 同步代码块的锁对象可以是任意的
- 需要同步的代码:多条语句操作共享数据的代码
// SellTicket.java
public class SellTicket implements Runnable {
// 共享资源
private int tickets = 100;
// 锁对象可以是任意的
private Object obj = new Object();
// private Test obj = new Test();
@Override
public void run() {
// 为了模拟一直有票
while(true){
if(tickets > 0){
synchronized (obj){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票");
}
}
}
}
class Test{}
}
// SellTicketDemo.java
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();
}
}
-
- 同步方法:把synchronized加在方法上
- 同步方法的锁对象:this
-
// SellTicket.java public class SellTicket implements Runnable { // 共享资源 private int tickets = 100; @Override public synchronized void run() { // 为了模拟一直有票 while(true){ if(tickets > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票"); } } } }
- 静态方法的锁对象:类的字节码文件对象。
- synchronized SellTicket.class
- 同步方法:把synchronized加在方法上
- 线程安全的类
- StringBuffer
- Hashtable
- Vector
- 即是线程安全,也不用它,效率太低
- 一般用
List<String> list = Collections.synchronizedList(new ArrayList<String>());
同步机制实现2
- Lock是一个接口
- 可以明确的看到什么时候加锁,什么时候放锁
- void lock()
- void unlock()
- 实现类:ReentrantLock
// SellTicket.java
public class SellTicket implements Runnable {
// 共享资源
private int tickets = 100;
// 创建锁对象
private Lock lock = new ReentrantLock();
@Override
public void run() {
// 为了模拟一直有票
while(true){
try {
// 加锁
lock.lock();
if(tickets > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票");
}
} finally {
// 如果发生异常可以不用catch,但是一定要把锁释放掉
lock.unlock();
}
}
}
}
// SellTicketDemo.java
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();
}
}
- 同步的弊端
- 效率低
- 容易产生死锁
- 两个或者两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象。
- 面试题:写一个死锁Demo(圆桌上人吃饭餐具互相持有一个且等待的问题)
-
public class MyLock { // 创建两个锁对象,模拟一副筷子的两只 public static final Object objA = new Object(); public static final Object objB = new Object(); } public 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"); } } } } } public class DieLockDemo { public static void main(String[] args) { DieLock dl1 = new DieLock(true); DieLock dl2 = new DieLock(false); dl1.start(); dl2.start(); } }
生产者消费者模型
- 线程间通信问题:不同种类的线程(生产者线程、消费者线程)间针对同一个资源的操作。
- 生产和消费的应该是用一个资源——线程通信
- 实现:在外界把这个数据生产出来,通过构造方法传递给生产线程和消费线程。
- 线程安全问题:生产者和消费者加的锁必须是同一把。
- 生产和消费的应该是用一个资源——线程通信
public class Student {
String name;
int age;
}
public class SetThread implements Runnable{
private Student s;
private int x = 0;
public SetThread(Student s){
this.s = s;
}
@Override
public void run() {
while(true){
synchronized (s){
if (x % 2 == 0){
s.name = "林青霞";
s.age = 27;
} else {
s.name = "刘意";
s.age = 30;
}
x++;
}
}
}
}
public class GetThread implements Runnable{
private Student s;
public GetThread(Student s){
this.s = s;
}
@Override
public void run() {
while(true){
synchronized (s){
System.out.println(s.name + "------" + s.age);
}
}
}
}
/**
* 分析:
* 资源类:Student
* 设置学生数据:SetThread(生产者)
* 获取学生数据:GetThread(消费者)
* 测试类:StudentDemo
*/
public class StudentDemo {
public static void main(String[] args) {
// 创建资源
Student s = new Student();
// 生产和消费的类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
// 创建线程
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
// 启动线程
t1.start();
t2.start();
}
}
- 线程的等待唤醒机制:生产者有生产出来的东西,才能让消费者去消费。(没有东西可以消费时,消费者必须等待)
- Object类中的:
- 等待:wait()
- 唤醒:
- notify():唤醒单个线程
- notifyAll():唤醒所有线程
- 为什么这些方法不定义在Thread类中呢?
- 这些方法的调用必须通过锁对象(可以是任意的)调用,所以必须在Object类中。
- Object类中的:
public class Student {
String name;
int age;
boolean flag;
}
public class SetThread implements Runnable{
private Student s;
private int x = 0;
public SetThread(Student s){
this.s = s;
}
@Override
public void run() {
while(true){
synchronized (s){
// 判断有没有生产好的
if(s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (x % 2 == 0){
s.name = "林青霞";
s.age = 27;
} else {
s.name = "刘意";
s.age = 30;
}
x++;
// 修改标记,并唤醒线程
s.flag = true;
s.notify();
}
}
}
}
public class GetThread implements Runnable{
private Student s;
public GetThread(Student s){
this.s = s;
}
@Override
public void run() {
while(true){
synchronized (s){
if(!s.flag){
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "------" + s.age);
// 修改标记,并唤醒线程
s.flag = false;
s.notify();
}
}
}
}
public class StudentDemo {
public static void main(String[] args) {
// 创建资源
Student s = new Student();
// 生产和消费的类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
// 创建线程
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
// 启动线程
t1.start();
t2.start();
}
}
- 改进版
- 将Student的成员变量私有化
- 把设置和获取的操作封装成了功能,并同步
- 设置和获取的线程中只需要调用方法即可
public class Student {
private String name;
private int age;
private boolean flag;
public synchronized void set(String name, int age){
// 生产者:有数据就等待
if (this.flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 设置数据
this.name = name;
this.age = age;
// 修改标记
this.flag = true;
this.notify();
}
public synchronized void get(){
// 消费者:没有数据就等待
if(!this.flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 获取资源
System.out.println(this.name + "------" + this.age);
// 修改标记
this.flag = false;
this.notify();
}
}
public class SetThread implements Runnable{
private Student s;
private int x = 0;
public SetThread(Student s){
this.s = s;
}
@Override
public void run() {
while(true){
if (x % 2 == 0){
s.set("林青霞", 27);
} else {
s.set("刘意", 30);
}
x++;
}
}
}
public class GetThread implements Runnable{
private Student s;
public GetThread(Student s){
this.s = s;
}
@Override
public void run() {
while(true){
s.get();
}
}
}
public class StudentDemo {
public static void main(String[] args) {
// 创建资源
Student s = new Student();
// 生产和消费的类
SetThread st = new SetThread(s);
GetThread gt = new GetThread(s);
// 创建线程
Thread t1 = new Thread(st);
Thread t2 = new Thread(gt);
// 启动线程
t1.start();
t2.start();
}
}
8.线程池
- 最麻烦的:线程池的大小
- 压力测试
- 并发访问测试
- Executors工厂类:产生线程池
-
newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。(线程最大并发数不可控制);线程池为无限大,当执行第二个任务时若第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
-
newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
-
可以指定线程池数量
-
可以执行Runnable或者Callable对象代表的线程
-
调用如下方法即可
-
Future<?> submit(Runnable task) <T> Future<T> submit(Callable<T> task)
public class MyCallable implements Callable { @Override public Object call() throws Exception { for(int i = 0; i < 100; ++i){ System.out.println(Thread.currentThread().getName() + "------" + i); } return null; } } public class ExcutorsDemo { public static void main(String[] args) { // 创建线程池 ExecutorService pool = Executors.newFixedThreadPool(2); // 创建两个匿名的新线程传进去 pool.submit(new MyRunnable()); pool.submit(new MyRunnable()); // 结束线程池 pool.shutdown(); } }
-
-
newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行、延迟执行。
-
newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
-
public static ExecutorService newCachedThreadPool(){}
public static ExecutorService newFixedThreadPool(){}
public static ExecutorService newScheduledThreadPool(){}
public static ExecutorService newSingleThreadPool(){}
- 匿名内部类的方式实现多线程程序
- 创建的线程太多了,化简
- 匿名内部类的格式
- 本质:使该类或者接口的子类对象
new 类名或者接口名(){
重写方法;
};
public class ThreadDemo {
public static void main(String[] args) {
// 方式1
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; ++i){
System.out.println(Thread.currentThread().getName() + "------" + i);
}
}
}.start();
// 方式2
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; ++i){
System.out.println(Thread.currentThread().getName() + "------" + i);
}
}
}).start();
// 更有难度的
// 两个run()执行哪个? 执行Thread的World
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; ++i){
System.out.println("Hello" + "------" + i);
}
}
}){
@Override
public void run() {
for (int i = 0; i < 100; ++i){
System.out.println("World" + "------" + i);
}
}
}.start();
}
}
- 定时器
- 可用于调度多个不定时任务以后台线程的方式执行
- 实现
- java.util.Timer:定时器类
- java.util.TimerTask:(抽象类)任务类
public class TimerDemo {
public static void main(String[] args) {
// 创建定时器对象
Timer t = new Timer();
// 3s后执行任务
t.schedule(new MyTask(t), 3000);
// // 3s后执行任务第一次,如果不成功,每隔2s再执行一次
// t.schedule(new MyTask(t), 3000,2000);
}
}
class MyTask extends TimerTask{
private Timer t;
public MyTask(){}
public MyTask(Timer t){
this.t = t;
}
@Override
public void run() {
System.out.println("该任务需要执行的操作");
// 任务执行完了要结束,不然会一直执行
t.cancel();
}
}
import java.io.File;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
/**
* 在指定的时间删除我们指定目录
*/
public class DeleteDir {
public static void main(String[] args) throws ParseException {
Timer t = new Timer();
String s = "2021-04-30 18:00:00";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-mm-dd HH:mm:ss");
Date d = simpleDateFormat.parse(s);
t.schedule(new DeleteFolder(), d);
}
}
class DeleteFolder extends TimerTask{
@Override
public void run() {
File file = new File("demo");
deleteFolder(file);
}
/**
* 递归删除文件夹及其子文件夹和子文件
* @param file
*/
public void deleteFolder(File file){
File[] files = file.listFiles();
if(files != null){
for(File i : files){
if(i.isDirectory()){
deleteFolder(i);
} else {
System.out.println(i.getName() + "------" + file.delete());
}
}
System.out.println(file.getName() + "------" + file.delete());
}
}
}
9.多线程面试题
- sleep() 和 wait() 方法的区别
- sleep():必须指定时间,不释放锁
- wait():可以不指定时间,也可以指定时间,释放锁
- run() 和 start() 的区别?
- run():仅仅是封装需要被执行的代码,直接调用是普通方法。
- start():首先启动了线程,然后再由JVM去调用该线程的run()。
- java.lang.IllegalThreadStateException 非法线程状态异常?
- 同一个线程启动两次,会报这个错误。
- 实现多线程,需要创建两个对象,分别调用他们的start()。