一、进程和线程的概念
Thread和Runnable的区别:
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
main函数,实例化线程对象也有所不同,
extends Thread :t.start();
implements Runnable : new Thread(t).start();
总结:
实现implements Runnable接口比继承 extends Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制(不能访问父类的私有成员?)
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4):线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
二、线程的创建
1.继承Thread类
/**
* 线程的创建-继承Thread类
*/
public class Demo01 {
public static void main(String[] args) {
//创建线程对象
Thread t1 = new MyThread01();
//启动线程
//启动线程应该使用start方法,run方法虽然可以执行具体逻辑,但是不会创建新的线程对象
//而start方法在执行run方法的同时也会一个开辟一个新的线程,而这正是多线程所需要的
t1.start();
}
}
/**
* 定义一个类,用来表示线程,该类需要实现Thread类
*/
class MyThread01 extends Thread {
@Override
public void run() {
System.out.println("这是一个线程");
}
2.实现Runnable接口
/**
* 线程对象的创建--实现Runnable接口
*/
public class Demo02 {
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread02());
//也通过start去启动线程
t1.start();
//也可以通过匿名内部类实现Runnable接口
Thread t2=new Thread(new Runnable(){
@Override
public void run() {
System.out.println("通过匿名内部类创建的线程...");
}
});
t2.start();
}
}
class MyThread02 implements Runnable {
@Override
public void run() {
System.out.println("这是第二个线程...");
}
}
3.返回线程的标识符
/**
* long getId()
* 返回该线程的标识符
*/
public class Demo05 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
}
});
t1.start();
System.out.println(t1.getId());
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
}
});
t2.start();
//由于虚拟机在启动时会创建很多其它的线程
//所以在之前的id都被这些线程给占据了
//自己创建的线程的编号就不是从1开始的了
System.out.println(t2.getId());
}
}
4.获取当前代码块片段的线程
/**
* Thread.currentThread()
* 获取运行当前代码片段的线程
*/
public class Demo04 {
public static void main(String[] args) {
//Thread[main,5,main]
//main-表示线程名,main表示首要线程或主要线程
//5-表示线程的优先级
//main-表示的是当前线程所属的线程组
System.out.println(Thread.currentThread());
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//通过start方法启动线程会开启新的线程对象,此时执行该代码块的线程即为新建的线程
System.out.println("t1 run方法中的线程:" + Thread.currentThread());
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t2 run方法中的线程:" + Thread.currentThread());
}
});
t2.start();
//由于method01方法是直接在主方法中调用的,并没有开辟新的线程对象
//所以执行该方法的线程依然是主线程
method01();
//由于method02方法是在main线程中直接调用的,而method02中通过run方法调用
//并没有开启新的线程
method02();
}
public static void method01() {
System.out.println("方法1中的线程:" + Thread.currentThread());
}
public static void method02() {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("方法2中的线程:" + Thread.currentThread());
}
}).run();
}
}
5.获取/设置线程的名字
/**
* String getName()/void setName(String threadName)
* 获取/设置线程名
*/
public class Demo06 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程名:" + Thread.currentThread().getName());
}
});
t1.setName("线程1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程名:" + Thread.currentThread().getName());
}
},"线程2");
t2.start();
}
}
6.测试线程是否处于活跃状态
/**
* boolean isAlive()
* 测试线程是否处于活动状态
*/
public class Demo07 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("这是一个线程...");
}
});
//线程刚刚实例化以后,处于新建状态
System.out.println("1:" + t1.isAlive());
//新建状态的线程不具备竞争时间片的能力
//通过start方法使得线程成为就绪状态
t1.start();
//就绪状态的线程是一种活动状态,它可能因为竞争到时间片而执行
System.out.println("2:" + t1.isAlive());
}
}
7.当前线程休眠指定毫秒数
/**
* void sleep(long millis)
* 当前线程休眠指定毫秒数
*/
public class Demo08 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "->" + i);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}, "线程2").start();
}
}
8.等待当前代码的结束
/**
* void join()
* 该方法用于等待当前线程的结束
*/
public class Demo09 {
public static void main(String[] args) {
Thread downLoadThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("已经下载了:" + (i * 10) + "%");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
downLoadThread.start();
Thread showThread = new Thread(new Runnable() {
@Override
public void run() {
//保证在downLoadThread执行完毕以后再执行当前线程
try {
downLoadThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("显示图片");
}
});
showThread.start();
}
}
9.守护线程
/**
* void setDaemon(boolean isDaemon)
* 当参数为true时,该线程为守护线程(在调用start方法之前,否则,会有异常)
* 守护线程
* 当进程中只剩下守护线程时,所有守护线程强制终止。
* GC与守护线程
* Java垃圾回收线程就是一个典型的守护线程,当我们的程序中不再有任何运行中的Thread,程序就不会再产生垃圾,
* 垃圾回收器也就无事可做,所以当垃圾回收线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动离开。
*/
public class Demo10 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("守护线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//2秒后主线程结束,而此时只剩下t1这个守护线程,守护线程会在没有其它线程的时候自动退出
t1.setDaemon(true);
t1.start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程结束了");
}
}
10.获取线程的状态
/**
* State getState():获取线程的状态
* NEW
* 线程实例化后还从未执行start()方法时的状态
* RUNNABLE:
* runnable状态是线程进入运行的状态
* BLOCKED:
* 某一个线程在等待锁的时候的状态
* WAITING
* 是线程执行了Object.wait()方法后所处的状态
* TIMED_WAITING
* 代表线程执行了Thread.sleep()方法,呈等待状态,等待时间到达,继续向下运行。
* TERMINATED
* 终结
*/
public class Demo11 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程执行...");
//如果加了这一段,则main线程执行完毕,t1依然处于休眠状态中
//所以此时t1状态为TIMED_WAITING
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
});
System.out.println(t1.getState());
t1.start();
System.out.println(t1.getState());
//对main线程做休眠操作
Thread.sleep(2000);
//main线程,t1线程,两者是并发执行的
//t1在执行的过程中,main也在执行,当main线程等到时,t1已经执行完毕
//所以main休眠以后,t1是死亡状态
System.out.println(t1.getState());
}
}
11.
三、线程中相关的API
线程的两种调度方式
(1)分时调度模型。所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片。
(2)抢占式调度模型。优先让优先级高的线程使用CPU,若相同,则随机选择,优先级高的线程获取CPU的时间片相对多一些。
Java使用的是抢占式调度模型。
优先级
线程的优先级划分为1-10级,其中1最低,10最高,线程提供了3个常量来表示最低、最高以及默认优先级。线程的调度是由线程调度控制的,可以通过提高线程的优先级来最大程度改善线程获取时间片的几率。但不一定就能保证优先级高的就先获得时间片。
join方法和yield方法的区别
void join()
该方法用于等待当前线程的结束
void yield()
该方法用于使当前线程主动让出当次CPU时间片回到就绪状态,等待时间片分配。使得多个线程的执行更和谐,但是保证不了严格交替执行。
线程的状态转换图
新建状态:线程对象创建完毕以后,还没调用start方法之前,此时的线程不具备任何竞争时间片资源的能力
就绪状态:新建状态的线程通过start方法启动后得到的状态,此时的线程可以竞争时间片,如果竞争到时间片则进入运行状态,如果失去时间片则进入就绪状态
死亡状态:run方法执行完毕(正常死亡),出现异常或错误(非正常死亡)
阻塞状态:
- 运行过程中遇到了IO输入操作,例如Scanner对象中的nextxxx方法,只有当方法返回结果以后,程序才能继续运行。
- sleep方法运行过程种,只有等到休眠时间到了以后才能继续运行。
- 同步锁:
- 等待通知:
线程并发安全问题
线程并发安全问题的成因
多线程并发操作访问同一个临界资源,例如:有一个静态变量,有两个线程,一个对其加1,一个对其加5,分别执行5次,执行过程中如果两个线程并发执行,则有可能同时对其进行修改,最后可能希望1先走,5再走,也有可能希望5再走,1再走。
Int a=0;
a=5
a=25
并发执行时,有可能1走一次,接下来就是5走了,……顺序不确定,和最后的结果可能不同。
临界资源:共享的变量(实例变量、静态变量)
线程并发安全问题的解决方案
默认多线程是异步执行的
异步执行改为同步执行
异步:同时并发执行,各干各的
同步:有顺序地执行,你干完我再干
具体解决方案
- synchronized同步锁
synchronized是Java语言内置的语义锁
synchronized是Java中的同步锁,提供内置锁机制来支持原子性。
可以用在代码块和方法上
每个java对象都可以用作一个同步锁,线程进入同步代码块之前自动获得锁,并且在退出同步代码块时自动释放锁,无论是正常还是通过抛异常,都一样,获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。
(1)同步代码块
synchronized(锁对象){
}
注意事项:
A.使用synchronied时需要对一个对象上锁以保证线程同步,这个对象应该注意:多个需要同步的线程在访问该同步块时,看到的应该是同一个对象的引用,否则达不到同步效果,通常用this作为锁对象。
B.在使用同步块时,应当尽量在允许的情况下减少同步范围,以提高开发的执行效率。
public class Demo13 {
public static void main(String[] args) {
// 此时的this代表的对象就是new MyThread()对象,
// MyThread myThread = new MyThread();
// 对于线程而言,myThread就是同一个对象,所以选择的this对象锁也就是同一个对象
// Thread t1 = new Thread(myThread, "线程1");
// Thread t2 = new Thread(myThread, "线程2");
// 对于两个线程而言,执行了两次new MyThread()对象,所以this对象锁不是同一个对象,因此
// 无法做到同步
Thread t1 = new Thread(new MyThread(), "线程1");
Thread t2 = new Thread(new MyThread(), "线程2");
t1.start();
t2.start();
}
}
class MyThread implements Runnable {
// 如果是不同的MyThread对象,objA也不是同一个对象,无法同步
private Object objA = new Object();
// 对于静态变量而言,不管是不是同一个线程,一定是同一个对象(因为静态变量在全局只有一份)
private static Object objB = new Object();
@Override
public void run() {
// synchronized后面要求指定锁对象
// synchronized将代码锁定,一个线程在执行的过程中,另外一个线程就必须等待前面一个线程执行完毕,将锁对象释放才能执行
// 锁对象的选取:Java中的任何对象都能作为锁对象(语法层面)
// 对于多个线程而言,锁对象必须是同一个对象
// this:
// 实例变量:
// 对于前两者而言,一定要关注不同线程,this或实例变量是不是同一个对象
// 静态变量:
// 类名.class:获取类信息,无论哪个类,在虚拟机加载了以后,其类信息也是全局唯一的
// 对于后两者而言,是一种全局锁,由于都是静态的,所以对于多个线程而言,一定是同一个锁对象
synchronized (objB) {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "->" + i);
}
}
}
}
(2)同步方法
可以使用synchronized关键字修饰方法
注意:
A.接口方法,构造方法都不能使用synchronized关键字修饰
B.在继承关系中,synchronized如果修饰父类方法,子类默认是不继承这个关键字的,如果子类方法需要同步,可以自行添加该关键字
C.在一个不同步的方法中,调用了一个同步方法,则原来不同步的方法此时也可以按照同步方法的逻辑来执行
**
* 修饰方法
*
* @author Administrator
*/
public class Demo14 {
public static void main(String[] args) {
MyThread04 myThread04 = new MyThread04();
Thread t1 = new Thread(myThread04, "线程1");
Thread t2 = new Thread(myThread04, "线程2");
t1.start();
t2.start();
}
}
class MyThread04 implements Runnable {
@Override
public void run() {
method02();
}
/**
* 如果修饰的是实例方法,则同步锁默认就是类似于this这种和实例相关的对象 ,所以同步方法和同步代码块相比
* <p>
* 其实只是少了锁的选取,并且同步的范围从代码块变成了整个方法
*/
public synchronized void method01() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "->" + i);
}
}
/**
* 如果修饰的是静态方法,则同步锁默认就是类似于类信息或静态变量等对象,由于全局是唯一的,所以无论何种情况,都可以做到同步
*/
public synchronized static void method02() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "->" + i);
}
}
}
2.lock
Lock锁属于Java中的编程式锁,和synchronized最大的不同之处在于,Lock锁的获取、释放都可以通过代码来进行控制,而synchronized同步锁中,锁的获取和释放都是通过Jvm虚拟机自行完成的。
ReentrantLock是Lock接口一种常见的实现,该锁在同一时刻只允许一个线程来访问,
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo15 {
public static void main(String[] args) {
MyThread03 myThread03 = new MyThread03();
Thread t1 = new Thread(myThread03, "线程1");
Thread t2 = new Thread(myThread03, "线程2");
t1.start();
t2.start();
}
}
class MyThread03 implements Runnable {
/**
* 由于上锁和解锁对应的是同一把锁,所以不能将其定义在方法中, 因为方法中定义了以后就是局部变量了,
*
* 这样在两个线程执行的时候,对应的其实是两个不同的锁对象,所以应该定义为全局的
*/
private static Lock lock = new ReentrantLock();
@Override
public void run() {
method();
}
public void method() {
// 获取锁
lock.lock();
try {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "->" + i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 无论正常与否都要释放锁--synchronized中也是如此
// 只不过synchronized是自动释放的
// 如果锁没有正确释放,会发生死锁现象
// 面试官:告诉我什么叫死锁,我就录用你!
// 程序猿:你录用我,我就告诉你什么叫死锁!
lock.unlock();
}
}
}
synchronized和Lock锁的区别
- synchronized是内置语义锁,Lock锁是JDK1.5后提供的编程锁
- synchronized锁的获取和释放自动完成,而Lock锁需要手动操作
- Lock锁可以知道锁对象有没有获取成功
-
死锁案例
public class Demo16 {
private static Object objA = new Object();
private static Object objB = new Object();
public static void main(String[] args) {
/***
* 正常情况下,两个线程按照顺序依次执行出来,但是,也有特殊情况:
*
* 两个线程是并发执行的
*
* 第一个线程在执行的过程中占据objA锁,第二个线程在执行过程中占据objB锁
*
* 对于第一个线程而言,此时如果要继续执行需要获取到objB锁,而第二个线程中只有当代码执行完毕才会释放objB
*
* 对于第二个线程而言,此时如果要继续执行需要获取到objA锁,
*
* 而此时第一个线程由于处于等到objB锁的状态,所以不会往下执行,自然也不可能释objA
*
* 两个线程之间都在等待彼此释放锁,此时出现死锁现象
*/
new Thread(new Runnable() {
@Override
public void run() {
synchronized (objA) {
System.out.println(1);
synchronized (objB) {
System.out.println(2);
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (objB) {
System.out.println(3);
synchronized (objA) {
System.out.println(4);
}
}
}
}).start();
}
}
import java.util.concurrent.Executors;
/**
* 死锁
*
* @author Administrator
*/
public class Demo16 {
private static Object objA = new Object();
private static Object objB = new Object();
public static void main(String[] args) {
/***
* 正常情况下,两个线程按照顺序依次执行出来,但是,也有特殊情况:
*
* 两个线程是并发执行的
*
* 第一个线程在执行的过程中占据objA锁,第二个线程在执行过程中占据objB锁
*
* 对于第一个线程而言,此时如果要继续执行需要获取到objB锁,而第二个线程中只有当代码执行完毕才会释放objB
*
* 对于第二个线程而言,此时如果要继续执行需要获取到objA锁,
*
* 而此时第一个线程由于处于等到objB锁的状态,所以不会往下执行,自然也不可能释objA
*
* 两个线程之间都在等待彼此释放锁,此时出现死锁现象
*/
new Thread(new Runnable() {
@Override
public void run() {
synchronized (objA) {
System.out.println(1);
synchronized (objB) {
System.out.println(2);
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (objB) {
System.out.println(3);
synchronized (objA) {
System.out.println(4);
}
}
}
}).start();
}
}
3、线程间的通讯方式wait()/notify()/notifyAll()
前面的同步操作只能保证多线程并发执行的过程中,一个线程在执行时,其它线程处在等待状态,使得多线程在执行过程中有一个先后顺序。但是同步操作中并不能确定执行顺序中具体的顺序。
当多线程协同工作时,可以通过线程通讯API来完成线程之间通讯。
wait()
用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用wait()之前,线程必须要获得该对象的对象级别锁
notify()
用于唤醒一个处于休眠状态的线程,该方法用来通知那些可能等待该对象的对象锁的其他线程。如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知。
notifyAll()
notifyAll使所有原来在该对象上wait的线程统统退出wait的状态,即全部被唤醒,不再等待。
/**
* 1:1-5
* 2:6-10
* 3:11-15
* 1:16-20
* ……
* n:71-75
*/
public class Homework02 {
private static int num = 0;
/**
* 当前正在使用的线程
**/
private static int curThread = 1;
private static Object obj = new Object();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
print(1, 2);
}
}, "线程1").start();
new Thread(new Runnable() {
@Override
public void run() {
print(2, 3);
}
}, "线程2").start();
new Thread(new Runnable() {
@Override
public void run() {
print(3, 1);
}
}, "线程3").start();
}
/**
* 用来打印数字的方法
*
* @param now 当前使用哪个线程来打印
* @param next 当前线程执行完毕以后再由哪个线程来执行操作
*/
public static void print(int now, int next) {
while (num <= 75) {
synchronized (obj) {
//限定一下当前应该打印数字的线程
while (curThread != now) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1; i <= 5; i++) {
num++;
System.out.println(Thread.currentThread().getName() + "->" + num);
}
if (num == 75) {
//break;
System.exit(0);
}
curThread = next;
obj.notifyAll();
}
}
}
}
/**
* wait()
* 用来将当前线程置入休眠状态,直到接到通知或被中断为止。在调用wait()之前,线程必须要获得该对象的对象级别锁
* notify()
* 用于唤醒一个处于休眠状态的线程,该方法用来通知那些可能等待该对象的对象锁的其他线程。
* 如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知。
* notifyAll()
* notifyAll使所有原来在该对象上wait的线程统统退出wait的状态,即全部被唤醒,不再等待。
* <p>
* <p>
* <p>
* 上述三个方法需要在同步代码块中进行调用
*/
public class Demo17 {
public static void main(String[] args) {
//定义一个对象锁
Object obj = new Object();
Thread downLoadThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println("已经下载了:" + (i * 10) + "%");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized (obj) {
//随机唤醒一个线程
// obj.notify();
//唤醒所有的线程
obj.notifyAll();
}
}
});
downLoadThread.start();
Thread showThread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("显示图片");
}
});
showThread.start();
}
}
wait和sleep的区别?
wait是object类中的方法,任何对象都能调用,sleep只能被Thread调用
wait可以是任意对象来调用,sleep只能当前线程调用
wait可以释放对象锁,sleep保留对象锁
wait可以通过notify随时唤醒,sleep只能等待设定时间结束后自然唤醒,否则将引发异常
wait必须在同步方法或同步块中进行调用,sleep可以在任意位置调用
生产者消费者模式
生产者消费者
import java.util.LinkedList;
import java.util.Queue;
/**
* 生产者、消费者模式
*/
public class Homework01 {
public static void main(String[] args) {
//缓冲区
Queue<Integer> queue = new LinkedList<>();
//生产者线程
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 4; i++) {
synchronized (queue) {
//假定有一个元素就视作满的情况
//如果缓冲区不为空,则不需要添加元素,如果缓冲区为空,需要添加元素
while (queue.size() == 1) {
System.out.println("通知消费者进行消费...");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产了:" + i);
queue.offer(i);
//当缓冲区中有元素时,唤醒消费者
queue.notifyAll();
}
}
}
}, "生产者").start();
//消费者线程
new Thread(new Runnable() {
@Override
public void run() {
//消费者被唤醒后才开始消费,此前不知道会被唤醒多少次
while (true) {
synchronized (queue) {
while (queue.size() == 0) {
System.out.println("缓冲区为空,等待生产者生产...");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费操作
int num = queue.poll();
//通知生产者进行生产
queue.notifyAll();
System.out.println("消费了元素:" + num);
if (num == 4) {
break;
}
}
}
}
}, "消费者").start();
}
}
线程池
线程池的作用
对于大量的线程如果要执行,在不使用线程池的时候,需要创建大量的线程对象,这些对象在任务执行完毕以后被销毁。
存在的问题:构建了大量的线程对象、对象的频繁创建和销毁会耗费大量的资源,影响执行效率。
有了线程池以后,首先先创建一个集合,其中预先就创建一些线程对象放入这个集合,当有请求需要使用线程时,从线程池中获取一个空闲的线程来执行任务,当任务执行完毕以后,将这个线程对象归还到线程池中(不是销毁) ,等到下一次再有要使用线程的请求时,将这个线程取出再次使用。
优点:控制线程数量、线程对象的重用
- 常用线程池的实现
1、newCachedThreadPool
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
**
* newCachedThreadPool
* 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,
* 若无可回收,则新建线程。线程池为无限大,当执行第二个任务时第一个任务已经完成,
* 会复用执行第一个任务的线程,而不用每次新建线程。
*/
public class Demo18 {
public static void main(String[] args) {
//创建了线程池对象
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程1:" + Thread.currentThread().getName());
//两个线程并发执行时,有可能第一个线程还没执行完,第二个线程已经开始执行
//此时第二个线程执行时需要创建一个新的线程对象,有可能两次分配不同的线程
//也有可能第一个线程执行完毕以后,第二个线程在再来执行,有可能得到相同的线程
//如果加了try以后,第二个线程执行时,第一个线程一定还没执行完毕,所以一定会分配出一个新的线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程2:" + Thread.currentThread().getName());
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程3:" + Thread.currentThread().getName());
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程4:" + Thread.currentThread().getName());
}
});
//在合理的时机去关闭线程池
executorService.shutdown();
}
}
2、newFixedThreadPool
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* newFixedThreadPool
* 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
*/
public class Demo19 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程1" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程2" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
//由于线程池的最大长度为2,而此时有3个线程需要执行
//前两个线程在执行过程中,需要等到2s钟,第三个线程此时没有空闲的线程可以使用
//就会处于等到的状态,所以通过定长线程池可以约束线程数量的上限
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程3" + Thread.currentThread().getName());
}
});
executorService.shutdown();
}
}
3、newScheduledThreadPool
创建一个定长线程池,支持定时及周期性任务执行。
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* newScheduledThreadPool
* 创建一个定长线程池,支持定时及周期性任务执行。
*/
public class Demo21 {
public static void main(String[] args) {
//创建一个定时执行的线程池对象
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
//第一个参数:要执行的任务
//第二个参数:延迟执行的时间
//第三个参数:延迟执行的时间单位
//3s以后执行
// scheduledExecutorService.schedule(new Runnable() {
// @Override
// public void run() {
// System.out.println("3s后执行...");
// }
// }, 3, TimeUnit.SECONDS);
//第三个参数:周期性执行的时间间隔
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("5s后开始执行,每3s秒执行一次");
}
}, 5, 3, TimeUnit.SECONDS);
//scheduledExecutorService.shutdown();
}
}
4、newSingleThreadExecutor
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* newSingleThreadExecutor
* 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
*/
public class Demo20 {
public static void main(String[] args) {
//只有一个线程,所以每一个线程在执行过程中会排队等待前面的线程执行完毕
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程1" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程2" + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程3" + Thread.currentThread().getName());
}
});
executorService.shutdown();
}
}