1.什么是多线程?为什么要有多线程
进程:
进程是程序的基本执行实体
在任务管理器第一个界面当中第一个其实就是进程,每一个软件都对应着一个进程,咱们可以这样去理解,一个软件运行它就是一个进程,
举例:
目前操作系统都是支持多进程,可以同时执行多个进程,通过进程ID区分
线程:
线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.
举例:
比如说360,在360里面有很多独立的功能比如说木马查杀,电脑清理,系统修复,,优化加速...360软件运行之后,它的本身是一个进程,而它里面的木马查杀,电脑清理,系统修复,,优化加速...我们可以把它看做四个线程
简单理解:
线程就是应用软件当中互相独立,又可以同时运行的功能,互相独立的能同时运行的功能比较多,就形成了多线程
为什么有多线程呢?
举例:
小白是一名流水线的员工,工作内容就是把流水线上的货物拿下来,堆放在一起,但是流水线的货物不是连续的,假设每件货物的间隔是十分钟,小白搬了一个货物就可以摸鱼十分钟,小白心里非常的爽,但是老板不满意了
所以老板让小白负责一个人同时负责三条流水线,工作的效率得到了提高,把货物间隔可以摸鱼的十分钟,充分利用起来了,小白才能在三条流水线之间进行切换,这样才能提高工作效率
单线程:
代码举例:
在运行的时候,cpu首先读到了第一行代码,在内存当中就会创建一个变量a,但是内存当中创建变量需要时间,虽然说这个时间很短,很快,但是它也要时间,在创建的过程当中,cpu是没有办法继续运行下面的代码,所以cpu先等着,这个被称为单线程程序,从头往下依次运行,就跟刚刚一条流水线是一样的,cpu不会切换到其他代码中,所以它的效率比较低
多线程程序的特点:
多线程程序的特点就是能,同时的能去做多件事情,cpu可以在多个程序之间进行切换,把等待的空闲时间利用起来,这就是多线程最大的特点,提高程序的运行效率,就像小白一个人工作可以同时操控几个流水线一样
多线程的应用场景:
软件中的耗时操作, 拷贝丶迁移大文件丶加载大量的资源文件,所有的聊天软件,所有的后台服务器
总结:
1.什么是多线程?
有了多线程,我们就可以让程序同时做多件事情
2.多线程的作用?
充分利用程序的等待时间,让cpu在多个程序中来回切换,从而提高程序运行效率
3.多线程的应用场景?
只要你想让多个事情同时运行就需要用到多线程比如: 软件中的耗时操作、所有的聊天软件、所有的服务器
4.进程和线程的区别
4.1
进程是操作系统资源分配的基本单位,而线程是CPU的基本调度
单位。
4.2
一个程序运行后至少有一个线程。
4.3
一个进程可以包含多个线程,但是至少需要有一个线程,否则这个进程是没有意义。
4.4
进程之间不能共享数据段地址,但是同进程的线程之间可以
5.线程的特点
5.1线程抢占式执行
效率高可防止单一线程长时间独占CPU
5.2在单核CPU中,宏观上同时执行,微观上顺序执行
2.并发和并行
并发:在同一时刻,有多个指令在单个cpu上交替执行
并行:在同一时刻,有多个指令在多个cpu上同时执行
如果计算机只有四条线程是不用切换的
如果有多个就会随机在里面切换
3.多线程的实现方式
1.继承Theread类的方式进行实线
代码:
package com.bbb.a01tjreadcase1;
//1.继承thread
public class MyThread extends Thread{
//2.重写run方法:当线程启动时,会调用run方法中的代码
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 20; i++) {
System.out.println(getName()+"线程==="+i);//获取线程名
}
}
}
注意:开启线程不能直接调用线程里面run方法 ,输出结果还是自上而下依次运行,相当于调用普通的run方法输出了结果一样,并没有起到交替效果,也不属于开启线程
package com.bbb.a01tjreadcase1;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第一种启动方式:
* 1.自己定义一个类继承Thread
* 2.重写run方法
* 3.创建子类对象,并启动线程
*
* */
//1.创建一个线程对象
MyThread m1 = new MyThread();
MyThread m2 = new MyThread();
m1.setName("线程1:");//给线程起名
m2.setName("线程2:");
//2.开启线程.start()---线程会自动调用线程的run方法,来执行该线程的任务
m1.start();
m2.start();
}
}
打印结果:两个线程交替执行
2.实线Runnable接口的方式进行实线
注意:
第二种方法获取线程名字不能再用getName
package com.bbb.a02tjreadcase2;
public class MyRun implements Runnable{
@Override
public void run() {
//书写线程要执行的代码
for (int i = 0; i < 20; i++) {
//获取线程的名字,谁调用获取谁的名字
System.out.println(Thread.currentThread().getName()+"线程===="+i);
}
}
}
注意:这里开启线程需要new Thread(线程任务名)
package com.bbb.a02tjreadcase2;
public class ThreadDemo {
public static void main(String[] args) {
/*
* 多线程的第二种启动方式:
* 1.自己定义一个类实现Runnable接口
* 2.重写里面的run方法
* 3.创建自己的类的对象
* 4.创建一个Thread类的对象,并开启线程
* */
//创建MyRun的对象
//表示多线程要执行的任务
MyRun mr = new MyRun();
//创建线程的对象
Thread t1 = new Thread(mr,"线程1");//也可以直接在后面给线程起名字
Thread t2 = new Thread(mr,"线程2");
/*
//给线程设置名字
t1.setName("线程1");
t2.setName("线程2");
*/
//开启线程
t1.start();
t2.start();
//main线程
for (int i = 0; i < 20; i++) {
System.out.println("main==="+i);
}
}
}
3.利用Callable接口和Future接口方式实
package com.bbb.a03tjreadcase3;
import java.util.concurrent.Callable;
//实现接口,泛型
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
//求1~20之间的和
int sum=0;
for (int i = 0; i < 20; i++) {
sum=sum+i;
}
return sum;
}
}
package com.bbb.a03tjreadcase3;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
* 多线程的第三种方式:
* 特点:可以获取到多线程运行的结果
*
* 1.创建一个类MyCallable实现Callable接口
* 2.重写call (是有返回值的,表示多线程运行的结果)
*
* 3.创建MyCallable的对象 (表示多线程要执行的任务)
* 4.创建FutureTask的对象 (作用管理多线程的运行的结果)
* 5.创建Thread类的对象,并启动 (表示线程)
* */
//创建MyCallable的对象 (表示多线程要执行的任务)
MyCallable mc = new MyCallable();
// 创建FutureTask的对象 (作用管理多线程的运行的结果)
FutureTask<Integer> ft = new FutureTask<>(mc);
//创建Thread类的对象
Thread thread = new Thread(ft);
//并启动线程
thread.start();
//获取多线程运行的结果
Integer integer = ft.get();
System.out.println(integer);
}
}
"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:E:\ideal\IntelliJ IDEA 2022.3.1\lib\idea_rt.jar=59084:E:\ideal\IntelliJ IDEA 2022.3.1\bin" -Dfile.encoding=UTF-8 -classpath D:\ideacj\duoXianCheng\target\classes com.bbb.a03tjreadcase3.ThreadDemo
190
Process finished with exit code 0
4.多线程三种实现方式对比:
继承Thread类
优点:编程比较简单,可以直接使用Thread类中的方法
缺点:可以扩展性较差,不能再继承其他的类
实现Runnable接口
实现callable接口
优点:
扩展性强,实编辑口的同时还可以继承其他的类
缺点:
编程相对复杂,不能直接使用Thread类中的方法
它俩的区别:
返回值,抛异常,方法
用第一种共享代码块需要加static 而第二种第三种不需要
4.常见的成员方法
String getName() 返回此线程的名称
void setName(String name) 设置线程的名字(构造方法也可以设置名字)
细节:1、如果我们没有给线程设置名字,线程也是有默认的名字的
格式:Thread-X(X序号,从0开始的)
不设置名字获取名字,默认会给名字
2、如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置
刚才能直接给名字是因为是Thread对象,现在是它的子类对象,所以直接给名字会报错(构造方法是不能继承的,子类要想用父类的构造,需要自己去调用父类的方法)
static Thread currentThread() 获取当前线程的对象
细节:
当JVM虚拟机启动之后,会自动的启动多条线程
其中有一条线程就叫做main线程
他的作用就是去调用main方法,并执行里面的代码
在以前,我们写的所有的代码,其实都是运行在main线程当中
static void sleep(long time) 让线程休眠指定的时间,单位为毫秒
细节:
1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
2、方法的参数:就表示睡眠的时间,单位毫秒1 秒= 1000毫秒
3、当时间到了之后,线程会自动的醒来,继续执行下面的其他代码
setPriority(int newPriority) 设置线程的优先级
final int getPriority() 获取线程的优先级
线程优先级1-10,默认为5,优先级越高,表示获取CPU的概率越高。优先级是抢占式调度,是随机的
final void setDaemon(boolean on) 设置为守护线程
线程对象.setDaemon(true);设置为守护线程。
线程有两类:用户线程(前台线程)和守护线程(后台线程)
如果程序中所有前台线程都执行完毕了,后台线程也会自动结束。
垃圾回收线程属于守护线程。
细节: 当其他的非守护线程执行完毕之后,守护线程会陆续结束 通俗易懂: 当女神线程结束了,那么备胎也没有存在的必要了
细节:守护线程不是一下子就结束了,而是陆陆续续结束
public static void yield() 出让线程/礼让线程
当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
交替的频率会变多
public static void join() 插入线程/插队线程
main线程会等m1执行完才执行
5.线程的安全问题
比如下面的列子:
当多个线程共享一个资源时,可能会出现线程安全问题。
重复或者超卖票的现象,
package com.aaa.demo4;
public class My implements Runnable{
private int ticket=100;
@Override
public void run() {
while (ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket--;
System.out.println(Thread.currentThread().getName()+"卖了一张,剩余"+ticket+"张");
}
}
}
package com.aaa.demo4;
public class test {
public static void main(String[] args) throws InterruptedException {
//创建一个卖票任务
My my = new My();
//创建四个线程
Thread t1 = new Thread(my,"aaa");
Thread t2 = new Thread(my,"bbb");
Thread t3 = new Thread(my,"ccc");
Thread t4 = new Thread(my,"ddd");
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
/*
System.out.println("1111");
Thread.sleep(5000);
System.out.println("2222");
*/
}
}
如何解决线程安全问题? ----使用锁。---凡是用锁 锁定的代码都是原子操作。
package com.aaa.demo7;
import java.util.Arrays;
public class Test {
private static String[] arr=new String[2];
private static int index=0;
public static void main(String[] args) throws InterruptedException {
//创建一个线程类---并为其指定任务代码
Thread thread = new Thread(new Runnable() {//匿名实现类
@Override
public void run() {
if (arr[index]==null){
arr[index]="hello";
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
if (arr[index]==null){
arr[index]="word";
}
}
});
thread.start();
thread2.start();
thread.join();
thread2.join();
System.out.println(Arrays.toString(arr));
}
}
1.同步代码块
自动锁:synchronized
格式:
synchronized(锁){
操作共享数据的代码
}
特点1: 锁默认打开,有一个线程进去了,锁自动关闭
特点2: 里面的代码全部执行完毕,线程出来,锁自动打开
package com.aaa.demo8;
public class My implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
synchronized (this) {//共享资源 不能用上边的ticket
if (ticket > 0) {
ticket--;
System.out.println(Thread.currentThread().getName() + "卖了一张,剩余" + ticket + "张");
} else {
break;
}
}
}
}
}
package com.aaa.demo8;
public class test {
public static void main(String[] args) throws InterruptedException {
//创建一个卖票任务
My my = new My();
//创建四个线程
Thread t1 = new Thread(my,"aaa");
Thread t2 = new Thread(my,"bbb");
Thread t3 = new Thread(my,"ccc");
Thread t4 = new Thread(my,"ddd");
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
/*
System.out.println("1111");
Thread.sleep(5000);
System.out.println("2222");
*/
}
}
注意:
synchronized不能放到while的外边因为这样会A窗口使停在-----一直到100才出来,B和C.D窗口才能进去
"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:E:\ideal\IntelliJ IDEA 2022.3.1\lib\idea_rt.jar=54352:E:\ideal\IntelliJ IDEA 2022.3.1\bin" -Dfile.encoding=UTF-8 -classpath D:\ideacj\duoXianCheng\target\classes com.aaa.demo8.Test
aaa卖了一张,剩余99张
aaa卖了一张,剩余98张
aaa卖了一张,剩余97张
aaa卖了一张,剩余96张
aaa卖了一张,剩余95张
aaa卖了一张,剩余94张
aaa卖了一张,剩余93张
aaa卖了一张,剩余92张
aaa卖了一张,剩余91张
aaa卖了一张,剩余90张
aaa卖了一张,剩余89张
aaa卖了一张,剩余88张
aaa卖了一张,剩余87张
aaa卖了一张,剩余86张
aaa卖了一张,剩余85张
aaa卖了一张,剩余84张
aaa卖了一张,剩余83张
aaa卖了一张,剩余82张
aaa卖了一张,剩余81张
aaa卖了一张,剩余80张
aaa卖了一张,剩余79张
aaa卖了一张,剩余78张
aaa卖了一张,剩余77张
aaa卖了一张,剩余76张
aaa卖了一张,剩余75张
aaa卖了一张,剩余74张
aaa卖了一张,剩余73张
aaa卖了一张,剩余72张
aaa卖了一张,剩余71张
aaa卖了一张,剩余70张
aaa卖了一张,剩余69张
aaa卖了一张,剩余68张
aaa卖了一张,剩余67张
aaa卖了一张,剩余66张
aaa卖了一张,剩余65张
aaa卖了一张,剩余64张
aaa卖了一张,剩余63张
aaa卖了一张,剩余62张
aaa卖了一张,剩余61张
aaa卖了一张,剩余60张
aaa卖了一张,剩余59张
aaa卖了一张,剩余58张
aaa卖了一张,剩余57张
aaa卖了一张,剩余56张
aaa卖了一张,剩余55张
aaa卖了一张,剩余54张
aaa卖了一张,剩余53张
aaa卖了一张,剩余52张
aaa卖了一张,剩余51张
aaa卖了一张,剩余50张
aaa卖了一张,剩余49张
aaa卖了一张,剩余48张
aaa卖了一张,剩余47张
aaa卖了一张,剩余46张
aaa卖了一张,剩余45张
aaa卖了一张,剩余44张
aaa卖了一张,剩余43张
aaa卖了一张,剩余42张
aaa卖了一张,剩余41张
aaa卖了一张,剩余40张
aaa卖了一张,剩余39张
aaa卖了一张,剩余38张
aaa卖了一张,剩余37张
aaa卖了一张,剩余36张
aaa卖了一张,剩余35张
aaa卖了一张,剩余34张
aaa卖了一张,剩余33张
aaa卖了一张,剩余32张
aaa卖了一张,剩余31张
aaa卖了一张,剩余30张
aaa卖了一张,剩余29张
aaa卖了一张,剩余28张
aaa卖了一张,剩余27张
aaa卖了一张,剩余26张
aaa卖了一张,剩余25张
aaa卖了一张,剩余24张
aaa卖了一张,剩余23张
aaa卖了一张,剩余22张
aaa卖了一张,剩余21张
aaa卖了一张,剩余20张
aaa卖了一张,剩余19张
aaa卖了一张,剩余18张
aaa卖了一张,剩余17张
aaa卖了一张,剩余16张
aaa卖了一张,剩余15张
aaa卖了一张,剩余14张
aaa卖了一张,剩余13张
aaa卖了一张,剩余12张
aaa卖了一张,剩余11张
aaa卖了一张,剩余10张
aaa卖了一张,剩余9张
aaa卖了一张,剩余8张
aaa卖了一张,剩余7张
aaa卖了一张,剩余6张
aaa卖了一张,剩余5张
aaa卖了一张,剩余4张
aaa卖了一张,剩余3张
aaa卖了一张,剩余2张
aaa卖了一张,剩余1张
aaa卖了一张,剩余0张
Process finished with exit code 0
2.手动锁: Lock
package com.aaa.demo8;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class My implements Runnable {
private int ticket = 100;
private Lock l=new ReentrantLock();//创建手动锁对象
@Override
public void run() {
while (true) {
l.lock();//手动上锁
if (ticket > 0) {
ticket--;
System.out.println(Thread.currentThread().getName() + "卖了一张,剩余" + ticket + "张");
} else {
break;
}
l.unlock();//手动开锁
}
}
}
package com.aaa.demo8;
public class test {
public static void main(String[] args) throws InterruptedException {
//创建一个卖票任务
My my = new My();
//创建四个线程
Thread t1 = new Thread(my,"aaa");
Thread t2 = new Thread(my,"bbb");
Thread t3 = new Thread(my,"ccc");
Thread t4 = new Thread(my,"ddd");
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
/*
System.out.println("1111");
Thread.sleep(5000);
System.out.println("2222");
*/
}
}
3.死锁问题
死锁说白了就是在我们程序当中出现了嵌套,死锁不是一个知识点,是一个错误,是为了让我们以后不要犯这个错误
假设有你跟一个妹子在吃饭的游戏规则.
游戏规则是筷子只有一双,
每次需要拿起筷子才能吃饭,
但是不能一次拿一双,只能一只一只的拿
拿到一双筷子后才可以吃一口
理想情况下:
第一次你抢到了一只,第二次又抢到了一只,拿到了一双筷子可以吃一口,然后吃完放下继续抢,
第三次可能被妹子抢到了一只,第四次还是被妹子抢到了另一只,妹子吃了一口,然后吃完放下继续抢
但是这是理想情况下,但是有一种可能,你抢到了一只,妹子抢到了一只,这时候就不能吃饭了,你在等着妹子放下筷子,但是妹子在等着你放下筷子,这时候游戏就会卡死在这个地方,玩不下去了
在代码里假设男孩是线程A,女孩是线程B,两只筷子是两把不一样的锁,A线程拿着A锁,B线程拿着B锁,他们都在等着对方先释放锁,这时候程序就会卡死,进行不下去了
package com.aaa.demo9;
public class LockObject {
public static Object lockA=new Object();//锁A
public static Object lockB=new Object();//锁B
}
package com.aaa.demo9;
public class A extends Thread{
@Override
public void run() {
synchronized (LockObject.lockA){
System.out.println("线程A拿到了A锁,准备拿B锁");
synchronized (LockObject.lockB){
System.out.println("线程A拿到了B锁准备重新开始");
}
}
}
}
package com.aaa.demo9;
public class B extends Thread {
@Override
public void run() {
synchronized (LockObject.lockB) {
System.out.println("线程B拿到了B锁,准备拿A锁");
synchronized (LockObject.lockA){
System.out.println("线程B拿到了A锁准备重新开始");
}
}
}
}
package com.aaa.demo9;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) throws InterruptedException {
//设置死锁
A a = new A();
B b = new B();
a.start();
b.start();
}
}
"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:E:\ideal\IntelliJ IDEA 2022.3.1\lib\idea_rt.jar=50768:E:\ideal\IntelliJ IDEA 2022.3.1\bin" -Dfile.encoding=UTF-8 -classpath D:\ideacj\duoXianCheng\target\classes com.aaa.demo9.Test
线程A拿到了A锁,准备拿B锁
线程B拿到了B锁,准备拿A锁
那么我们如何解决死锁?
1.减少同步代码块的嵌套,
2.设置锁的超时时间
3.可以使用安全类-jdk提高的安全类。
6.线程的通信(同步方法)
同步方法
就是把synchronized关键字加到方法上
格式:
修饰符 synchronized 返回值类型 方法名(方法参数){...}
特点1:同步方法是锁住方法里面所有的代码
特点2:锁对象不能自己指定 非静态:this 静态: 当前类的字节码文件对象
package com.bbb.a06tjreadcase6;
public class MyRunnable implements Runnable{
private int ticket=0;
@Override
public void run() {
//1.循环
while (true){
//2.同步代码块 (同步方法)
synchronized (this){
//3.判断共享数据是否到了末尾,如果到了末尾
if (ticket==100){
break;
}else {
//4.判断共享数据是否到了末尾,如果没有到末尾
ticket++;
System.out.println(Thread.currentThread().getName()+"在卖第"+ ticket +"张票!!!");
}
}
}
}
}
package com.bbb.a06tjreadcase6;
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();
}
}
我们把synchronized代码块里面全都抽取放到方法当中
快捷键:选中里面的方法,ctrl+alt+M
package com.bbb.a06tjreadcase6;
public class MyRunnable implements Runnable{
private int ticket=0;
@Override
public void run() {
//1.循环
while (true){
//2.同步代码块 (同步方法)
if (extracted()) break;
}
}
private synchronized boolean extracted() {
//3.判断共享数据是否到了末尾,如果到了末尾
if (ticket==100){
return true;
}else {
//4.判断共享数据是否到了末尾,如果没有到末尾
ticket++;
System.out.println(Thread.currentThread().getName()+"在卖第"+ ticket +"张票!!!");
}
return false;
}
}
等待唤醒机制
void wait( ) 当前线程等待,直到被其他线程唤醒
notify() 随机唤醒单个线程
notifyAll() 唤醒所有线程
1.抽取类: 1.线程任务类:存钱2.线程任务类:取钱3.银行卡类[余额 功能:存钱 取钱]
package com.aaa.demo10;
public class BankCard {
private int balance;//余额
private boolean flag;//true:表示卡中有钱 false:表示卡中没钱
//存钱功能: 为整个方法上锁
public synchronized void save(int money) {
if (flag==true){
try {
this.wait();//这个wait 来自Object里 调用了wait 那么当前线程就会释放锁资源,
// 并且进入等待队列中,等待队列中的线程需要其他线程调用 notify 来唤醒等待队列中的线程,参与下次锁竞争
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
balance=balance+money;
System.out.println(Thread.currentThread().getName()+"往卡中存入了"+money+";卡中余额:"+balance+"元");
flag=true;//标记卡中已经有钱了
//唤醒--等待队列中的线程对象--notify随机唤醒一个--notifyAll唤醒所有
notifyAll();//也是Object里面的
}
//取钱功能
public synchronized void take(int money){
if (flag==false){
try {
this.wait();//这个wait 来自Object里 调用了wait 那么当前线程就会释放锁资源,
// 并且进入等待队列中,等待队列中的线程需要其他线程调用 notify 来唤醒等待队列中的线程,参与下次锁竞争
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
balance=balance-money;
System.out.println(Thread.currentThread().getName()+"往卡中取了"+money+";卡中余额:"+balance+"元");
flag=false;//标记卡中没钱
//唤醒--等待队列中的线程对象--notify随机唤醒一个--notifyAll唤醒所有
notifyAll();//也是Object里面的
}
}
package com.aaa.demo10;
public class SaveTask implements Runnable{
//属性:银行卡
private BankCard bankCard;
public SaveTask(BankCard b){
bankCard=b;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
bankCard.save(1000);
}
}
}
package com.aaa.demo10;
public class TakeTak implements Runnable {
//属性:银行卡
private BankCard bankCard;
public TakeTak(BankCard b){
bankCard=b;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
bankCard.take(1000);
}
}
}
package com.aaa.demo10;
public class Test {
public static void main(String[] args) {
BankCard bankCard = new BankCard();//创建银行卡
SaveTask saveTask = new SaveTask(bankCard);//存钱
TakeTak takeTak = new TakeTak(bankCard);//取钱
Thread t01 = new Thread(saveTask,"小白");
Thread t02= new Thread(takeTak,"小黑");
t01.start();
t02.start();
}
}
"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:E:\ideal\IntelliJ IDEA 2022.3.1\lib\idea_rt.jar=52289:E:\ideal\IntelliJ IDEA 2022.3.1\bin" -Dfile.encoding=UTF-8 -classpath D:\ideacj\duoXianCheng\target\classes com.aaa.demo10.Test
小白往卡中存入了1000;卡中余额:1000元
小黑往卡中取了1000;卡中余额:0元
小白往卡中存入了1000;卡中余额:1000元
小黑往卡中取了1000;卡中余额:0元
小白往卡中存入了1000;卡中余额:1000元
小黑往卡中取了1000;卡中余额:0元
小白往卡中存入了1000;卡中余额:1000元
小黑往卡中取了1000;卡中余额:0元
小白往卡中存入了1000;卡中余额:1000元
小黑往卡中取了1000;卡中余额:0元
小白往卡中存入了1000;卡中余额:1000元
小黑往卡中取了1000;卡中余额:0元
小白往卡中存入了1000;卡中余额:1000元
小黑往卡中取了1000;卡中余额:0元
小白往卡中存入了1000;卡中余额:1000元
小黑往卡中取了1000;卡中余额:0元
小白往卡中存入了1000;卡中余额:1000元
小黑往卡中取了1000;卡中余额:0元
小白往卡中存入了1000;卡中余额:1000元
小黑往卡中取了1000;卡中余额:0元
Process finished with exit code 0
sleep和wait方法的区别?
1.来自不同的类:
sleep的类来自Thread,而wait来自Object类
2.是否释放锁资源:
slepp不会释放锁资源,因为slepp休眠的时候不会释放,只有休眠结束才释放资源
wait会释放锁资源,因为它会把锁释放,然后把它放到等待队列中
3.用法不同:
sleep休眠时间到了自动会醒,而wait需要调用notify或notifyAll()方法才会醒
4.notify和notifyAll方法的区别
notifyAll会唤醒所有的线程,notify之后唤醒一个线程。notifyAll 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。
7.线程的六种状态
new:新建状态
runnable: start()就绪状态--时间片--运行状态,统称为runnable
blocked: 堵塞状态 加锁时就如该状态
waiting: 无期等待 调用wait方法时会进入该状态
timed_waiting: 有期等待---当调用sleep 方法时就会进入该状态
terminated : 终止状态.线程的任务代码执行完毕或出现异常
线程的状态之间可以通过调用相应的方法,进行转换。
8.线程池
吃饭买碗的故事
假设小A同学中午吃饭,但是家里没有碗了,然后去买碗了,就可以吃饭了,吃完饭小A同学懒得刷碗了,就把碗给摔了,过了半天到了晚上,小A同学要吃晚饭,这个时候发现家里还是没有碗,因为白天的碗被摔了,于是他只好又去买个碗吃饭,吃完饭又把碗给摔了
吃饭买碗的故事(问题)
1.每次都要买碗,浪费时间
2.每次吃完都把碗摔了,浪费资源
吃饭买碗的故事(解决方案)
准备一个碗柜就可以了,第一次还是要买碗的,只不过用完了之后不要摔,把它放到碗柜当中,下一次要用到的时候,再从碗柜中拿出来就可以了,这样就可以解决刚刚所遇到的问题
以前写多线程的弊端
弊端1:用到线程的时候就创建 弊端2:用完之后线程消失
改进:
跟刚刚的故事一样,我们也去准备一个容器,用来存放线程,这个容器就叫做线程池
刚开始线程池是空的没有线程,当给它提交一个任务的时候,线程池它本身就自动的去创建一个线程,拿着这个线程去执行任务,执行完了,再把线程还回去,第二次提交一个任务的时候,它就不需要创建新线程了,而是拿着已经存在的线程去执行任务,执行完了,再还回去
线程池(特殊情况)
如果在提交第二个任务的时候,线程还正在执行第一个任务,还没有还回去,此时,线程池就会创建一个新的线程,拿着新的线程去执行第二个任务,在这个时候,又提交了很多任务,此时,线程池就会继续创建新的线程,执行新提交的任务,任务执行完毕,它们会把线程再还给线程池
问题:那么线程池的没有上限吗?会一直创建
线程池有上限的,而且这个上限我们也可以自己设置,假设设置了最大限制数量为3,这3个线程它就会执行前面三个任务,后面的任务只能排队等着
线程池主要核心原理
1.创建一个池子,池子中是空的
2.提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可
3.但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
线程池代码实现
1.创建线程池
Executors:
线程池的工具类通过调用方法返回不同类型的线程池对象,该类提供了创建线程池的一些静态方法
static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池(可变线池) static ExecutorService newFixedThreadPool(int nThreads) 创建有上限的线程池 static Executors.newSingleThreadExecutor 单一线程池 static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 延迟线程池
Executor:它是线程的根接口:
void execute(Runnable command):执行Runnable类型的任务。 ExecutorService: 它是Executor的子接口。--- void shutdown():关闭线程池。需要等任务执行完毕。 shutdownNow(); 立即关闭线程池。 不在接受新的任务。 isShutdown(): 判断是否执行了关闭。 isTerminated(): 判断线程池是否终止。表示线程池中的任务都执行完毕,并且线程池关闭了 submit(Callable<T> task);提交任务,可以提交Callable submit(Runnable task): 提交任务,可以提交Runnable任务
线程任务
//提交任务
for (int i = 0; i < 5; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"======");
}
});
}
固定长度的线程池
//1.创建一个固定长度的线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
pool-1-thread-2======
pool-1-thread-1======
pool-1-thread-5======
pool-1-thread-3======
pool-1-thread-4======
单一线程池
//2.单一线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
"C:\Program Files\Java\jdk-17\bin\java.exe" "-javaagent:E:\ideal\IntelliJ IDEA 2022.3.1\lib\idea_rt.jar=57030:E:\ideal\IntelliJ IDEA 2022.3.1\bin" -Dfile.encoding=UTF-8 -classpath D:\ideacj\duoXianCheng\target\classes com.bbb.a07tjreadcase7.ThreadDemo
pool-1-thread-1======
pool-1-thread-1======
pool-1-thread-1======
pool-1-thread-1======
pool-1-thread-1======
Process finished with exit code 0
创建一个没有上线线程池(可变线程池--缓存线程池)
//3. 创建一个没有上限的线程池(可变线程池--缓存线程池)
ExecutorService executorService = Executors.newCachedThreadPool();
注意:在创建多个任务的时候,前边的任务也会把线程池还回去,创建的速度没有还的快,所以有时候创建多少个线程不一定
延迟线程池
注意:用延迟线程池就不能用submit了,用了也没有延迟,它里面提供了自带的延迟方法
//4.延迟线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);//长度
第一个参数是传递的任务,第二个是延迟时间,延迟时间的单位
//提交任务
for (int i = 0; i < 10; i++) {
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"======");
}
},10, TimeUnit.SECONDS);
}
代码总览:
package com.bbb.a07tjreadcase7;
import com.bbb.a03tjreadcase3.MyCallable;
import java.util.concurrent.*;
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
/*
Executors: 它是线程池的工具类,该类提供了创建线程池的一些静态方法
static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池(可变线池)
static ExecutorService newFixedThreadPool(int nThreads) 创建有上限的线程池
static Executors.newSingleThreadExecutor 单一线程池
static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 延迟线程池
Executor: 它是线程池的根接口:
void execute(Runnable command):执行Runnable类型的任务。
ExecutorService: 它是Executor的子接口。---
void shutdown():关闭线程池。需要等任务执行完毕。
shutdownNow(); 立即关闭线程池。 不在接受新的任务。
isShutdown(): 判断是否执行了关闭。
isTerminated(): 判断线程池是否终止。表示线程池中的任务都执行完毕,并且线程池关闭了
submit(Callable<T> task);提交任务,可以提交Callable
submit(Runnable task): 提交任务,可以提交Runnable任务
*/
//1.创建一个固定长度的线程池
// ExecutorService executorService = Executors.newFixedThreadPool(5);
//2.单一线程池
//ExecutorService executorService = Executors.newSingleThreadExecutor();
//3. 创建一个没有上限的线程池(可变线程池--缓存线程池)
// ExecutorService executorService = Executors.newCachedThreadPool();
//4.延迟线程池
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);//长度
//提交任务
for (int i = 0; i < 10; i++) {
/* executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"======");
}
});*/
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"======");
}
},10, TimeUnit.SECONDS);
}
//3.销毁线程池
executorService.shutdown();
}
}
2.提交任务
注意:提交任务的时候,线程池底层它会去创建线程,或者复用已经存在的线程,这些代码不需要我们自己写,是线程池底层自动去完成,我们只需要提交任务就可以了
3.所有的任务全部执行完毕,关闭线程池
在实际开发中,线程池一般是不会关闭的,因为服务器是24小时运行,比如说登录百度24小时都能登录,玩游戏的时候,24小时都可以玩,所以服务器是不会关闭的,服务器不关闭,就会随时随地有新的任务,那么线程池也不会关闭
9.自定义线程池
上面通过Executors工具类创建线程池,但是阿里巴巴不建议使用,阿里建议使用原生的模式创建线程池,因为比较灵活,自己可以根据需求来创建
package com.aaa.demo12;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class TestDemo {
public static void main(String[] args) {
//上面通过Executors工具类创建线程池,但是阿里巴巴不建议使用,阿里建议使用原生的模式创建线程池
/*
int corePoolSize,核心线程的个数
int maximumPoolSize,最多的线程个数
long keepAliveTime, 线程空闲时长。
TimeUnit unit, 空闲的单位
BlockingQueue<Runnable> workQueue:等待队列
*/
BlockingQueue<Runnable> workQueue=new ArrayBlockingQueue<>(5);//最多等待5个任务
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,10,100, TimeUnit.SECONDS,workQueue);
//任务:
for(int i=0;i<15;i++){
executor.submit(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~");
}
});
}
//销毁任务
executor.shutdown();
}
}
注意:如果提交任务数量超过了做多线程单位+等待队列单位的数量,就会拒绝
发现线程执行非常麻烦。都使用线程池来执行任务。---不要自己创建线程对象,而是使用线程池中的对象
以前写法
package com.aaa.demo13;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建MyCallable的对象 (表示多线程要执行的任务)
MyCallable myCallable = new MyCallable();
// 创建FutureTask的对象 (作用管理多线程的运行的结果)
FutureTask<Integer> integerFutureTask = new FutureTask<>(myCallable);//把线程任务封装到该类中,该类可以获取线程任务执行后的结果.
//创建Thread类的对象
Thread thread = new Thread(integerFutureTask);
//并启动线程
thread.start();
//获取多线程运行的结果
System.out.println(integerFutureTask.get());
}
}
class MyCallable implements Callable<Integer>{
//线程任务
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i < 100; i++) {
sum+=i;
}
return sum;
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
}
}
用线程池来写
package com.aaa.demo13;
import java.util.concurrent.*;
public class TestDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
//创建MyCallable的对象 (表示多线程要执行的任务)
MyCallable myCallable = new MyCallable();
// 创建FutureTask的对象 (作用管理多线程的运行的结果)
FutureTask<Integer> integerFutureTask = new FutureTask<>(myCallable);//把线程任务封装到该类中,该类可以获取线程任务执行后的结果.
//创建Thread类的对象
Thread thread = new Thread(integerFutureTask);
//并启动线程
thread.start();
//获取多线程运行的结果
System.out.println(integerFutureTask.get());
*/
//1.创建一个线程池对象
ExecutorService executorService = Executors.newSingleThreadExecutor();
//2.提交任务
Future<Integer> submit = executorService.submit(new MyCallable());
//3.获取返回结果
System.out.println(submit.get());
//4.关闭线程池
executorService.shutdown();
}
}
class MyCallable implements Callable<Integer>{
//线程任务
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i < 100; i++) {
sum+=i;
}
return sum;
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
}
}