#多线程详解
线程,进程
什么是进程?
开发写的代码称为程序,那么我们将程序运行起来,我们称之为进程;当我们运行一个程序,那么我们将运行的程序叫进程;进程就是申请一块内存空间,将数据放到内存空间中去,是申请数据的过程中最小的资源管理单元;进程是线程的容器;
程序与进程的区别
程序是数据和指令的集合,是一个静态的概念,就是一堆代码,可以长时间的保存在系统中;
进程是程序运行的过程,是一个动态的概念,进程存在着生命周期,也就是说进程会随着程序的终止而销毁,不会永久存在系统中;
进程间的交互
进程之间通过 TCP/IP 端口实现的
什么是线程?
01.线程是操作系统能够进行运算调度的最小单位;
02.被包含在进程之中,是进程中的实际运作单位;
03.一个线程指进程中一个单一顺序的控制流,一个进程可以并发多个线程,每条线程并行执行不同的任务;
04.是进程的一条流水线,只用来执行程序,而不涉及到申请资源,是程序的实际执行者,是最小的执行单位;
线程之间的交互
多个线程共享同一内存,通过共享的内存空间来交互
举例说明,进程和线程的关系
比如说我们用手机打开微信,运行的微信就是开启的一个线程,当我们使用微信的一些功能,扫一扫,付款等,这些都是线程;
进程包含线程,线程属于进程的子集;
进程池
进程池是资源进程,管理进程组成的应用及技术;
为什么要有进程池?
😮忙时会有成千上万的任务需要被执行,闲时可能只有零星任务。
😒那么在成千上万个任务需要被执行的时候,我们就需要去创建成千上万个进程么?
😓首先,创建进程需要消耗时间,销毁进程也需要消耗时间。
😟第二即便开启了成千上万的进程,操作系统也不能让他们同时执行,这样反而会影响程序的效率。
😥因此我们不能无限制的根据任务去开启或者结束进程。那么我们要怎么做呢?
进程池的概念
1.定义一个池子,里面放固定数量的进程,有需求来了,就从池子里拿一个进程来处理任务;
2.任务处理完毕,进程并不关闭,而是将进程放回池子中等待任务;
3.如果有很多任务要执行,池子中进程数量不够,任务就要等待进程执行完之后,拿到空闲的进程才能执行;
4.池子中进程数量是固定的,也就是说同一时间最多能执行固定经常的任务
5.这样不会增加操作系统的调度难度,还节省了开关进程的时间,也能在一定程度上实现并发效果;
资源进程
预先创建好空的进程,管理进程会把任务分发到空闲进程来处理
管理进程
管理进程负责创建资源进程,把工作交给空闲资源处理,回收已经处理完的资源进程;
资源进程与管理进程的交互
管理进程如何有效的管理资源进程,分配任务给资源进程?
通过 IPC,信号,信号量,消息队列,管道等进行交互。
并发、并行和串行
并发:多个任务看起来同时执行,这是一种假并行;
单核下使用多道技术实现;
并行:多个任务同时进行
并行必须有多核才能实现,否则只能实现并发
串行:一个程序处理完当前进程,接着处理下一个进程,一个一个连着进行
进程的三态
进程在运行的过程中不断的改变其运行状态;
通常一个运行的进程必须要有三个状态,就绪态,运行态,阻塞态
1. 就绪态
当进程获取除 CPU 外所有的资源后,只要在获得 CUP 就可执行程序,这时的状态叫做就绪态;
在一个系统中处于就绪态的进程会有多个,通常把这些排成一个队列,这个就叫就绪队列;
2. 运行态
当进程已经获得 CPU 操作权限,正在运行,这个时间就是运行态
在单核系统中,同一个时间只能有一个运行态,多核系统中,会有多个运行态;
3. 阻塞态
正在执行的进程,等待某个事件而无法继续运行时,便**作系统剥夺了 cpu 操作权限,这时时阻塞态;
引起阻塞的原因有很多,等待 I/O 操作,被更高优先级的进程剥夺了 cpu 权限等;
线程的方法
方法 | 说明 |
---|---|
setPriority (int newPriority) | 更改线程的优先级 |
sleep (long millis) | 制定毫秒数,让当前运行的线程休眠 |
join () | 等待该线程的终止 |
yield () | 暂停当前正在执行的线程对象,并执行其他的线程 |
interrupt () | 中断线程 |
isAlive | 线程是否在活跃状态 |
Java 使用多线程
创建线程的三种方式
继承 Thread 类
public class MyThread extends Thread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("继承Thread实现了第"+i+"次调用;");
}
}
}
/**
* 继承thread 实现多线程
* 1. 类继承Thread
* 2. 重写run()
* 3. start() 调用
*/
@Test
public void extendThreadTest(){
MyThread myThread = new MyThread();
myThread.start();
int ii = 20;
for (int i = 0; i < ii ; i++) {
System.out.println("main调用了"+i+"次;");
}
}
实现 Runable 接口
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("实现Runnable实现了第"+i+"调用;");
}
}
}
/**
* 实现Runnable接口实现多线程
* 1. 类实现Runnable
* 2. 重写run()
* 3. start() 调用
*/
@Test
public void implementsRunnableTest(){
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
for (int i = 0; i < 20; i++) {
System.out.println("main调用了"+i+"次;");
}
}
实现 Callable 接口
实现 Callable 接口, 需要返回值
重写 call 方法,需要抛出异常
创建目标对象
创建执行服务,: ExecutorService service = Executors.newCachedThreadPool();
提交执行:提交执行:Future<> submit = service. Submit (对象);
获取结果 :Boolean aBoolean = submit. Get ();
关闭服务:service. ShutdownNow ();
public class MyCallable implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
return false;
}
}
@Test
public void implementCallableTest() throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
ExecutorService service = Executors.newCachedThreadPool();
Future<Boolean> submit = service.submit(myCallable);
Boolean aBoolean = submit.get();
service.shutdownNow();
}
线程的状态
New
线程已创建,尚未启动
Runnable
在 Java 虚拟机中正在执行的线程
Blocked
被等待监视器锁定的线程
Waiting
正在等待另一个线程执行特定动作的线程处于此状态
Timed_waiting
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态
Terminated
已退出的线程
/**
* @ClassName: MyThreadState
* @Description: 观察线程的状态
* 通过state方法可以观察线程的状态
*/
public class MyThreadState implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(i==5 && i<10){
try {
Thread.sleep(1);
System.out.println("阻塞时的状态"+Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+i+"线程正在执行的状态:"+Thread.currentThread().getState());
}
}
}
public static void main(String[] args) {
MyThreadState myThreadState = new MyThreadState();
Thread thread = new Thread(myThreadState);
Thread.State state = thread.getState();
System.out.println("线程创建未启动时状态:"+state);
thread.start();
state = thread.getState();
System.out.println("线程启动后的状态:"+state);
while (thread.getState()!= Thread.State.TERMINATED){
state = thread.getState();
System.out.println(thread.getName()+"线程运行时状态:"+state);
}
state = thread.getState();
System.out.println("线程结束状态:"+state);
System.out.println(Thread.currentThread().getName()+"线程优先级"+Thread.currentThread().getPriority());
}
线程停止
建议线程正常停止; -> 一般是利用次数
建议使用标志位;
不建议使用 stop 和 destroy 方法,已过时
public class MyThreadStop implements Runnable {
/**标识位判断线程是否停止*/
private boolean flag = true;
@Override
public void run() {
int i =0;
while (flag){
System.out.println(Thread.currentThread().getName()+""+i++);
}
}
/**设置一个公开的方法停止线程*/
public void stopThread(boolean flag){
this.flag=flag;
}
}
public static void main(String[] args) {
MyThreadStop myThreadStop = new MyThreadStop();
Thread thread = new Thread(myThreadStop);
thread.start();
for (int i = 0; i < 1000; i++) {
if(i==200){
myThreadStop.stopThread(false);
}
System.out.println(Thread.currentThread().getName()+"执行"+i);
}
}
线程的优先执行
Join 主要作用是优先执行
/**
* @ClassName: MyThreaJoin
* @Description: join 主要作用是优先执行
*/
public class MyThreaJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"vip线程开始跑!"+i);
}
}
}
public static void main(String[] args) {
//启动线程
MyThreaJoin threaJoin = new MyThreaJoin();
Thread thread = new Thread(threaJoin);
thread.start();
for (int i = 0; i < 20; i++) {
if(i==10){
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+i+"正在跑!");
}
}
线程的优先级
SetPriority (int i ) 设置线程优先级 GetPriority () 获取线程的优先级
class TestPriority extends Thread{
@Override
public void run() {
System.out.println(TestPriority.currentThread().getName()+"的优先级"+TestPriority.currentThread().getPriority());
}
}
public class MyThreadPriority {
public static void main(String[] args) {
TestPriority priority1 = new TestPriority();
TestPriority priority2 = new TestPriority();
TestPriority priority3 = new TestPriority();
TestPriority priority4 = new TestPriority();
TestPriority priority5 = new TestPriority();
System.out.println(Thread.currentThread().getName()+"线程优先级"+Thread.currentThread().getPriority());
priority1.setPriority(6);
priority1.start();
// System.out.println("priority1"+priority1.getPriority());
priority2.setPriority(2);
priority2.start();
// System.out.println("priority2"+priority2.getPriority());
priority3.setPriority(3);
priority3.start();
// System.out.println("priority3"+priority3.getPriority());
priority4.setPriority(8);
priority4.start();
// System.out.println("priority4"+priority4.getPriority());
priority5.setPriority(5);
priority5.start();
// System.out.println("priority5"+priority5.getPriority());
}
}
守护线程
线程分为用户线程和守护线程\
虚拟机必须确保用户线程执行完\
虚拟机不必等待守护线程执行完毕\
如:后台记录日志,监控内存,垃圾回收线程等
/**
* 上帝,守护线程
*/
class God implements Runnable{
@Override
public void run() {
long l = 0;
while (true){
out.println("上帝保佑你!"+l++);
}
}
}
/**
* you 用户线程
*/
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 35000; i++) {
out.println("每天都在健康的活着"+i);
}
out.println("you goodBye world!");
}
}
/**
* 启动用户线程和守护线程
*/
public class MyThreadDaemon {
public static void main(String[] args) {
You you = new You();
God god = new God();
Thread youThread = new Thread(you);
Thread godThread = new Thread(god);
godThread.setDaemon(true);
youThread.start();
godThread.start();
}
}
Synchronized 方法
Sybchronized 方法控制对对象的访问,每个对象对应一把锁,每个 synchronized 方法都必须获取调用方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道方法返回才释放锁,后面被阻塞的线程才能获得锁,继续执行; 若将一个大的方法申明为 synchronized 将会影响概率 同步代码块:synchronized(obj){} Obj 称之为同步监视器 obj 可以是任何对象,但是推荐使用共享资源作为同步监视器 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是 this,这个对象本身; 同步监视器的执行过程 01.第一个线程访问,锁定同步监视器,执行其中的代码 02.第二个线程访问,发现同步监视器被锁定,无法访问 03.第一个线程访问完毕,解锁同步监视器 04.第二个线程访问,发现同步监视器没有锁,然后锁定并访问;
多人购票的问题
class BuyTicket implements Runnable{
//票数
private int ticketNums= 10;
//外部停止方式
boolean flag = true;
@Override
public void run() {
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
private synchronized void buy() throws InterruptedException {
Thread.sleep(100);
//判断是否有票
if(ticketNums<=0){
flag=false;
return;
}
System.out.println(Thread.currentThread().getName()+"买到了第"+ticketNums--+"张票;");
}
}
/**
* @ClassName: MyThreadSyn
* @Description: 线程同步的问题-多人购票
*/
public class MyThreadSyn1 {
public static void main(String[] args) {
BuyTicket buyTicket1 = new BuyTicket();
Thread you = new Thread(buyTicket1, "you");
Thread me = new Thread(buyTicket1, "me");
Thread other = new Thread(buyTicket1, "other");
you.start();
me.start();
other.start();
}
}
银行取钱问题
-
创建账户信息
@Data @AllArgsConstructor class Account { /**余额*/ private int money; /**卡名*/ private String name; }
-
模拟银行取钱
/** * 模拟银行取钱 */ class Drawing extends Thread{ private final Account account; /**现有的钱*/ private int nowMoney; /**取出的钱*/ private int drawingMoney; public Drawing( Account account, int drawingMoney,String name) { super(name); this.account = account; this.drawingMoney = drawingMoney; } @Override public void run() { synchronized (account){ //判断有没有钱 if(account.getMoney()-drawingMoney<0){ out.println("你的账户没有那么多钱!"); return; }try { sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //余额 account.setMoney( account.getMoney() - drawingMoney); //手中的钱 nowMoney=nowMoney+drawingMoney; out.println("余额为:"+account.getMoney()); out.println(Thread.currentThread().getName()+"手中的钱:"+nowMoney); } } }
-
测试取钱流程
/** * @ClassName: MyThreadSyn2 * @Description: 线程同步的问题-多人从银行取钱 */ public class MyThreadSyn2 { public static void main(String[] args) { Account account = new Account(100, "工商"); Drawing me = new Drawing(account, 40, "me"); Drawing girlFriend = new Drawing(account, 89, "girlFriend"); me.start(); girlFriend.start(); } }
ArrayList 线程不安全
/**
* @ClassName: MyThreadSyn
* @Description: 线程同步的问题-ArrayList线程不安全
*/
public class MyThreadSyn3 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
new Thread(()-> {
synchronized(arrayList){
arrayList.add(Thread.currentThread().getName());
}
}).start();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
out.println(arrayList.size());
}
}
注意: 如果将线程等待去掉,还是会有线程不安全,暂不明白其原理,待后续搞明白
死锁
多个线程各自占有一些共享资源,并且相互等待其他线程占用的资源才能运行,而导致两个或两个以上的线程都在等待对方释放资源,都停止执行的情形;某一个同步块同时拥有两个对象以上的锁,就有可能发生死锁
产生死锁的必要条件
互斥条件:一个线程只能被一个人使用
请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
不剥夺条件: 进程以获得的资源,在未使用完之前,不能剥夺;
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源的关系
实现一个死锁
/**
* 口红
*/
class Lipstick{
}
/**
* 镜子
*/
class Mirroe{
}
/**
* 写一个抢夺资源的方法
*/
class Makeup extends Thread{
static Lipstick lipstick = new Lipstick();
static Mirroe mirroe = new Mirroe();
int choice;
String girlName;
public Makeup(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void makeup() throws InterruptedException {
if(choice==0){
synchronized (lipstick){
System.out.println(this.girlName+"获取口红的锁");
Thread.sleep(1000);
synchronized (mirroe){
System.out.println(this.girlName+"获取镜子的锁");
}
}
}else{
synchronized (mirroe){
System.out.println(this.girlName+"获取镜子的锁");
Thread.sleep(1000);
synchronized (lipstick){
System.out.println(this.girlName+"获取口红的锁");
}
}
}
}
}
Lock
Java 提供了更强大的线程同步机制—通过显式定义同步锁对象来实现同步;同步锁使用 Lock 对象充当;
Lock 接口时控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lokc 对象;
ReentrantLock 类实现了 Lock, 他拥有 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的时 ReentrantLock,可以显示枷锁释放锁;
/**
* 多人买票,用lock的方式实现
*/
class TestLock2 implements Runnable{
int ticketNums=10;
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
if(ticketNums>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"买到了票"+ticketNums--);
}else {
break;
}
}finally {
lock.unlock();
}
}
}
}
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2,"you").start();
new Thread(testLock2,"me").start();
new Thread(testLock2,"other").start();
}
}
线程通信
方法名 | 作用 |
---|---|
wait () | 表示线程一直等待,直到其他线程通知,与 sleep 不同会释放锁 |
wait (long timeOut) | 指定等待的毫秒数 |
notify () | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象所有的调用 wait()方法的线程,优先级高的优先调度 |
线程通讯,生产消费模式-管程法
/**
* 产品-鸡
*/
@AllArgsConstructor
class Chicken{
int id;
}
/**
* 生产者
*/
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container){
this.container=container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.pushTo(new Chicken(i));
System.out.println("生产了"+i+"鸡");
}
}
}
/**
* 消费者
*/
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container){
this.container=container;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了第"+container.popTo().id+"只鸡");
}
}
}
class SynContainer{
/**定义一个容器的大小*/
Chicken[] chickens = new Chicken[10];
/** 容器计数器*/
int count;
/**
* 生产者想容器中放入产品
* @param chicken
*/
public synchronized void pushTo(Chicken chicken){
//如果容器满了,就需要等待消费者消费
if(chickens.length==count){
//通知消费者消费
try {
this.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满就丢入产品
chickens[count]=chicken;
count++;
//通知消费者消费
this.notifyAll();
}
/**
* 消费者从中取出产品
* @return
*/
public synchronized Chicken popTo(){
//判断能否消费
if(count==0){
// 消费者等待生产者产出
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果可以消费
count--;
Chicken chicken = chickens[count];
//吃完了通知生产者消费
this.notifyAll();
return chicken;
}
}
线程通讯,生产消费模式-信号灯法
代理模式
-
不属于23种设计模式之一,只是在代码种使用的模式 1. 创建一个公共接口; 2. 一个真实的类,实现公告接口 3. 一个代理类实现公共接口,代理类中要有真实的类的属性 4. 通过代理类,实现真实类的方法
/**
* 结婚接口
*/
interface Marry{
/**
* 实现结婚的方法
*/
void happenMarry();
}
/**
真实角色
*/
class Men implements Marry{
private String name;
public Men(String name) {
this.name = name;
}
@Override
public void happenMarry() {
System.out.println(this.name+ "结婚");
}
}
/**
* 婚庆公司
*/
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void happenMarry() {
before();
this.target.happenMarry();
after();
}
private void after() {
System.out.println("婚礼结束,准备收钱!");
}
private void before() {
System.out.println("婚礼准备中!");
}
}
public class DynamicProxy {
public static void main(String[] args) {
Marry sun = new Men("sun");
WeddingCompany weddingCompany = new WeddingCompany(sun);
weddingCompany.happenMarry();
}
}
多线程例子
多图片下载
/**
* @ClassName: WebDownLoader
* @Description: 使用commas 类中的数据进行图片下载
*/
public class WebDownLoader {
public void downLoader(String filaUrl,String fileName){
try {
FileUtils.copyURLToFile(new URL(filaUrl), new File(fileName));
System.out.println("成功执行了方法");
}
catch (IOException e) {
e.printStackTrace();
System.out.println("运行了下载器");
}
}
}
/**
* @ClassName: MyThread
* @Description: Thread 实现多图片下载
*/
public class MyThreadDown extends Thread {
private String fileUrl;
private String fileName;
public MyThreadDown(String fileUrl, String fileName) {
this.fileUrl = fileUrl;
this.fileName = fileName;
System.out.println("成功构造");
}
@Override
public void run(){
System.out.println("调用下载器");
WebDownLoader loader = new WebDownLoader();
loader.downLoader(fileUrl,fileName);
System.out.println("下载文件:"+fileName);
}
}
/**
* 下载网络图片
*/
@Test
public void downImage(){
MyThreadDown m1 = new MyThreadDown("https://scpic.chinaz.net/files/pic/pic9/202112/bpic25011.jpg", "1.jpg");
MyThreadDown m2 = new MyThreadDown("https://scpic.chinaz.net/files/pic/pic9/202112/bpic25012.jpg", "2.jpg");
MyThreadDown m3 = new MyThreadDown("https://scpic.chinaz.net/files/pic/pic9/202112/bpic25013.jpg", "3.jpg");
m1.start();
m2.start();
m3.start();
}
火车票抢票问题的模拟
/**
* @ClassName: ConcurrencyQuestion
* @Description: 并发问题的模拟
* 火车票抢票模拟
* 多线程是不安全的,多线程处理会导致同一个票被多个人抢到
*/
public class ConcurrencyQuestion implements Runnable {
/** 票数 */
private int ticketNums = 10;
@Override
public void run() {
while (true){
if (ticketNums == 0) {
break;
}
System.out.println(Thread.currentThread().getName()+"拿到了票"+ticketNums--);
}
}
public static void main(String[] args) {
ConcurrencyQuestion concurrencyQuestion = new ConcurrencyQuestion();
new Thread(concurrencyQuestion,"sun").start();
new Thread(concurrencyQuestion,"sun1").start();
new Thread(concurrencyQuestion,"sun2").start();
}
}
龟兔赛跑
/**
* @ClassName: Race
* @Description: 龟兔赛跑模拟
*/
public class Race implements Runnable {
/**胜利者*/
private String winner;
@Override
public void run() {
for (int i = 0; i <= 1000; i++) {
if (getOver(i)) {
break;
}
//让乌龟稳赢,给兔子下绊子
if(Thread.currentThread().getName().equals("兔")&&i==50){
try {
Thread.sleep(10);
System.out.println("兔子被石头绊倒了!");
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"步;");
}
}
/**判断比赛是否结束*/
public boolean getOver(int i){
if(winner!=null){
return true;
}else {
if(i==100){
winner = Thread.currentThread().getName();
System.out.println(winner+"赢得了比赛!");
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔").start();
new Thread(race,"龟").start();
}
}
参考视频:https://www.bilibili.com/video/BV1V4411p7EF?spm_id_from=333.1007.top_right_bar_window_custom_collection.content.click&vd_source=a558872bc8c238e86d17d82aaad3f286