多线程技术
概述
线程与进程
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
线程
是进程中的一个执行路径,共享一个内存空间,线程之间可以相互切换,并发执行,一个进程至少有一个线程
线程的调度
同步和异步
同步:排队执行,效率低但是安全
异步:同时执行,效率高但是数据不安全
并发与并行
并发:指两个或者多个事情在同一时间段发生
并行:指两个或者多个事件在同一个时刻发生
如何使用多线程
多线程的内存管理
每个线程都拥有自己的栈空间,共享一份堆内存
画图
实现线程的两种方式
1.继承Thread类
package cn.kkeba.thread;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2320:05
*/
public class main {
public static void main(String[] args) {
//抢占式调度
MyThread n = new MyThread();
n.start(); //开启一个线程使用start方法
for(int i=0;i<10;i++){
System.out.println("主线程"+i);
}
}
}
package cn.kkeba.thread;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2320:03
*/
public class MyThread extends Thread{
@Override
public void run() {
// super.run(); run方法里定义了线程的任务
for(int i=0;i<10;i++){
System.out.println("m线程"+i);
}
}
}
通过匿名内部类的方式实现多线程
package cn.kkeba.thread;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2320:26
*/
public class Demo1 {
public static void main(String[] args) {
//匿名内部类的方式 也属于第一种继承Thread类的方式 只执行一次
new Thread(){
@Override
public void run() {
for (int i = 0;i<10;i++){
System.out.println("12345"+i);
}
}
}.start();
for(int i=0;i<10;i++){
System.out.println("一二三四五"+i);
}
}
}
2.实现Ranable接口
package cn.kkeba.thread;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2320:31
*/
public class Demo2 {
public static void main(String[] args) {
//1.创建一个任务对象
MyRunable r = new MyRunable();
//2.创建一个Thread对象,给Thread对象指定一个任务
Thread t = new Thread(r);
//3.开始线程任务
t.start();
for (int i = 0; i <10 ; i++) {
System.out.println("abcede"+i);
}
}
}
Runnable接口相对于继承Thread类的优势
通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时执行相同任务的情况
可以避免单继承带来的局限性
任务与线程本身是分离的,提高了程序的健壮性
后续学习的线程池技术,接受Runnable 类型的任务,不接受 Thread类型的线程
守护线程和用户线程
用户线程:执行任务的线程,平时用到的普通线程均是用户线程,当在Java程序中创建一个线程,它就被称为用户线程
守护线程:守护用户线程的线程,依附于用户线程
是个服务线程,准确地来说就是服务其他的线程,这是它的作用——而其他的线程只有一种,那就是用户线程。所以java里线程分2种,
1、守护线程,比如垃圾回收线程,就是最典型的守护线程。
2、用户线程,就是应用程序里的自定义线程
当一个进程不存在任何的用户线程时,进程结束
当最后一个用户线程结束时,所有守护线程自动死亡
如何把一个子线程设置为守护线程?
package cn.kkeba.thread;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2417:14
*/
public class Demo6 {
public static void main(String[] args) {
//开启子线程
Thread t1 = new Thread(new MyRunnable());
//设置子线程为守护线程 一定要在线程开始之前设置
t1.setDaemon(true);
t1.start();
for (int i = 0; i <5 ; i++) {
System.out.println("main线程"+i);
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <1000 ; i++) {
System.out.println("子线程"+i);
}
}
}
}
线程的一些常用方法
获取线程名称以及设置线程名称
package cn.kkeba.thread;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2322:56
*/
public class Demo3 {
public static void main(String[] args) {
MyTestThread t = new MyTestThread();
Thread th = new Thread(t,"abc");
//调用Thread类的静态方法设置当前线程的名称
Thread.currentThread().setName("dee");
th.start();
System.out.println(Thread.currentThread().getName());
}
static class MyTestThread implements Runnable{
@Override
public void run() {
//调用Thread类的静态方法 currentThread()可以获取到当前线程的实类
System.out.println(Thread.currentThread().getName());
}
}
}
线程休眠
package cn.kkeba.thread;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2323:16
*/
public class Demo4 {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i <10 ; i++) {
System.out.println(i);
//Thread 类的sleep静态方法 参数是毫秒
Thread.sleep(1000);
}
}
}
线程阻塞
可以简单理解为所有需要消耗时间的操作,也称为耗时操作 例如读取文件
线程的中断
一个线程是一个独立的执行路径,他是否应该结束,应该由他自身来决定
之前有stop 方法来杀死一个线程。但是这样做被杀死的线程所占有的资源都无法被释放,从而变成内存垃圾
也无法被回收。
现在的做法是给需要杀死的线程打一个标记,从而触发异常,可以在catch块里处理一些释放资源的操作
杀死线程的方法就是直接run方法return
线程安全问题
package cn.kkeba.thread;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2417:39
*/
public class Demo7 {
public static void main(String[] args) {
Runnable r1 = new SaleTichet();
new Thread(r1).start();
new Thread(r1).start();
new Thread(r1).start();
}
static class SaleTichet implements Runnable {
//票数
private int count = 10;
@Override
public void run() {
while(count>0){
System.out.println("准备卖票:");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("还剩余票"+count);
count--;
}
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VWTSuE3Q-1632589537553)(C:\Users\52769\AppData\Local\Temp\1632484262337.png)]
解决方案
线程的同步的三种方式
1.同步代码块
给可能发生线程安全问题的代码块加上关键字 使用关键字 synchronized
格式:
synchronized(锁对象){
//同步代码块
}
原理:锁对象可以使任何对象,java在使用锁对象的时候会去查找这个对象是否已经加上了锁的标记,如果这个对象
已经有了锁的标记,就会等待代码执行完,并把锁标记清除掉,其他线程就可以获得这个对象
注意:多个线程应该是使用同一个锁对象,不同的线程使用不同的锁对象是不合理的,这样就不会存在同步的问题
package cn.kkeba.thread;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2417:39
*/
public class Demo7 {
public static void main(String[] args) {
Runnable r1 = new SaleTichet();
//启动三个线程
new Thread(r1).start();
new Thread(r1).start();
new Thread(r1).start();
}
static class SaleTichet implements Runnable {
private int count = 10;
Object o = new Object(); //定义一个锁对象
@Override
public void run() {
while(true){
synchronized (o){ //o是锁对象 保证是同一把锁
//同步代码块
if(count >=0){
System.out.println("准备卖票:");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"还剩余票"+count);
count--;
}else {
break;
}
}
}
}
}
}
2.同步方法
给方法加上 synchronized关键字 注意:同步方法也有锁对象,锁就是this 谁调用这个方法谁就是锁对象
但是方法是静态方法 锁对象就是 类名.class
如果同步代码块里传的锁对象也是this 那么 同步方法就会调用不到,因为使用的是同一把锁
package cn.kkeba.thread;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2417:39
*/
public class Demo8 {
public static void main(String[] args) {
Runnable r1 = new SaleTichet();
new Thread(r1).start();
new Thread(r1).start();
new Thread(r1).start();
}
static class SaleTichet implements Runnable {
private int count = 10;
Object o = new Object();
//同步方法
public synchronized boolean sale(){
if(count >=0){
System.out.println("准备卖票:");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"还剩余票"+count);
count--;
return true;
}
return false;
}
@Override
public void run() {
while(true){
boolean flag = sale();
if(!flag){
break;
}
}
}
}
}
3.显示锁
同步代码块和同步方法都属于隐式锁
Lock 子类:ReentrantLock
开锁解锁 更能体现面向对象的思想
package cn.kkeba.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2417:39
*/
public class Demo9 {
public static void main(String[] args) {
Runnable r1 = new SaleTichet();
new Thread(r1).start();
new Thread(r1).start();
new Thread(r1).start();
}
static class SaleTichet implements Runnable {
private int count = 10;
// Object o = new Object();
private Lock l = new ReentrantLock();
@Override
public void run() {
while(true){
l.lock(); //使用锁
if(count >=0){
System.out.println("准备卖票:");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"还剩余票"+count);
count--;
}else {
break;
}
l.unlock(); //解开锁
}
}
}
}
拓展:显示锁和隐式锁的区别
公平锁和非公平锁
1000个线程来排队,按照先到先得的机制就是公平锁,否则就是非公平锁
java默认采用的都是非公平锁 大家一块抢
设置公平锁
// fair 参数 设置为 true 表示公平锁
private Lock l = new ReentrantLock(true);
线程死锁
避免死锁
在使用一个锁对象的时候尽量避免使用到另外一个锁
多线程通信
生产者和消费者的问题
生产者生产的时候消费者休眠,生产完唤醒消费者消费
消费者消费的时候生产者休眠,消费完唤醒生产者生产 通过 wait() 和 notify()方法或者notifyAll()方法
线程的六种状态
带返回值的线程 Callble接口
创建线程的步骤:
1.创建一个类实现Callble接口
2.创建
常用的方法
get 方法:会使主线程等待子线程执行完毕再 执行
线程池
概述
是一种容器
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。
为什么要使用线程池
频繁创建线程销毁线程非常耗时
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?在Java中可以通过线程池来达到这样的效果
创建线程
创建任务
执行任务
关闭线程
主要耗时的是创建线程和关闭线程
java的四种线程池
缓存线程池
执行流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池,然后使用
4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
package cn.kkeba.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
*
* @date 2021/9/2522:38
*/
public class ThreadPool {
public static void main(String[] args) {
//1.创建一个缓存线程池对象
ExecutorService service = Executors.newCachedThreadPool();
//2.给线程池里的线程分配一个任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
//主线程休眠一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3.再次执行任务 看是否是线程池里缓存的线程在执行
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
}
}
//执行结果
pool-1-thread-1锄禾日当午
pool-1-thread-2锄禾日当午
pool-1-thread-3锄禾日当午
pool-1-thread-3锄禾日当午
定长线程池
长度是指定的数值
任务加入后的执行流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池,然后使用
4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
package cn.kkeba.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2522:48
*/
public class Demo11 {
public static void main(String[] args) {
//1.通过Executors类的静态方法 创建定长线程池对象 长度为2
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"哈哈哈");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"哈哈哈");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"哈哈哈");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
//运行结果
pool-1-thread-2哈哈哈
pool-1-thread-1哈哈哈
pool-1-thread-1哈哈哈
单线程线程池
执行流程
1.判断线程池 的 那个线程是否空闲
2.空闲则使用
3.不空闲,则等待池中的单个线程空闲后使用
package cn.kkeba.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2523:07
*/
public class Demo12 {
public static void main(String[] args) {
//1.创建单线程池对象
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
}
}
//运行结果
pool-1-thread-1锄禾日当午
pool-1-thread-1锄禾日当午
pool-1-thread-1锄禾日当午
pool-1-thread-1锄禾日当午
周期任务 定长线程池
执行流程:
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池,然后使用
4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
周期性人物执行时:
定时执行,当某个时机触发时,自动执行某任务
package cn.kkeba.thread;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* @author Lazy Programmer
* @Title:
* @Package
* @Description:
* @date 2021/9/2523:32
*/
public class Demo13 {
public static void main(String[] args) {
//1.创建一个具有定时定长的线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
//2.给线程池里的线程设置定时任务
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},5,1,TimeUnit.SECONDS);
//initialDelay 参数是指延迟几秒开始执行任务 period 参数指 每隔几秒执行一次任务 Unit参数是设定时间单位
}
}
//运行结果: delay 5s 后 每隔1s输出 一次 线程名+锄禾日当午
//pool-1-thread-2锄禾日当午
pool-1-thread-2锄禾日当午
pool-1-thread-2锄禾日当午
pool-1-thread-1锄禾日当午
...无线循环
Lambda表达式
函数式编程思想
jdk 1.8 引入 Lambda表达式 只留下方法参数和方法实现
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
-
@author Lazy Programmer
-
@Title:
-
@Package
-
@Description:
-
@date 2021/9/2523:32
*/
public class Demo13 {
public static void main(String[] args) {
//1.创建一个具有定时定长的线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);//2.给线程池里的线程设置定时任务 service.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"锄禾日当午"); } },5,1,TimeUnit.SECONDS); //initialDelay 参数是指延迟几秒开始执行任务 period 参数指 每隔几秒执行一次任务 Unit参数是设定时间单位
}
}
//运行结果: delay 5s 后 每隔1s输出 一次 线程名+锄禾日当午
//pool-1-thread-2锄禾日当午
pool-1-thread-2锄禾日当午
pool-1-thread-2锄禾日当午
pool-1-thread-1锄禾日当午
…无线循环
---
### Lambda表达式
函数式编程思想
jdk 1.8 引入 Lambda表达式 只留下方法参数和方法实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-72RuJF06-1632589537558)(C:\Users\52769\AppData\Local\Temp\1632588990555.png)]