文章目录
进程和线程
1、程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。像qq、360没有启动之前还有各种安装包什么的,他就是一个可以执行的程序。
2、进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。 ——生命周期
进程就是程序运行起来了,在windows窗口中能看见了或者在linux中能看到执行了,总之运行起来了,占用了cpu内存这种系统资源,这就是一个运行中的进程。
如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的,进程作为资源分配的单位, 系统在运行时会为每个进程分配不同的内存区域
进程拥有以下三个特点:
1:独立性:进程是系统中独立存在的实体,它可以独立拥有资源,每一个进程都有自己独立的地址空间,没有进程本身的运行,用户进程不可以直接访问其他进程的地址空间。
2:动态性:进程和程序的区别在于进程是动态的,进程中有时间的概念,进程具有自己的生命周期和各种不同的状态。
3:并发性:多个进程可以在单个处理器上并发执行,互不影响。
3、线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程是一条执行的路径,是进程中的一个小任务,就像360运行起来后,一个线程去杀毒,一个线程去清理垃圾,这种就是多线程一起执行。
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
一个进程中的多个线程共享相同的内存单元/内存地址空间
它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
特点:
1)进程中负责程序执行的执行单元
2)依靠程序执行的顺序控制流,只能使用程序的资源和环境,共享进程的全部资源
3)有自己的堆栈和局部变量,没有单独的地址空间
4)CPU调度和分派的基本单位,持有程序计数器,寄存器,堆栈
并行与并发
并行: 多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发: 一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事。
操作系统并发程序执行的特点:
1、并发环境下,由于程序的封闭性被打破,出现了新的特点: ①程序与计算不再一一对应,一个程序副本可以有多个计算
2、并发程序之间有相互制约关系,直接制约体现为一个程序需要另一个程序的计算结果,间接制约体现为多个程序竞争某一资源,如处理机、缓冲区等。
3、并发程序在执行中是走走停停,断续推进的
并行和并发区别
区别一:
并发是指一个处理器同时处理多个任务。并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。并发是逻辑上的同时发生(simultaneous),而并行是物理上的同时发生。
来个比喻:并发是一个人同时吃三个馒头,而并行是三个人同时吃三个馒头。
区别二:
并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行。就好像两个人各拿一把铁锨在挖坑,一小时后,每人一个大坑。所以无论从微观还是从宏观来看,二者都是一起执行的。
并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。这就好像两个人用同一把铁锨,轮流挖坑,一小时后,两个人各挖一个小一点的坑,要想挖两个大一点得坑,一定会用两个小时。
并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在,并发能够在单处理器系统中存在是因为并发是并行的假象,并行要求程序能够同时执行多个操作,而并发只是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行)。
区别三:
当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态。这种方式我们称之为并发(Concurrent)。
当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
Java线程的创建和启动
1)继承Thread类创建线程
2)实现Runnable接口创建线程
3)使用Callable和Future创建线程
1和2 都是实现runable接口的方法。方法1中的继承thread类,其实thread类也是实现了runable接口。在不谈第三种的情况下还是推荐实现runable的方法,他不影响业务正常的继承结构。
下面让我们分别来看看这三种创建线程的方法。
方法一 继承Thread类创建线程
继承Thread类创建线程
/**
* 多线程的创建,方式一:继承于Thread类
* 1. 创建一个继承于Thread类的子类
* 2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
* 3. 创建Thread类的子类的对象
* 4. 通过此对象调用start()
* 例子:遍历100以内的所有的偶数
*/
//1. 创建一个继承于Thread类的子类
class MyThread extends Thread {
//2. 重写Thread类的run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//3. 创建Thread类的子类的对象
MyThread t1 = new MyThread();
//4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
t1.start();
//问题一:我们不能通过直接调用run()的方式启动线程。
// t1.run();
//问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException
// t1.start();
//我们需要重新创建一个线程的对象
MyThread t2 = new MyThread();
t2.start();
//如下操作仍然是在main线程中执行的。
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");
}
}
}
}
这里体现的就是不能直接调用run方法,应该调用线程的start方法,线程的start中调用到了重写的run方法,这样线程就起来了。
类对象继承的thread类,再调用start,子类又没有去重写start,这里其实又是多态的体现,父类的start其实是执行的子类的run。
感觉这个多态的理解很重要,很多场景下其实都用到了这个多态,而自己却没有注意到
方法二 实现Runnable接口创建线程
实现Runnable接口创建线程
/**
* 创建多线程的方式二:实现Runnable接口
* 1. 创建一个实现了Runnable接口的类
* 2. 实现类去实现Runnable中的抽象方法:run()
* 3. 创建实现类的对象
* 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5. 通过Thread类的对象调用start()
*/
//1. 创建一个实现了Runnable接口的类
class MThread implements Runnable {
//2. 实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3. 创建实现类的对象
MThread mThread = new MThread();
//4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
t1.setName("线程1");
//5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
t1.start();
//再启动一个线程,遍历100以内的偶数
Thread t2 = new Thread(mThread);
t2.setName("线程2");
t2.start();
}
}
比较
比较创建线程的两种方式。
开发中:优先选择:实现Runnable接口的方式原因:
1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况。
联系:public class Thread implements Runnable
相同点:其实这个方法一继承的thread类他自己也是实现了runable接口,两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
Thread类的有关方法
start(): 启动线程,并执行对象的run()方法
run(): 线程在被调度时执行的操作
String getName(): 返回线程的名称
void setName(String name):设置该线程名称
Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
static void yield(): 线程让步,暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程,若队列中没有同优先级的线程,忽略此方法
join() : 当某个程序执行流中调用其他线程的 join() 方法时, 调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
这个需要注意一下,join之后自己不执行了,一直等着join进来的线程执行完之后自己再去执行,即使是低优先级的线程也可以获得执行
static void sleep(long millis): (指定时间:毫秒)
令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
stop(): 强制线程生命期结束,不推荐使用
boolean isAlive(): 返回boolean,判断线程是否还活着
线程的优先级
MAX_PRIORITY: 10
MIN _PRIORITY: 1
NORM_PRIORITY: 5
涉及的方法
getPriority() : 返回线程优先值
setPriority(int newPriority) : 改变线程的优先级
说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
线程的生命周期
这个图很形象的说明线程的状态之间的转换
1、新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
2、就绪: 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
3、运行: 当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能,线程只是start之后进入这个就绪的状态,这时候还没有开始执行,等着cpu调度,cpu才是老大,有空才搞执行,还有时间片的概念没弄懂,详细深入再学操作系统
4、阻塞: 在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
5、死亡: 线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
线程同步机制 也就是加锁
Synchronized的使用方法
Synchronized其实就是这个关键字怎么用,可以对什么加锁,可以用来修饰代码块,这个时候显式的指定一把锁,obj对象的锁,可以修饰同步方法,这整个方法就是同步的,方法执行完之后自己释放锁。最直白的例子就是stringbuffer类,他里面的方法都有这个修饰,是线程安全的,保证在多线程的环境下只有一个线程在操作这个当前的共享数据。
- 同步代码块:
synchronized (对象){
// 需要被同步的代码;
} - synchronized还可以放在方法声明中,表示整个方法为同步方法。
例如:
public synchronized void show (String name){
….
}
这里尚硅谷康师傅给了四个例子,创建线程的时候有两个方法,继承和实现;线程的同步有两个方法,修饰同步代码块可修饰同步方法。
所有的sleep方法只是为了增大线程去争夺资源的概率,并不是说某一次执行正确就证明不存在线程安全问题。用加锁的方法才能保证同一时间只有一个线程在操作资源类,不会产省线程安全问题。
例一 实现+同步代码块
* 操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。 ---局限性
this是加载到jvm的类,类在jvm中只有一份,可以作为锁,new之后,是类的对象有多个。
这里的理解不到位,以后学了jvm之后再补充这里,或者学了之后再写新的文章吧。。。
*/
class Window1 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (this) {//此时的this:唯一的Window1的对象 //方式二:synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
例二 继承+同步代码块
/**
* 使用同步代码块解决继承Thread类的方式的线程安全问题
* 例子:创建三个窗口卖票,总票数为100张.使用继承Thread类的方式
* 说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器。
*/
class Window2 extends Thread {
private static int ticket = 100;
private static Object obj = new Object();//这样用锁的时候,锁是静态的,常量池中只有一份
@Override
public void run() {
while (true) {
//正确的
// synchronized (obj){
//这里又出现了class在内存中加载一次,还是理解不够透彻
synchronized (Window2.class) {//Class clazz = Window2.class,Window2.class只会加载一次
//错误的方式:this代表着t1,t2,t3三个对象
// synchronized (this){
if (ticket > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":卖票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 t1 = new Window2();
Window2 t2 = new Window2();
Window2 t3 = new Window2();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
例三 实现+同步方法
/**
* 使用同步方法解决实现Runnable接口的线程安全问题
* 关于同步方法的总结:
* 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。锁不用我们自己指名了
* 2. 非静态的同步方法,同步监视器是:this
* 静态的同步方法,同步监视器是:当前类本身
*/
class Window3 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (ticket > 0) {
show();
}
}
private synchronized void show() {//同步监视器:this
//synchronized (this){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
//}
}
}
public class WindowTest3 {
public static void main(String[] args) {
Window3 w = new Window3();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
例四 继承+同步方法
package com.atguigu.java;
/**
* 使用同步方法处理继承Thread类的方式中的线程安全问题
*/
class Window4 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (ticket > 0) {
show();
}
}
private static synchronized void show() {//同步监视器:Window4.class
//private synchronized void show(){ //同步监视器:t1,t2,t3。此种解决方式是错误的
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 t1 = new Window4();
Window4 t2 = new Window4();
Window4 t3 = new Window4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
Lock锁
import java.util.concurrent.locks.ReentrantLock;
/**
* 解决线程安全问题的方式三:Lock锁 --- JDK5.0新增
*
* 1. 面试题:synchronized 与 Lock的异同?
* 相同:二者都可以解决线程安全问题
* 不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
* Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
*
* 2.优先使用顺序:
* Lock 同步代码块(已经进入了方法体,分配了相应资源) 同步方法(在方法体之外)
*/
class Window implements Runnable{
private int ticket = 100;
//1.实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
//2.调用锁定方法lock()
lock.lock();
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally {
//3.调用解锁方法:unlock()
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
synchronized 与 Lock 的对比
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁), synchronized是隐式锁,出了作用域自动释放
- Lock只有代码块锁, synchronized有代码块锁和方法锁
- 使用Lock锁, JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock
同步代码块(已经进入了方法体,分配了相应资源)
同步方法(在方法体之外)
线程安全的单例模式 懒汉式
这里有个比较重要的点就是volatile关键字,以后再写文章单独搞这个,可以对这种懒汉式的单例再提高,还能避免在声明一个变量,指定了内存地址,还未对这个地址进行初始化的时候,这种情况下的执行优化。
还有一个点就是 双端检索机制,中间加锁的两端都判空,有各种好处,以后再说
/**
* 使用同步机制将单例模式中的懒汉式改写为线程安全的
*/
public class BankTest {
}
class Bank {
private Bank() {
}
private static Bank instance = null;
public static Bank getInstance() {
//方式一:效率稍差
// synchronized (Bank.class) {
// if(instance == null){
//
// instance = new Bank();
// }
// return instance;
// }
//方式二:效率更高
if (instance == null) {
synchronized (Bank.class) {
if (instance == null) {
instance = new Bank();
}
}
}
return instance;
}
}
线程的通信
通信主要的就是用了这几个方法,wait() 与 notify() 和 notifyAll()
wait():令当前线程挂起并放弃CPU、 同步资源并等待, 使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。
wait() 方法
在当前线程中调用方法: 对象名.wait()
使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify(或notifyAll) 为止。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
调用此方法后,当前线程将释放对象监控权 ,然后进入等待
在当前线程被notify后,要重新获得监控权,然后从断点处继续代码的执行。
notify()/notifyAll()
在当前线程中调用方法: 对象名.notify()
功能:唤醒等待该对象监控权的一个/所有线程。通知的是以这个锁对象为锁的线程。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
这篇文章写的不错,唤起其他的线程,还有一个争夺资源的过程
https://blog.csdn.net/u014658905/article/details/81035870
Java中notify和notifyAll的区别
Java提供了两个方法notify和notifyAll来唤醒在某些条件下等待的线程,你可以使用它们中的任何一个,但是Java中的notify和notifyAll之间存在细微差别,这使得它成为Java中流行的多线程面试问题之一。当你调用notify时,只有一个等待线程会被唤醒而且它不能保证哪个线程会被唤醒,这取决于线程调度器。虽然如果你调用notifyAll方法,那么等待该锁的所有线程都会被唤醒,但是在执行剩余的代码之前,所有被唤醒的线程都将争夺锁定,这就是为什么在循环上调用wait,因为如果多个线程被唤醒,那么线程是将获得锁定将首先执行,它可能会重置等待条件,这将迫使后续线程等待。因此,notify和notifyAll之间的关键区别在于notify()只会唤醒一个线程,而notifyAll方法将唤醒所有线程。
何时在Java中使用notify和notifyAll
1如果所有线程都在等待相同的条件,并且一次只有一个线程可以从条件变为true,则可以使用notify over notifyAll。
2在这种情况下,notify是优于notifyAll 因为唤醒所有这些因为我们知道只有一个线程会受益而所有其他线程将再次等待,所以调用notifyAll方法只是浪费CPU。
3虽然这看起来很合理,但仍有一个警告,即无意中的接收者吞下了关键通知。通过使用notifyAll,我们确保所有收件人都会收到通知
方法三 使用Callable和Future创建线程
实现callable
implements Callable
重写call方法
public Object call()
new Thread(new FutureTask<>(numThread)).start();
支持泛型的这个玩意,以后再写
Future接口
可以对具体Runnable、 Callable任务的执行结果进行取消、查询是否完成、获取结果等。
FutrueTask是Futrue接口的唯一的实现类
FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建线程的方式三:实现Callable接口。 --- JDK 5.0新增
* 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
* 1. call()可以有返回值的。object自己可以定义返回,返回任意类型的子类,又是多态
* 2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
* 3. Callable是支持泛型的
*/
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
方法四 使用线程池
背景: 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路: 提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
参数
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown() :关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
/**
* 创建线程的方式四:使用线程池
*/
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0;i <= 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//设置线程池的属性
// System.out.println(service.getClass());
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合适用于Runnable
service.execute(new NumberThread1());//适合适用于Runnable
// service.submit(Callable callable);//适合使用于Callable
//3.关闭连接池
service.shutdown();
}
}
项目中的线程池
我在自己的项目中记录日志的功能是使用线程池实现的,来了请求之后就走这个线程池拿一个线程去记录日志
放一下自己项目中用到的代码
/**
* @ClassName DBLogUtil
* @Description: 日志的工具类
* @Author: WangWenpeng
* @date: 11:37 2019/9/20
* @Version 1.0
*/
public class DBLogUtil {
private static final Logger logger = Logger.getLogger(DBLogUtil.class);
private final static ExecutorService fixedThreadPool = new ThreadPoolExecutor(20, 20, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
/**
* @Description 新开一个线程 记录外围日志
* @Author WangWenpeng
* @Date 11:09 2019/9/20
* @Param [sysApiLogBean]
*/
public static void recordOutLogThread(final SysApiLogBean sysApiLogBean) {
try {
fixedThreadPool.execute(new Runnable() {
@Override
public void run() {
logger.info("新线程打开记录日志");
SysApiLogService sysApiLogService = (SysApiLogService) SpringContextUtil.getBean("sysApiLogService");
sysApiLogService.addSysApiLogWithRst(sysApiLogBean);
}
});
} catch (Exception e) {
logger.error("recordOutLogThread 线程记录日志失败 error!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" + e.toString(), e);
}
}
}
这里直接调用这个util的recordOutLogThread方法就行了。传上相应的参数,这个方法还是静态的,那用起来就方便多了。
总结
总结一下,这篇文章写了俩小时,做知识总结真是一个费时间的工作啊