Java多线程<三>常见的多线程设计模式

news2025/1/17 4:00:01

多线程的设计模式

两阶段线程终止

image-20220522205120212

  • park方法

image-20220522205104932

  • interrupted() 会让他失效。

  • 使用volatile关键字进行改写

image-20220522210245081

单例模式 双锁检测

保护性暂停

image-20220529184223468

  • 实现1:
package threadBase.model;

/**
 * @author: Zekun Fu
 * @date: 2022/5/29 19:01
 * @Description:
 * 保护性暂停,
 * Future 中get方法的实现原理
 */
public class GuardedObject {

    private Object response;

    // 获取结果
    public Object get() {
        synchronized (this) {
            // 等待结果
            while (response == null) {
                try {
                    this.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return response;    // 这里自然释放锁
    }

    public void complete(Object response) {
        synchronized (this) {
            this.response = response;
            this.notifyAll();
        }
    }
}

  • 实现2:设置超时
package threadBase.model;

import java.util.concurrent.ThreadLocalRandom;

/**
 * @author: Zekun Fu
 * @date: 2022/5/29 19:01
 * @Description:
 * 保护性暂停,
 * Future 中get方法的实现原理
 *
 *
 */
public class GuardedObject {

    private Object response;

    public Object get(long timeout) {
        synchronized (this) {
            long begin = System.currentTimeMillis();
            long passedTime = 0;
            while(response == null) {
                long waitTime = timeout - passedTime;   // 这里是为了防止虚假唤醒
                if (waitTime <= 0) {
                    break;
                }
                try {
                    this.wait(waitTime);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("被唤醒了");
                passedTime = System.currentTimeMillis() - begin;
            }
            return response;
        }
    }
    // 获取结果
    public Object get() {
        synchronized (this) {
            // 等待结果
            while (response == null) {
                try {
                    this.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return response;    // 这里自然释放锁
        }
    }

    public void complete(Object response) {
        synchronized (this) {
            this.response = response;
            this.notifyAll();
        }
    }

    public static void main(String[] args) {
        GuardedObject go = new GuardedObject();
        new Thread(()-> {
            // 等待两秒
            Object response = go.get(2000);
            System.out.println("结果是" + response);
        }).start();

        new Thread(()-> {
            try {
                // 3s才进行返回
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            go.complete(new Object());
        }).start();

        // 虚假唤醒
        new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            go.complete(null);

        }).start();
    }
}

  • Join的实现原理

image-20220529191720553

弱鸡版本的生产者消费者

  • 保护性暂停协议的扩展。
  • 解决线程之间的同步问题。
package threadBase.model;

import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ThreadLocalRandom;

/**
 * @author: Zekun Fu
 * @date: 2022/5/29 19:01
 * @Description:
 * 保护性暂停,
 * Future 中get方法的实现原理
 *
 * 写信者和收信者1:1实现模型。
 * 写信的人和收信的人是一一对应的关系。
 * 两个线程通过一个唯一的id确定彼此的身份。
 *
 * 1. 解耦了收信人线程和写信人线程
 * 2. 防止写信人线程用完对象不销毁。
 * 3. MailBoxes获取之后应该销毁对象。
 *
 *
 * 多个线程之间速度不匹配,所以使用消息队列。也就是生产者消费者模型。
 * 这里的停止等待也可以看作是一种一对一的解决速度不匹配问题的机制。
 *
 * 加上MailBox并没有改变这个的本质,只是方便编码了而已,就是把future
 * 放入了一个公共的消息队列,然后消费者进行取出。
 *
 * 可以看作是弱鸡版的生产者消费者模型。
 * 具有缓解速度不匹配问题的机制,但是必须要实现一对一的模型。
 *
 *
 */

class MailBoxes {           // 消息队列机制

    private static Map<Integer, GuardedObject> boxes = new Hashtable<>();

    private static int id = 1;
    // 产生唯一的id
    private static synchronized int generateId() {
        return id++;
    }

    public static GuardedObject createGuardedObject() {
        GuardedObject go = new GuardedObject(generateId());
        boxes.put(go.getId(), go);
        return go;
    }

    public static Set<Integer> getIds() {
        return boxes.keySet();
    }

    public static GuardedObject getGuardedObject(int id) {
        return boxes.remove(id);        // 使用remove方法,防止内存泄漏
    }
}

/*
*
*   消费者线程消费特定的future也就是GuardObject。
* */

class ReadPeople extends Thread{    // 生产者线程
    @Override
    public void run() {
        //  收信
        // 1. 创建
        GuardedObject guardedObject = MailBoxes.createGuardedObject();
        System.out.println(Thread.currentThread().getName() + "等待收信 id:" + guardedObject.getId());
        Object mail = guardedObject.get(5000);
        System.out.println(Thread.currentThread().getName() + "受到信:" + guardedObject.getId() + " 内容:" + mail);
    }
}

/*
*
* 生产者线程,生产特定的Future
**/
class WriteMan extends Thread{          // 消费者线程
    private int id;
    private String mail;
    WriteMan(int id, String mail) {
        this.id = id;
        this.mail = mail;
    }

    @Override
    public void run() {
        GuardedObject guardedObject = MailBoxes.getGuardedObject(id);
        System.out.println(Thread.currentThread().getName() + "写信 id = " + id +" 内容:" + mail);
        guardedObject.complete(mail);
    }
}

public class GuardedObject {        // future任务机制



    private int id;

    private Object response;

    public GuardedObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public Object get(long timeout) {
        synchronized (this) {
            long begin = System.currentTimeMillis();
            long passedTime = 0;
            while(response == null) {
                long waitTime = timeout - passedTime;   // 这里是为了防止虚假唤醒
                if (waitTime <= 0) {
                    break;
                }
                try {
//                    System.out.println("等待中..");  // 如果被虚假唤醒
                    this.wait(waitTime);
                } catch (Exception e) {
                    e.printStackTrace();
                }
//                System.out.println("被唤醒了"); // 如果被虚假唤醒
                passedTime = System.currentTimeMillis() - begin;
            }
            return response;
        }
    }
    // 获取结果
    public Object get() {
        synchronized (this) {
            // 等待结果
            while (response == null) {
                try {
                    this.wait();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return response;    // 这里自然释放锁
        }
    }

    public void complete(Object response) {
        synchronized (this) {
            this.response = response;
            this.notifyAll();
        }
    }

    public static void main(String[] args) throws InterruptedException {
//        GuardedObject go = new GuardedObject(1);
//        new Thread(()-> {
//            // 等待两秒
//            Object response = go.get(2000);
//            System.out.println("结果是" + response);
//        }).start();
//
//        new Thread(()-> {
//            try {
//                // 3s才进行返回
//                Thread.sleep(3000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            go.complete(new Object());
//        }).start();
//
//        // 虚假唤醒
//        new Thread(()->{
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            go.complete(null);
//
//        }).start();



        // 模拟写信和收信的过程
        // 1. 三个收信的人 (消费者)
        for (int i = 0; i < 3; i++) {
            ReadPeople p = new ReadPeople();
            p.start();
        }
        // 2. 三个写信人 (必须对应三个写信者)
        Thread.sleep(1000);
        for (int idx: MailBoxes.getIds()) {
            new WriteMan(idx, "内容" + idx).start();
        }
    }
}

生产者消费者模型(终止协议修改)

1. 手动枷锁实现

  • 首先枷锁中的wait应该使用while循环
  • 其次MsgQue应该抛出异常,而不是捕捉。这样终止线程的决定权就在线程那里,而不是必须等消费完最后一个才进行。
  • 而放入队列的异常可以放在MsgQue或者消费者线程中,因为不管怎么样,生产的都需要放入队列中。
package threadBase.model;

import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

/**
 * @author: Zekun Fu
 * @date: 2022/5/31 15:48
 * @Description: 生产者消费者,使用自己加锁实现
 */
public class MessageQue {

    LinkedList<Message>que = new LinkedList<>();
    int capcity;

    public MessageQue(int capcity) {
        this.capcity = capcity;
    }

    public void put(Message x) throws InterruptedException{
        synchronized (que) {
            while (que.size() >= capcity) {
                System.out.println("队列已满," + Thread.currentThread().getName() + "等待");
                que.wait();
            }
            que.addLast(x);         // 尾部添加
            que.notifyAll();
        }
    }

    public Message get() throws InterruptedException{
        synchronized (que) {
            while (que.size() == 0) {
                que.wait();
            }
            Message msg = que.removeFirst();        // 头部取出
            que.notifyAll();
            return msg;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MessageQue mq = new MessageQue(2);
        for (int i = 0; i < 3; i++) {
            int idx = i;
            new Thread(()->{
                System.out.println("生产者线程" + idx + "生产完成");
                try {
                    mq.put(new Message("消息" + idx));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"生产者线程" + i).start();
        }

        Thread t = new Thread(()-> {

            Thread cur = Thread.currentThread();
            while (true) {
                if (cur.isInterrupted()) {
                    break;
                }
                try {
                    Thread.sleep(1000);     // 每隔1s消费一次
                    Message msg = mq.get();
                    System.out.println("消费" + msg.getMsg());
                } catch (InterruptedException e) {
                    cur.interrupt();
                    System.out.println("停止消费");
                }
            }
        }, "消费者线程");
        t.start();
        Thread.sleep(4000);
        t.interrupt();
    }
}

class Message {
    private String msg;
    public Message(String mgs) {
        this.msg = mgs;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

2. 使用Semphore实现

package threadBase.model;

import java.util.LinkedList;
import java.util.concurrent.Semaphore;

/**
 * @author: Zekun Fu
 * @date: 2022/5/31 16:30
 * @Description: 使用Semaphore实现
 *
 *
 *
 */
public class MessageQue2 implements MesQ{

    private Semaphore mutex = new Semaphore(1);
    private Semaphore full;
    private Semaphore empty;
    LinkedList<Message>list = new LinkedList<>();

    public MessageQue2(int capcity) {
        full = new Semaphore(capcity);
        empty = new Semaphore(0);
    }

    @Override
    public void put(Message msg) throws InterruptedException {
        full.acquire();
        mutex.acquire();
        list.addLast(msg);
        mutex.release();
        empty.release();
    }

    @Override
    public Message get() throws InterruptedException {
        empty.acquire();
        mutex.acquire();
        Message ans = list.removeFirst();
        mutex.release();
        full.release();
        return ans;
    }

    public static void main(String[] args) {
        Test.testMesQ(new MessageQue2(2));
    }
}

哲学家进餐问题

  • 参考博客

哲学家进餐问题:

  1. 哲学家进餐,不应该有阻塞的线程,因为线程阻塞的条件是等待其他线程的完成通知。

1. 锁住房子破坏了请求保持

请求与保持,就是线程在运行期间,拥有一部分资源,但是仍旧需要更多的资源才能继续运行。

简单的办法,就是采用静态分配的方式,首先把所有的资源都分配好,程序就不会在进行请求了。

简单办法2,就是只有当能够同时拿到两双筷子的时候,才进行分配,如果没有同时拿到两双筷子,就直接放下。

缺点就是:如果程序运行时间很长,而某些资源虽然用的很快就用完了,但是也得等到程序运行完成之后才能进行释放。导致资源利用效率很低。同时也会导致某些进程的饥饿。

第二种的缺点很明显,拿起筷子和放下筷子都是需要消耗cpu和存储资源的,如果拿起放下时间很长,那么就会导致性能低下,资源利用效率低,同时有可能导致活锁问题。

Java中可以使用Mutex加上Synchornized进行资源的静态分配。也就是先设置一个房间。只允许其中的一部分人进去。

img

import java.util.concurrent.Semaphore;

public class MealOfPhilosopher {

	static Semaphore[] chopsticks = new Semaphore[5];
	static Semaphore mutex = new Semaphore(1);

	static {
		for (int i = 0; i < 5; i++) {
			chopsticks[i] = new Semaphore(1);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			int j = i;
			new Thread(()->{
				while (true) {
					try {
						mutex.acquire();
						chopsticks[j].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");
						chopsticks[(j + 1) % 5].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了他右边的" + (j + 1) % 5 + "号筷子");
						mutex.release();
						System.out.println(Thread.currentThread().getName() + "正在吃饭。");
						chopsticks[j].release();
						System.out.println(Thread.currentThread().getName() + "放下了他左边的" + j + "号筷子");
						chopsticks[(j + 1) % 5].release();
						System.out.println(Thread.currentThread().getName() + "放下了他右边的" + (j + 1) % 5 + "号筷子");
						System.out.println(Thread.currentThread().getName() + "正在思考。");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}, "哲学家" + i + "号").start();
		}
	}

}

2. 规定顺序破坏了循环等待

方法是,申请资源一定要按照某种顺序进行。比如设备的id进行从小到达的申请这种。

缺点是,资源的扩展性不好,如果新来了资源,上面的申请逻辑就需要改变。同时因为线程申请资源和使用资源的顺序可能不一致,从而导致请求到的资源无法投入使用的情况,从而导致资源的利用效率低

实现方法1:奇数的只能拿左手边的,偶数的只能拿右手边的

实现方法2:

img

3. 尝试上锁破坏了不可剥夺

img

缺点是,代价很大,可能线程已经运行了一半了,又得重新运行。

实现就是,使用tryAquire的方式获取锁。而不是aquire的方式。

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class MealOfPhilosopher {

	static final Semaphore[] chopsticks = new Semaphore[5];
	static final Semaphore mutex = new Semaphore(1);

	static {
		for (int i = 0; i < 5; i++) {
			chopsticks[i] = new Semaphore(1);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			int j = i;
			new Thread(()->{
				while (true) {
					try {
						System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");
						if (!chopsticks[j].tryAcquire(10, TimeUnit.SECONDS))
							System.out.println(Thread.currentThread().getName() + "等待了好长时间,他只好放下他左边的" + j + "号筷子");
						System.out.println(Thread.currentThread().getName() + "拿走了他右边的" + (j + 1) % 5 + "号筷子");
						if (!chopsticks[(j + 1) % 5].tryAcquire(10, TimeUnit.SECONDS))
							System.out.println(Thread.currentThread().getName() + "等待了好长时间,他只好放下他右边的" + (j + 1) % 5 + "号筷子");
						System.out.println(Thread.currentThread().getName() + "正在吃饭。");
						System.out.println(Thread.currentThread().getName() + "放下了他左边的" + j + "号筷子");
						chopsticks[j].release();
						System.out.println(Thread.currentThread().getName() + "放下了他右边的" + (j + 1) % 5 + "号筷子");
						chopsticks[(j + 1) % 5].release();
						System.out.println(Thread.currentThread().getName() + "正在思考。");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}, "哲学家" + i + "号").start();
		}
	}

}

  • 实现而使用Reentrantlock
package threadBase.model;

import leetcode.Philosophiers;

import java.util.TreeMap;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author: Zekun Fu
 * @date: 2022/6/1 14:59
 * @Description: 哲学家进餐问题,
 * 破坏不可剥夺 -- 使用tryLock
 * 破坏请求保持 -- 静态分配
 * 破坏循环等待 -- 规定顺序。可能会导致某一个线程饥饿。
 *      因为有的线程和两个人竞争,而有的线程在一个时刻只和一个人竞争
 *
 *      实现细节:
 *      1. 筷子可以继承锁变量
 *      2. 可以使用Semphore实现
 *      3. 可以使用ReentrantLock 实现的可以说是无锁的,因为线程一直处于就绪和执行装态。
 *
 *      4. 为什么不进入等待队列中呢?
 *          因为这不是一个同步问题,没有线程之间的协作,没有一个线程通知另外一个线程这种事情。
 *          也就是说,不会有人通知他醒过来。
 *          所以他需要不断的死循环去尝试,去抢筷子。
 *
 */

class Chopstic extends ReentrantLock {

}
public class Philosopher extends Thread {


    Chopstic left;
    Chopstic right;
    public Philosopher(Chopstic left, Chopstic right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        Thread t = Thread.currentThread();
        while(true) {

            if (t.isInterrupted()) {
                System.out.println(t.getName() + "嗝屁了");
                break;
            }

            if (left.tryLock()) {       // 如果拿到了左筷子
                try {
                    if (right.tryLock()) {  // 尝试拿右筷子, 没拿到
                        try {
                            eat();
                        } finally {
                            right.unlock();     // 如果拿到了,吃饭,放下锁
                        }
                    }
                }finally {
                    left.unlock();
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                t.interrupt(); // 重新设置打断标记
            }
        }
    }

    public void eat() {
        Thread t = Thread.currentThread();
        System.out.println(t.getName() + "正在吃饭...");
    }

    public static void main(String[] args) throws InterruptedException {

        Chopstic[] chopstics = new Chopstic[5];
        for (int i = 0; i < 5; i++) chopstics[i] = new Chopstic();
        String[] names = {
                "阿基米德",
                "柏拉图",
                "牛顿",
                "柯西",
                "亚里士多德"
        };

        Philosopher[] ps = new Philosopher[5];
        for (int i = 0; i < 5; i++) {
            Philosopher p = new Philosopher(chopstics[i], chopstics[(i + 1) % 5]);
            p.setName(names[i]);
            p.start();
            ps[i] = p;
        }

        Thread.sleep(10000);

        for (int i = 0; i < 5; i++) {
            ps[i].interrupt();
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1345375.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

后端程序员React初接触1

后端程序员React初接触 学习react基础与相关库的使用学习 包括react基础 路由 组件库等等 react是用于构建用户界面的JavaScript库 发送请求获取数据处理数据操作dom呈现页面&#xff08;react帮忙操作dom&#xff09; 数据渲染为视图 有facebook打造并开源 解决的问题 dom操…

MFC 文档类

目录 文档类概述 文档类的使用 程序框架过程 LoadFrame执行分析 框架窗口 WM_CREATE 消息处理 视图窗口 WM_CREATE 消息处理 对象关系图 窗口切分 命令消息处理顺序 文档类和视图类关系 文档类概述 相关类CDocument&#xff0c;作用&#xff1a;提供了一个用于管理数…

RuntimeError: The NVIDIA driver on your system is too old.

【报错】使用 AutoDL 复现实验时遇到 RuntimeError: The NVIDIA driver on your system is too old (found version 11070). Please update your GPU driver by downloading and installing a new version from the URL: http://www.nvidia.com/Download/index.aspx Alternativ…

go module本地包导入

go module本地包导入 本文目录 go module本地包导入启用go mod主项目工作目录本地module目录发布和使用模块 golang 1.11之后加入了go mod来替代GOPATH 官方文档参考&#xff1a;https://golang.google.cn/doc/tutorial/call-module-code 启用go mod 开启 Go modules # 临时开…

AI绘画工具Midjourney绘画提示词Prompt分享

一、Midjourney绘画工具 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭…

Go语言中的性能考虑和优化

优化您的Go代码以达到最佳性能 性能优化是软件开发的关键方面&#xff0c;无论您使用哪种编程语言。在这篇文章中&#xff0c;我们将探讨Go语言中的性能考虑和优化&#xff0c;Go是一种以其效率而著称的静态类型和编译语言。我们将深入探讨三个关键领域&#xff1a;分析并发代…

动态路由传参与查询参数传参详情

动态路由传参 路由规则path :/article/:aid 组件获取参数: this. $route. params.aid 如果想要所有的值&#xff0c;就用this. $route. params 注意&#xff1a;这两个必须匹配 如果是多个参数&#xff0c;path :/article/:aid/:name就写两个参数 接收方式一&#xff1a; 在…

Jupyter Notebook又一地理数据可视化扩展!

本次分享一个Jupyter Notebook地理数据可视化扩展&#xff1a;pyl7vp pyl7vpPythonl7vp&#xff0c;如其名&#xff0c;是l7vp在Python3方向的封装&#xff0c;l7vp是蚂蚁集团AntV数据可视化团队开发的地理空间智能应用研发开源平台。 通过pyl7vp可在Jupyter Notebook中轻松完…

day52 算法训练|动态规划part13

参考&#xff1a;代码随想录 300.最长递增子序列 1. dp[i]的定义 本题中&#xff0c;正确定义dp数组的含义十分重要。 dp[i]表示i之前包括i的以nums[i]结尾的最长递增子序列的长度 为什么一定表示 “以nums[i]结尾的最长递增子序” &#xff0c;因为我们在 做 递增比较的时…

Unity UnityWebRequest 在Mac上使用报CommectionError

今天是想把前两天写的Demo拿到Mac上打个IPA的完事我发现 在运行时释放游戏资源的时候UnityWebRequest返回的结果不是Success 查看Log发现是 req.result 是CommectionError error是 Cannot connect to destination host 代码如下&#xff1a; UnityWebRequest req UnityWebRequ…

磁盘管理-------RAID卡

目录 一、RAID概述 二、常见类型 &#xff08;一&#xff09;RAID 0 &#xff08;二&#xff09;RAID 1 &#xff08;三&#xff09;RAID 5 &#xff08;四&#xff09;RAID 6 &#xff08;五&#xff09;RAID 10 &#xff08;六&#xff09;总结 三、创建RAID &…

据报道,微软的下一代 Surface 笔记本电脑将是其首款真正的“人工智能 PC”

明年&#xff0c;微软计划推出 Surface Laptop 6和 Surface Pro 10&#xff0c;这两款设备将提供 Arm 和 Intel 两种处理器选项。不愿意透露姓名的不透露姓名人士透露&#xff0c;这些新设备将引入先进的人工智能功能&#xff0c;包括配备下一代神经处理单元 (NPU)。据悉&#…

(学习打卡1)重学Java设计模式之设计模式介绍

前言&#xff1a;听说有本很牛的关于Java设计模式的书——重学Java设计模式&#xff0c;然后买了(*^▽^*) 开始跟着小傅哥学Java设计模式吧&#xff0c;本文主要记录笔者的学习笔记和心得。 打卡&#xff01;打卡&#xff01; 设计模式介绍 一、设计模式是什么&#xff1f; …

macos 打开终端提示 You have new mail. 去除方法

这个提示信息是macos里面的mail消息提示, 如果需要查看详细的信息可以在终端输入 mail 命令即可查看所有信息, 这些信息都保存在 /private/var/mail/xxx 文件中 xxx 是你的macos的登录用户名, 要去除这些提示,只需要删除这个文件即可 # 删除mail信息存储文件 sudo rm -rf /…

【计算机毕业设计】python+django数码电子论坛系统设计与实现

本系统主要包括管理员和用户两个角色组成&#xff1b;主要包括&#xff1a;首页、个人中心、用户管理、分类管理、数码板块管理、数码评价管理、数码论坛管理、畅聊板块管理、系统管理等功能的管理系统。 后端&#xff1a;pythondjango 前端&#xff1a;vue.jselementui 框架&a…

Maven项目提示Ignored pom.xml问题

1 环境 &#xff08;1&#xff09;IDEA开发工具&#xff1a;2022.2.1 &#xff08;2&#xff09;JDK&#xff1a;Java17&#xff08;Spring6要求JDK最低版本是Java17&#xff09; &#xff08;3&#xff09;Spring&#xff1a;6.1.2 &#xff08;4&#xff09;Maven 3.8.8 2 …

uni-app API接口扩展组件(uni-ui)

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…

LV.13 D7 交叉编译工具链 学习笔记

一、交叉编译 1.1 编译原理 机器码&#xff08;二进制&#xff09;是处理器能直接识别的语言&#xff0c;不同的机器码代表不同的运算指令&#xff0c;处理器能够识别哪些机器码是由处理器的硬件设计所决定的&#xff0c;不同的处理器机器码不同&#xff0c;所以机器码不可移植…

如何正确使用docker搭建redis服务器,安装gcc和make以及出现错误时的解决办法

搭建redis服务器 目录 搭建redis服务器 &#xff08;1&#xff09;开启docker&#xff0c;并查看是否开启成功 &#xff08;2&#xff09;启动上面创建的ssrf容器&#xff0c;并进入ssrf容器 &#xff08;3&#xff09;进入opt&#xff0c;然后下载redis-5.0.5.tar.gz &a…

往期精彩推荐

所有的内容都在这个博客中&#xff0c;此博客为推广导航博客&#xff0c;过后会删掉https://blog.csdn.net/weixin_41620184/article/details/135042416 往期精彩&#xff1a;快来学习吧~~~ 机器学习算法应用场景与评价指标机器学习算法—分类机器学习算法—回归PySpark大数据处…