多线程
进程:
进程是程序的基本执行实体
每一个正在运行的软件都是一个进程
线程:
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
简单理解:
应用软件中互相独立,可以同时运行的功能
为什么有多线程:
提高程序的效率
多线程应用场景:
软件中的耗时操作:拷贝、迁移大文件、加载大量的资源文件
所有的聊天软件
所有的后台服务器
并发和并行
并发:
在同一时刻,有多个指令在单个CPU上交替执行
并行:
在同一时刻,有多个指令在单个CPU上同时进行
CPU有:2核4线程、4核8线程、8核16线程、16核32线程、32核64线程
线程的数量就是:你的电脑同时能运行多少条线程
多线程的实现方式
1、继承Thread类的方式进行实现
-
将一个类声明为
Thread
的子类。 这个子类应该重写run
类的方法Thread
。 然后可以分配并启动子类的实例。
ctrl+D向下复制该行
package com.cg.a01threadrealize1;
public class MyThread extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"HelloWorld");
}
}
}
package com.cg.a01threadrealize1;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第一种启动方式:
* 1、自己定义一个类继承Thread
* 2、重写run方法
* 3、创建子类的对象,并启动线程
* */
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
//给线程取个名字
t1.setName("线程1");
t2.setName("线程2");
//start 开启线程
t1.start();
t2.start();
}
}
————————————————————————————————————————————————————————————————————————
线程1HelloWorld
线程1HelloWorld
线程2HelloWorld
线程1HelloWorld
线程2HelloWorld
线程1HelloWorld
线程1HelloWorld
线程2HelloWorld
线程2HelloWorld
线程2HelloWorld
线程1HelloWorld
............
两个线程结果交替进行
2、实现Runnable接口的方式进行实现
-
创建一个线程是声明实现类
Runnable
接口。 那个类然后实现了run
方法。 然后可以分配类的实例,在创建Thread
时作为参数传递,并启动。
package com.cg.a02threadrealize2;
public class MyRun implements Runnable{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 100; i++) {
//先获取到当前线程的对象
// Thread t = Thread.currentThread();
// System.out.println(t.getName()+"HelloWorld");
System.out.println(Thread.currentThread().getName()+"HelloWorld");
}
}
}
package com.cg.a02threadrealize2;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第二种启动方式:
* 1、自己定义一个类实现Runnable接口
* 2、重写里面的run方法
* 3、创建自己的类的对象
* 3、创建一个Thread类的对象,并开启线程
* */
//创建MyRun的对象
//表示多线程要执行的任务
MyRun mr = new MyRun();
//创建线程对象
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
//给线程设置名字,方便区分
t1.setName("线程1");
t2.setName("线程2");
//开启线程
t1.start();
t2.start();
}
}
——————————————————————————————————————————————————————————————————
运行结果交替出现:
线程2HelloWorld
线程2HelloWorld
线程2HelloWorld
线程2HelloWorld
线程1HelloWorld
线程1HelloWorld
线程1HelloWorld
线程2HelloWorld
线程1HelloWorld
线程1HelloWorld
线程2HelloWorld
线程1HelloWorld
线程2HelloWorld
线程2HelloWorld
线程1HelloWorld
线程2HelloWorld
线程1HelloWorld
线程1HelloWorld
..........
3、利用Callable接口和Future接口方式实现
前两种方式的补充
重写的run方法都没有返回值,无法获取多线程的返回结果
package com.cg.a03threadrealize3;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//求1~100之间的和
int sum=0;
for (int i = 1; i <= 100; i++) {
sum = sum + i;
}
return sum;
}
}
package com.cg.a03threadrealize3;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) throws Exception {
/*
* 多线程的第三种启动方式:
* 特点:可以获取到多线程运行的结果
*
* 1、自己定义一个MyCallable类实现Callable接口
* 2、重写里面的call方法(有返回值,表示多线程运行的结果)
*
* 3、创建自己的MyCallable类的对象
* 4、创建一个FutureTask(Future是一个接口)的对象(作用管理多线程运行的结果)
* 5、创建Thread类的对象(表示线程),并启动
* */
//创建MyCallable的对象(表示多线程要执行的任务)
MyCallable mc = new MyCallable();
//创建FutureTask的对象(作用管理多线程运行的结果)
FutureTask<Integer> tf = new FutureTask<>(mc);
//创建线程的对象
Thread t1 = new Thread(tf);
//启动线程
t1.start();
//获取到多线程运行的结果
Integer result = tf.get();
System.out.println(result);
}
}
——————————————————————————————————————————————————————————————————————
5050
三种方法区别
方法1:
优:编程比较简单,可以直接使用Thread类中的方法
缺:扩展性较差,不能再继承其他的类
方法2、3:
优:扩展性强,实现该接口的同时还可以继承其他的类
缺:编程相对复杂,不能直接使用Thread类中的方法
Thread常见的成员方法
方法名称 | 说明 | |
---|---|---|
String | getName() | 返回此线程的名称 |
void | setName(String name) | 将此线程的名称更改为等于参数 name |
static Thread | currentThread() | 返回对当前正在执行的线程对象的引用。 |
static void | sleep(long millis) | 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行) |
void | setPriority(int newPriority) | 更改此线程的优先级 |
int | getPriority() | 返回此线程的优先级 |
void | setDaemon(boolean on) | 将此线程标记为守护线程或用户线程 |
static void | yield() | 对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。 出让线程/礼让线程 |
void | join() | 等待这个线程死亡。 插入线程/插队线程 |
package com.cg.a04threadrealize4;
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "@" + i);
}
}
}
package com.cg.a04threadrealize4;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
/*
String getName() 返回此线程的名称
void setName(String name) 设置线程的名字(构造方法也可以设置名字)
细节:
1、如果没有给线程设置名字,线程是有默认名字的
格式:Thread-x (x是序号,从0开始)
2、构造方法也可以设置名字,子类不能继承构造方法
static Thread currentThread() 获取当前线程的对象
细节:
当JVM虚拟机启动之后,会自动的启动多条线程
其中有一条线程就叫做main线程
它的作用就是去调用main方法,并执行里面的代码
再以前,我们写的所有的代码,其实都是运行在main线程当中
static void sleep(long millis) 让线程休眠指定的时间,单位毫秒
细节:
1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
2、方法的参数:就表示睡眠的时间,单位毫秒
1秒 = 1000毫秒
3、当时间到了之后,线程就会自动的醒来,继续执行下面的其他代码
*/
//1、创建线程对象
MyThread t1 = new MyThread("线程1");
MyThread t2 = new MyThread("线程2");
//2、开启线程
t1.start();
t2.start();
//哪条线程执行到这个地方,此时获取的就是哪条线程的对象
/*Thread t = Thread.currentThread();
String name = t.getName();
System.out.println(name); main */
/* System.out.println("1111111111111");
Thread.sleep(5000);
System.out.println("2222222222222");*/
}
}
抢占式调度(随机性)
优先级越大,这条线程抢到cpu的概率越大
优先执行,优先级不是绝对的,是概率问题
package com.cg.a05threadrealize2;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "——————" + i);
}
}
}
package com.cg.a05threadrealize2;
public class ThreadDemo {
public static void main(String[] args) {
/*
void setPriority(int newPriority) 更改此线程的优先级
int getPriority() 返回此线程的优先级
*/
//创建线程要执行的参数对象
MyRunnable mr = new MyRunnable();
//创建线程对象
Thread t1 = new Thread(mr,"飞机");
Thread t2 = new Thread(mr,"坦克");
/*//默认优先级是多少 ——5
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
System.out.println(Thread.currentThread().getPriority());*/
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
守护线程
package com.cg.a06threadrealize1;
public class MyThread1 extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 1; i <= 10; i++) {
System.out.println(getName() + "@" + i);
}
}
}
package com.cg.a06threadrealize1;
public class MyThread2 extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
package com.cg.a06threadrealize1;
public class ThreadDemo {
public static void main(String[] args) {
/*
final void setDaemon(boolean on) 设置为守护线程
细节:
当其他的非守护线程执行完毕之后,守护线程会陆续结束
通俗易懂:
当线程1结束了,线程2也没有存在的必要了
*/
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.setName("线程1");
t2.setName("线程2");
//把第二个线程设置为守护线程(备胎线程)
t2.setDaemon(true);
t1.start();
t2.start();
}
}
出让线程/礼让线程
package com.cg.a07threadrealize1;
public class MyThread extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + "@" + i);
//表示出让当前CPU的执行权
//尽可能让结果均匀一点,不是绝对的
//比如线程1又会抢回执行权
Thread.yield();
}
}
}
package com.cg.a07threadrealize1;
public class ThreadDemo {
public static void main(String[] args) {
/*
public static void yield() 出让线程/礼让线程
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
插入线程/插队线程
package com.cg.a08threadrealize1;
public class MyThread extends Thread{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 1; i <= 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
package com.cg.a08threadrealize1;
import com.cg.a07threadrealize1.MyThread;
public class ThreadDemo {
public static void main(String[] args) {
/*
public final void join() 插入线程/插队线程
*/
MyThread t = new MyThread();
t.setName("土豆");
t.start();
//执行在main线程当中
for (int i = 0; i < 10; i++) {
System.out.println("main线程" + i);
}
}
}
——————————————————————————————————————————————
main线程0
main线程1
main线程2
main线程3
main线程4
main线程5
main线程6
main线程7
main线程8
main线程9
土豆@1
土豆@2
土豆@3
土豆@4
土豆@5
土豆@6
土豆@7
土豆@8
土豆@9
..........
package com.cg.a08threadrealize1;
import com.cg.a07threadrealize1.MyThread;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
/*
public final void join() 插入线程/插队线程
*/
MyThread t = new MyThread();
t.setName("土豆");
t.start();
//表示把t这个线程,插入到当前线程之前
//t:土豆
//当前线程:main线程
t.join();
//执行在main线程当中
for (int i = 0; i < 10; i++) {
System.out.println("main线程" + i);
}
}
}
__________________________________________________________________________
土豆@1
土豆@2
土豆@3
土豆@4
土豆@5
土豆@6
土豆@7
土豆@8
土豆@9
......
main线程3
main线程4
main线程5
main线程6
main线程7
main线程8
main线程9
线程的生命周期
线程安全
问题
卖出了重复的、超出范围的票
package com.cg.a09threadrealize1;
public class MyThread extends Thread{
//static 表示这个类所有的对象,都共享ticket数据
static int ticket = 0; //0~99
@Override
public void run() {
while (true){
if (ticket < 100){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName() + "正在买第" + ticket + "张票!!!");
}else {
break;
}
}
}
}
package com.cg.a09threadrealize1;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:
某电影目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影买票
* */
//创建线程对象
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();
}
}
——————————————————————————————————————————————————————————————————————————
窗口2正在买第2张票!!!
窗口1正在买第3张票!!!
窗口3正在买第2张票!!!
窗口2正在买第5张票!!!
窗口1正在买第4张票!!!
窗口3正在买第6张票!!!
窗口2正在买第7张票!!!
......
窗口2正在买第99张票!!!
窗口3正在买第100张票!!!
窗口1正在买第101张票!!!
窗口2正在买第102张票!!!
方法:使用同步代码块
把操作共享数据的代码锁起来
格式
synchronized(锁对象){
操作共享数据的代码
}
特点:
1、锁默认打开,有一个线程进去了,锁自动关闭
2、里面的代码全部执行完毕,线程出来,锁自动打开
锁对象不一样,就没有意义了,相当于,一人一个门
package com.cg.a09threadrealize1;
public class MyThread extends Thread{
//static 表示这个类所有的对象,都共享ticket数据
static int ticket = 0; //0~99
//锁对象一定要是唯一的
//static Object obj = new Object();
@Override
public void run() {
while (true){
//线程安全 —— 同步代码块
synchronized (MyThread.class){ //obj
if (ticket < 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName() + "正在买第" + ticket + "张票!!!");
}else {
break;
}
}
}
}
}
方法:同步方法
格式
修饰符 synchronized 返回值类型 方法名(方法参数){...}
特点:
1、同步方法是锁住方法里面所有的代码
2、锁对象不能自己指定
非静态:this
静态:当前类的字节码文件对象
抽取方法快捷键:ctrl+alt+M
package com.cg.a10threadrealize2;
public class MyRunnable implements Runnable {
//不用使用static 因为这个类只创建一次,作为一个参数让线程执行
int ticket = 0;
@Override
public void run() {
//1、循环
while (true) {
//2、同步代码块()
if (method()) break;
}
}
//方法是非静态的 锁对象是this , 这里是创建的mr
private synchronized boolean method() {
//3、判断共享数据是否到了末尾,如果到了末尾
if (ticket == 100) {
return true;
} else {
//4、判断共享数据是否到了末尾,如果没有到末尾
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(Thread.currentThread().getName() + "正在买第" + ticket + "张票!!!");
}
return false;
}
}
package com.cg.a10threadrealize2;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:
某电影目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影买票
利用同步方法完成
技巧:
同步代码块 改成 同步方法
* */
MyRunnable mr = new MyRunnable();
//创建线程对象
Thread t1 = new Thread(mr, "窗口1");
Thread t2 = new Thread(mr, "窗口2");
Thread t3 = new Thread(mr, "窗口3");
//开启线程
t1.start();
t2.start();
t3.start();
}
}
lock锁
在同步代码块:锁自动关闭、锁自动打开
JDK5提供了一个新的锁对象Lock,可以手动的去加或释放锁
Lock比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁
lock是接口,不能直接实例化,采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例
package com.cg.a11threadrealize1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyThread extends Thread {
//static 表示这个类所有的对象,都共享ticket数据
static int ticket = 0; //0~99
//共享 同一把锁
static Lock lock = new ReentrantLock();
@Override
public void run() {
//1、循环
while (true) {
//2、同步代码块()
lock.lock();
//synchronized (MyThread.class){
try {
//3、判断共享数据是否到了末尾,如果到了末尾
if (ticket == 100) {
break;
} else {
//4、判断共享数据是否到了末尾,如果没有到末尾
Thread.sleep(100);
ticket++;
System.out.println(Thread.currentThread().getName() + "正在买第" + ticket + "张票!!!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally { //finally 里面的代码无论怎样都会被执行
lock.unlock();
}
//}
}
}
}
package com.cg.a11threadrealize1;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:
某电影目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影买票
用JDK5的lock锁实现
* */
//创建线程对象
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();
}
}
死锁-是一个错误
锁形成了一个嵌套
外面一个锁,里面一个锁
千万不要让两个锁嵌套起来
package com.cg.a12threadrealize1;
public class MyThread extends Thread {
//共享 同一把锁
static Object objA = new Object();
static Object objB = new Object();
@Override
public void run() {
//1、循环
while (true) {
if ("线程A".equals(getName())) {
synchronized (objA) {
System.out.println("线程A拿到了A锁,准备拿B锁");
synchronized (objB) {
System.out.println("线程A拿到了B锁,顺利执行完一轮");
}
}
} else if ("线程B".equals(getName())) {
if ("线程B".equals(getName())) {
synchronized (objB) {
System.out.println("线程B拿到了B锁,准备拿A锁");
synchronized (objA) {
System.out.println("线程B拿到了A锁,顺利执行完一轮");
}
}
}
}
}
}
}
package com.cg.a12threadrealize1;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:
死锁
*/
//创建线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
//取名字
t1.setName("线程A");
t2.setName("线程B");
//开启线程
t1.start();
t2.start();
}
}
等待唤醒机制(生产者和消费者)
生产者和消费者模式是一个十分经典的多线程协作的模式
核心思想:控制线程的执行
消费者:消费数据 生产者:生产数据
1、判断桌子上是否有食物 1、判断桌子上是否有食物
2、如果没有就等待 2、有:等待
3、如果有就开吃 3、没有:制作食物
4、吃完之后,唤醒厨师继续做 4、把食物放在桌子上
5、叫醒等待的消费者开吃
常见方法
方法名称 | 说明 |
---|---|
void wait() | 当前线程等待,直到被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAll() | 唤醒所有线程 |
package com.cg.a13waitandnotify;
public class Desk {
/*
* 作用:控制生产者和消费者的执行
*
* */
//是否有面条 0:没有面条 1:有面条
public static int foodFlag = 0;
//总个数
public static int count = 10;
//锁对象
public static Object lock = new Object();
}
package com.cg.a13waitandnotify;
public class Foodie extends Thread {
@Override
public void run() {
/*
* 1、循环
* 2、同步代码块
* 3、判断共享数据是否到了末尾(到了)
* 4、判断共享数据是否到了末尾(未到,执行核心逻辑)
* */
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
break;
} else {
//先判断桌子上是否有面条
if (Desk.foodFlag == 0) {
//如果没有,就等待
try {
Desk.lock.wait();//让当前线程跟锁进行绑定
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//把吃完的总数-1
Desk.count--;
//如果有,就开吃
System.out.println("吃货在吃面条,还能再吃" + Desk.count + "碗");
//吃完之后,唤醒厨师继续做
Desk.lock.notifyAll();//唤醒跟这把锁绑定的所有线程
//修改桌子的状态
Desk.foodFlag = 0;
}
}
}
}
}
}
package com.cg.a13waitandnotify;
public class Cook extends Thread{
@Override
public void run() {
/*
* 1、循环
* 2、同步代码块
* 3、判断共享数据是否到了末尾(到了)
* 4、判断共享数据是否到了末尾(未到,执行核心逻辑)
* */
while (true){
synchronized (Desk.lock){
if (Desk.count == 0){
break;
}else {
//先判断桌子上是否有面条
if (Desk.foodFlag == 1){
//如果有,等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//如果没有,开始做面条
System.out.println("厨师做了一碗面条");
//修改桌子状态
Desk.foodFlag = 1;
//叫醒等待的消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
package com.cg.a13waitandnotify;
public class ThreadDemo {
/*
需求:完成生产者和消费者(等待唤醒机制)的代码
* 实现线程轮流交替执行的效果
*/
public static void main(String[] args) {
//创建线程对象
Cook cook = new Cook();
Foodie foodie = new Foodie();
//设置线程名字
cook.setName("厨师");
foodie.setName("吃货");
//开启线程
cook.start();
foodie.start();
}
}
等待唤醒机制(阻塞队列方式)
队列
数据放进一个管道,先放的被先拿
阻塞
放数据时:放不进去,会等着,也叫做阻塞
拿数据时:取出第一个数据,取不到会等着,也叫做阻塞
阻塞队列的继承结构
接口:
Iterable 迭代器、增强for
Collection 集合
Queue 队列
BlockingQueue 阻塞队列
不能直接创建对象
实现类
ArrayBlockingQueue:底层是数组,有界(有长度的界限)
LinkedBlockingQueue:底层是链表,无界但不是真正的无界,最大为int的最大值
package com.cg.a14waitandnotify;
import java.util.concurrent.ArrayBlockingQueue;
public class Cook extends Thread{
ArrayBlockingQueue<String> queue;//成员变量
//构造方法
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true){
//不断地把面条放进阻塞队列当中
try {
queue.put("面条"); //不需要写锁,因为方法底层里面有锁
System.out.println("厨师放了一碗面条");//输出语句在锁外面会导致重复,不会造成安全影响
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*打印的语句没有被锁到
该线程执行一次锁里面的内容就释放锁并唤醒其他线程
锁被其他线程拿到 但没有被锁的语句任然可以执行 就会出现多次执行*/
package com.cg.a14waitandnotify;
import java.util.concurrent.ArrayBlockingQueue;
public class Foodie extends Thread{
ArrayBlockingQueue<String> queue;
//构造方法
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (true){
//不断地从阻塞队列当中获取面条
try {
String food = queue.take();//不需要写锁,因为方法底层里面有锁
System.out.println(food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.cg.a14waitandnotify;
import java.util.concurrent.ArrayBlockingQueue;
public class ThreadDemo {
/*
需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
* 细节:
生产者和消费者必需用同一个阻塞队列
*/
public static void main(String[] args) {
//1、测试类创建阻塞对象
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
//2、创建线程的对象,并把阻塞队列传递过去
Cook cook = new Cook(queue);
Foodie foodie = new Foodie(queue);
//3、取名字
cook.setName("厨师");
foodie.setName("吃货");
//4、开启线程
cook.start();
foodie.start();
}
}
线程的状态(六大状态)
没有定义运行状态:被交出去了
线程池
前面写的多线程的弊端:
1、用到线程的时候就创建
2、用完之后就消失
会浪费操作系统的资源
核心原理
1、创建一个池子,池子中是空的
2、提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
3、但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
代码实现
1、创建线程池
2、提交任务
3、所有任务全部执行完毕,关闭线程池(实际开发中一般不会关闭,服务器不会关闭,随时随地有新的任务)
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象
方法名称 | 说明 |
---|---|
public static ExecutorService newCachedThreadPool() | 创建一个没有上限的线程池,最多21个亿的 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建有上限的线程池 |
package com.cg.a15threadpool1;
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---" );
}
}
package com.cg.a15threadpool1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
/*
public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池,最多21个亿的
public static ExecutorService newFixedThreadPool(int nThreads) 创建有上限的线程池
*/
//1、获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();
//2、提交任务
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
//3、销毁线程池
//pool1.shutdown();
}
}
————————————————————————————————————————————————————————————————————————————————————————————
pool-1-thread-6---
pool-1-thread-4---
pool-1-thread-5---
pool-1-thread-1---
pool-1-thread-3---
pool-1-thread-2---
线程池的复用
package com.cg.a15threadpool1;
public class MyRunnable implements Runnable{
@Override
public void run() {
// for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" );
// }
}
}
package com.cg.a15threadpool1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
/*
public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池,最多21个亿的
public static ExecutorService newFixedThreadPool(int nThreads) 创建有上限的线程池
*/
//1、获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();
//2、提交任务
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
pool1.submit(new MyRunnable());
Thread.sleep(1000);
//3、销毁线程池
//pool1.shutdown();
}
}
——————————————————————————————————————————————————————————————————————————————————————————————————————————
pool-1-thread-1---
pool-1-thread-1---
pool-1-thread-1---
pool-1-thread-1---
pool-1-thread-1---
pool-1-thread-1---
有上限的线程池
package com.cg.a15threadpool1;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
package com.cg.a15threadpool1;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPoolDemo {
public static void main(String[] args) throws InterruptedException {
/*
public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池,最多21个亿的
public static ExecutorService newFixedThreadPool(int nThreads) 创建有上限的线程池
*/
//1、获取线程池对象
ExecutorService pool1 = Executors.newFixedThreadPool(3);
//2、提交任务
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
pool1.submit(new MyRunnable());
//3、销毁线程池
//pool1.shutdown();
}
}
可以debug看
自定义线程池
ThreadPoolExecutor
饭店的故事:
1、正式员工数量 ————核心线程数量(不能小于0)
2、餐厅最大员工数量 ————线程池中最大线程的数量(最大数量 >= 核心线程数量)
3、临时员工空闲多长时间被辞退(值) ————空闲时间(值)(不能小于0)
4、临时员工空闲多长时间被辞退(单位) ————空闲时间(单位)(用TimeUnit指定)
5、排队的客户 ————队列(不能为null)
6、从哪里招人 ————创建线程的方式(不能为null)
7、当排队人数过多,超出顾客请下次再来(拒绝服务) ————要执行的任务过多时的解决方案(不能为null)
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 创建一个新 ThreadPoolExecutor给定的初始参数。
三个临界点
核心线程:3
临时线程:3
队伍长度:3
1、当核心线程满时,再提交任务就会排队
2、当核心线程满,队伍满时,会创建临时线程
3、当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略
当有10个任务:
1,2,3被核心线程执行
4,5,6进入等待队列
7,8,9临时线程
10就会触发拒绝策略
拒绝策略类
任务拒绝策略 | 说明 | |
---|---|---|
static class | ThreadPoolExecutor.AbortPolicy | 被拒绝的任务的处理程序,抛出一个 RejectedExecutionException 。 |
static class | ThreadPoolExecutor.CallerRunsPolicy | 一个被拒绝的任务的处理程序,直接在 execute 方法的调用线程中运行被拒绝的任务,除非执行程序已经被关闭,否则这个任务被丢弃 |
static class | ThreadPoolExecutor.DiscardOldestPolicy | 被拒绝的任务的处理程序,丢弃最旧的未处理请求,然后重试 execute ,除非执行程序关闭,在这种情况下,任务被丢弃 |
static class | ThreadPoolExecutor.DiscardPolicy | 被拒绝的任务的处理程序静默地丢弃被拒绝的任务 |