avatarSHAXUTANG

JDK1.5引入java.util.current针对并发问题的解决方案

售票案例

import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class Ticket { private int ticket = 30; //创建锁 Lock lock = new ReentrantLock(); public void sale() { //加锁 lock.lock(); try { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "卖第" + (ticket--) + "张票,还剩" + ticket + "张票"); } } finally { //释放锁 lock.unlock(); } } } public class SaleTicket { public static void main(String[] args) { Ticket ticket = new Ticket(); //开启三个线程 new Thread(() -> { for (int i = 0; i < 30; i++) { ticket.sale(); } }, "窗口一:").start(); new Thread(() -> { for (int i = 0; i < 30; i++) { ticket.sale(); } }, "窗口二:").start(); new Thread(() -> { for (int i = 0; i < 30; i++) { ticket.sale(); } }, "窗口三:").start(); } }

生产者和消费者

声明一个资源提供者,提供对资源操作的自增自减操作

class AirConditional { private int num = 0; public synchronized void increment() throws InterruptedException { //当num!=0时就释放锁,回到就绪状态,不做自增操作 if (num != 0) { this.wait(); } num++; System.out.println("increment:" + num); //自增之后唤醒其他线程 this.notifyAll(); } public synchronized void decrement() throws InterruptedException { //当num==0时就释放锁,回到就绪状态,不做自减操作 if (num == 0) { this.wait(); } num--; System.out.println("decrement:" + num); //自减之后唤醒其他线程 this.notifyAll(); } }

创建两个线程,一个自增一个自减

AirConditional airConditional = new AirConditional(); new Thread(() -> { //循环自增10次 for (int i = 0; i < 10; i++) { try { airConditional.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(() -> { //循环自减10次 for (int i = 0; i < 10; i++) { try { airConditional.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); /** 结果:自增自减交替进行 1 0 1 0 1 0 **/

虚假唤醒

AirConditional airConditional = new AirConditional(); for (int j = 0; j < 2; j++) { new Thread(() -> { for (int i = 0; i < 10; i++) { try { airConditional.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(() -> { for (int i = 0; i < 10; i++) { try { airConditional.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } /** 当多个线程操作自增自减,此时if判断只能进行一次,会造成虚假唤醒的结果 结果:自增自减顺序不一致 0 1 5 10 0 **/

解决方案

//修改资源提供类 class AirConditional { private int num = 0; public synchronized void increment() throws InterruptedException { //利用while循环,不断的判断,判断通过即可进行下一步,不会出现一次判断虚假唤醒 while (num != 0) { this.wait(); } num++; System.out.println("increment:" + num); this.notifyAll(); } public synchronized void decrement() throws InterruptedException { while (num == 0) { this.wait(); } num--; System.out.println("decrement:" + num); this.notifyAll(); } }

JUC版本的生产者和消费者

  • 线程等待和唤醒被Condition中的Api替换 await signalAll
  • 使用Lock来进行手动加锁
class AirConditional { private int num = 0; private final Lock lock = new ReentrantLock(); priva public void increment() throws InterruptedException { //加锁 lock.lock(); try { //while循环判断,防止虚假唤醒 while (num != 0) { //JUC 中的Condition 线程等待方法 condition.await(); } num++; System.out.println("increment:" + num); //JUC 中的Condition 线程唤醒方法 condition.signalAll(); } finally { lock.unlock(); } } public void decrement() throws InterruptedException { lock.lock(); try { while (num == 0) { condition.await(); } num--; System.out.println("decrement:" + num); condition.signalAll(); } finally { lock.unlock(); } } }

线程不安全的集合

a. ArrayList:速度快,但线程不安全,

​ 当多个线程同时操作一个ArrayList就会产生并发异常:java.util.ConcurrentModificationException

线程不安全案例

List<String> list = new ArrayList(); for (int i = 0; i < 30; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 8)); System.out.println(list); }).start(); }

创建线程安全的List

  • 使用Collections中的工具方法
  • 使用JUC提供的创建线程安全的List
//Collections创建的线程安全的List List<String> list = Collections.synchronizedList(new ArrayList<>()); //JUC提供的 List<String> list = new CopyOnWriteArrayList<>(); for (int i = 0; i < 30; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 8)); System.out.println(list); }).start(); }

b. Set:可以存入无序且不重复的值,但同样线程不安全,会产生java.util.ConcurrentModificationException

​ 底层由HashMap实现

HashSet底层实现

//声明一个全局静态常量 private static final Object PRESENT = new Object(); //构造方法创建一个HashMap public HashSet() { map = new HashMap<>(); } //利用HashMap的键不会重复的特性,将值作为键存入,value就使用全局静态常量 //因此HashSet值不会重复 public boolean add(E e) { return map.put(e, PRESENT)==null; }

创建线程安全的Set

//Collections API Set<Object> set = Collections.synchronizedSet(new HashSet<>()); // JUC API Set<String> set = new CopyOnWriteArraySet<>();

c. HashMap:以键值对的形式存入数据,但线程不安全

创建线程安全的HashMap

// Collections API Map<String, Object> map = Collections.synchronizedMap(new HashMap<>()); // JUC API Map<String, Object> map = new ConcurrentHashMap<>();

CountDownLatch

计数器,为了让多个线程执行完后,主线程再继续进行

//定义计数器开始数 CountDownLatch countDownLatch = new CountDownLatch(6); for (int i = 0; i < 6; i++) { new Thread(() -> { System.out.println(Thread.currentThread().getName() + "离开了"); //执行完一个线程,计数器减一个 countDownLatch.countDown(); }).start(); } //阻塞主线程 countDownLatch.await(); System.out.println("关门了");

CyclicBarrier

当完成的线程数达到指定数量则执行指定逻辑

//当执行完成的数量达到指定数量则进行 CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> System.out.println("完成")); for (int i = 0; i < 7; i++) { int finalI = i; new Thread(() -> { System.out.println("第" + finalI + "步"); try { //执行完后阻塞, cyclicBarrier.await(); } catch (InterruptedException | BrokenBarrierException e) { e.printStackTrace(); } }).start(); }

信号量Semaphore

限流,指定可以操作资源的数量,超出则不可操作,等腾出位置抢到了之后才可以继续操作

//定义三个位置操作资源,分别由线程来抢占位置 Semaphore semaphore = new Semaphore(3); for (int i = 0; i < 3; i++) { try { //抢占到了位置,资源位置数减一 semaphore.acquire(); System.out.println("抢到了资源"); } finally { //执行完毕后,资源位置数+1 semaphore.release(); System.out.println("释放了资源"); } }

ReadWriteLock

所谓读写锁,是对访问资源共享锁和排斥锁,一般的重入性语义为如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读锁(锁降级);如果一个线程对资源加了读锁,其他线程可以继续加读锁。

class MyCache { private final Map<String, String> map = new HashMap<>(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); public void write(String key) { lock.writeLock().lock(); try { map.put(key, ""); System.out.println("写入成功"); } finally { lock.writeLock().unlock(); } } public void read(String key) { lock.readLock().lock(); try { System.out.println(map.get(key)); System.out.println("读取成功"); } finally { lock.readLock().unlock(); } } } public class ReadWriteLockDemo { public static void main(String[] args) { MyCache myCache = new MyCache(); for (int i = 0; i < 5; i++) { int finalI = i; new Thread(() -> { myCache.write(String.valueOf(finalI)); }).start(); } for (int i = 0; i < 5; i++) { int finalI = i; new Thread(() -> { myCache.read(String.valueOf(finalI)); }).start(); } } }

BlockingQueue阻塞队列

BlockingQueue<String> bq = new ArrayBlockingQueue<>(3); //当超出指定长度,会报错 bq.add("a"); //当队列中没有元素会报错 bq.remove(); //会返回boolean值,成功为true,超出长度为false bq.offer("a"); //当元素无法添加,超过指定元素后停止阻塞 bq.offer(e, longtime, TimeUnit.SECONDS); //返回队列第一个值,没有则为null bq.poll(); //当元素无法移除,超过指定元素后停止阻塞 bq.poll(e, longtime, TimeUnit.SECONDS); //超出长度会阻塞 bq.put("e"); //超出长度会阻塞 bq.take();

线程池

线程池的优势

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
//创建固定数量的线程,总共就只有这几个线程处理 ExecutorService executorService = Executors.newFixedThreadPool(3); // 只创建一个线程,所有任务都这一个线程执行 ExecutorService executorService = Executors.newSingleThreadExecutor(); //一池多线程,有空闲则由空闲线程处理,无空闲则创建新的线程处理 ExecutorService executorService = Executors.newCachedThreadPool();

线程池常用的七个参数,重要的三个:

ThreadPoolExecutor 3 个最重要的参数)(先判断核心线程数-->然后判断队列,队列满了-->判断最大容量)

  • corePoolSize : 核心线程数线程数定义了最小可以同时运行的线程数量。(即使这些线程处于空闲状态,这些线程也不会被销毁)
  • maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运行的线程数量变为最大线程数
  • workQueue: 当新任务来的时候会先判断当前运行的线程数量是否达到核心线程数如果达到的话,新任务就会被存放在队列中

ThreadPoolExecutor其他常见参数:

  1. keepAliveTime:当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁; unit : keepAliveTime 参数的时间单位。
  2. threadFactory :executor 创建新线程的时候会用到。
  3. handler :饱和策略。关于饱和策略下面单独介绍一下。

线程池的饱和策略

  • AbortPolicy(默认):直接抛出RejectedExcutionException异常阻止系统正常运行
  • CallerRunsPolicy:调用者运行一种调节机制,该策略不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量
  • DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中,尝试再次提交当前任务
  • DiscardPolicy:该策略默默地抛弃无法处理的任务,不予处理也不抛出异常,如果允许任务丢失,这是最好的一种策略

现代社会,科技日新月异,各种神奇工具纷至沓来,给我们的生活带来了前所未有的便利和乐趣。本文将向您介绍五款别具匠心、耐用实用的工具,让您在工作与生活中事半功倍,轻松愉悦度过每一天!

Soft Organizer

Soft Organizer 是一款功能强大且易于使用的Windows应用程序卸载和软件管理工具。它的主要目标是帮助用户完全卸载计算机上的应用程序,同时清理相关的残留文件和注册表项,确保系统保持干净整洁。

Soft Organizer

完全卸载应用程序

与Windows自带的卸载程序不同,Soft Organizer 通过监视应用程序的安装过程来跟踪所有文件和注册表项的更改。因此,当您决定卸载某个程序时,它可以完全删除所有相关的文件和设置,避免留下任何无用的残留。

Soft Organizer

应用程序评级

Soft Organizer 还提供了用户评级功能,使您可以了解其他用户对某个应用程序的评价和意见。这有助于您在安装新应用程序时做出更明智的决策。

Soft Organizer

总体而言,Soft Organizer是一个强大而实用的工具,特别适用于那些需要经常安装、卸载和更新应用程序的用户。它能够帮助您轻松地管理和维护计算机上的软件,确保系统始终保持干净,运行顺畅。

UTools

uTools 是一个极简、插件化、跨平台的现代化桌面软件。通过自由选配丰富的插件,打造你得心应手的工具集合。

utools_search.webp

很多人可能会想问:有了这个之后,我能够用来干什么?

这个工具的功能十分强大,包含了图片、翻译、音乐、资源搜索、编程、书签等等多种功能,很多新功能你都可以自己去发现的。

下面我就简单介绍一下一些让我觉得亮眼的功能:

音乐盒子

集合了网易云、QQ音乐、咪咕音乐、酷狗音乐、酷我音乐等多家音乐平台,你安装这个插件之后打开插件在搜索框输入你想要找的歌曲就能同时在上面的平台里面检索,所以你不用担心找不到想听的歌曲。

utools_music_search.webp

资源搜索

这个功能简直逆天,要是你想要找点百度网盘的资源的话,你可以在这个插件里面选择一个网盘节点,然后在搜索框里面输入你想要搜索的资源的名称,点击搜索就可以了。

里面收录的节点有:东京图书馆、BT电影天堂、大圣盘、罗马盘、小白盘、52网盘、小可搜搜等等,虽然不算很多,但是搜索音乐或者电影或者软件之类的还是可以的。

utools_video_search.webp

总体而言,UTools是一款功能强大且高效的桌面工具,它的目标是让用户在Windows系统下更加便捷地进行工作和操作。这款工具集合带来了诸多便利功能,节省了用户的时间和精力,使日常任务更加简单快捷。请注意,由于我无法浏览最新信息,建议您查找UTools的最新官方网站或社区以获取更多详细信息和最新功能。

Snipaste

Snipaste是一款功能强大且轻量级的截图工具和贴图工具,它允许用户快速截取屏幕上的任意区域,并将截图粘贴到其他应用程序中,如编辑器、聊天工具、电子邮件等。Snipaste的独特之处在于它提供了丰富的贴图功能,允许用户在截图上进行标记、绘图、贴文字和贴图,以便更好地表达想法和意图。

在我们初学编程的时候,经常会模仿别人的代码,一边看一边抄,alt+tab来回切换,fuck!!!

自从用了 Snipaste 之后,简直不要太爽,我们可以直接将需要抄的代码截下来固定在屏幕上,再也不用来回切换。

snipaste_shortcut.webp

堆友是一个由 Alibaba Design 打造的 3D 资源设计平台,提供了海量的高品质的 3D 素材和。堆友致力于帮助设计师在任何时候都能轻松获取高质量的 3D 素材资源。

免费!免费!免费!重要的事情说三遍

Alt text

平台资源

堆友平台上,设计师可以找到海量的高品质3D素材和模板,包括场景、道具、角色、动画等,这些素材资源都经过了严格的筛选和审核,确保其高质量和可用性。同时,堆友还提供了一系列的设计工具和功能,如智能搜索、分类推荐、素材替换、快速预览等,帮助设计师更加高效地完成设计和创作。

Alt text

资源免费下载

堆友平台上的很多作品和素材可以免费下载和使用。用户可以通过搜索或者分类推荐找到自己需要的素材,并可以免费下载和使用。

Alt text

AI创作

堆友推出了 Beta 版的AI反应堆,可以进行AI创作。

Alt text

AI创作展示

这是我用堆友创作的作品🤣

Alt text Alt text Alt text

除此之外,堆友还为设计师提供了技能提升的机会,包括设计技巧、行业动态、案例分享等,让设计师可以在不断学习和交流中提升自己的设计水平和能力。

tailwind CSS 是一个基于原子类的 CSS 框架,它提供了一套可复用的 CSS 类,用于快速构建现代化的用户界面。与传统的 CSS 框架不同,tailwind CSS 不依赖于预定义的组件,而是通过组合多个原子类来构建界面。

为什么要使用 tailwind

tailwind CSS 的核心理念是提供一组小而简单的 CSS 类,每个类都代表一个特定的样式属性。通过将这些类组合在一起,开发者可以轻松地构建出各种样式。

例如,要创建一个按钮,可以使用 bg-blue-500 类设置背景颜色为蓝色,使用 text-white 类设置文字颜色为白色,使用 px-4 py-2 类设置内边距等。

<button class="bg-blue-500 px-4 py-2 text-white">button</button>

与其他 CSS 框架相比,tailwind CSS 具有以下优点:

  • 灵活性:tailwind CSS 提供了大量的原子类,开发者可以根据需要自由组合,而不受预定义组件的限制。
  • 可定制性:tailwind CSS 允许开发者通过配置文件自定义样式属性,以适应不同的项目需求。
  • 性能优化:tailwind CSS 通过使用 PurgeCSS 等工具,可以自动删除未使用的样式,从而减小 CSS 文件的大小。
  • 社区支持:tailwind CSS 拥有庞大的社区,提供了大量的插件和扩展,可以进一步增强框架的功能。

如何使用 tailwind

因为 tailwind CSS 本身并不依赖于任何特定的前端框架。你只需要在项目中安装 tailwind CSS,并在你的项目中使用相应的 CSS 类即可。

安装

npm install -D tailwindcss postcss autoprefixer

初始化 tailwind 配置文件

npx tailwindcss init -p

执行完成后将会在项目根目录生成 tailwind.config.js 配置文件

我们只需要在content属性中配置哪类文件需要使用 tailwind 即可

/** @type {import('tailwindcss').Config} */ export default { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {}, }, plugins: [], };

引入 tailwindcss

  1. 在自己项目中通过 @tailwind 指令引入
@tailwind base; @tailwind components; @tailwind utilities;
  1. 在项目入口文件引入 tailwindcss/tailwind.css
import "tailwindcss/tailwind.css";

这种方式等同于上述 1

在 Vue 项目中使用

vue 项目中只需要在 tailwind.config.js 中配置 .vue 文件

/** @type {import('tailwindcss').Config} */ export default { content: ["./index.html", "./src/**/*.{vue,js,ts,jsx,tsx}"], theme: { extend: {}, }, plugins: [], };

在 vue 文件中使用

<template> <h1 class="text-3xl font-bold underline">Hello world!</h1> </template>

在 React 项目中使用

同在 vue 项目中一样,在tailwind.config.js 中配置 .jsx/tsx 文件即可

/** @type {import('tailwindcss').Config} */ export default { content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], theme: { extend: {}, }, plugins: [], };

在 jsx 文件中使用

export default function App() { return <h1 className="text-3xl font-bold underline">Hello world!</h1>; }

在本站使用

我引入 tailwind 的原因,是因为当我使用 flex 实现垂直水平居中时,总是会把 align-items 写成 items-center, fuck!!

由于本站基于React实现,因此按照上述步骤即使用 tailwind

秉承着能省就省的原则,既然网站部署都已经托管了,那么评论区功能我也就使用 Giscus 好了 🤣。

Giscus 的使用也很简单,进入官网后按照步骤来进行配置就可以了,它主要是利用了 Github 的 discussions 来实现的。

  • 你的仓库必须是公开的,否则访客将无法查看 discussion。
  • giscus app 已安装,否则访客将无法评论和回应。
  • Discussions 功能已在你的仓库中启用。

集成 Giscus

如果你已经按照 Giscus 的步骤完成操作后即可把 Giscus 添加到网站中。

原生 js

如果你的网站是使用原生 javascript 实现的那么你需要在网站中新增标签

<script src="https://giscus.app/client.js" data-repo="[在此输入仓库]" data-repo-id="[在此输入仓库 ID]" data-category="[在此输入分类名]" data-category-id="[在此输入分类 ID]" data-mapping="pathname" data-strict="0" data-reactions-enabled="1" data-emit-metadata="0" data-input-position="bottom" data-theme="preferred_color_scheme" data-lang="zh-CN" crossorigin="anonymous" async ></script>

组件框架

但如果向我这样使用了组件化的框架那么就需要使用 giscus 组件库

比如,我的网站是用 Docusaurus 构建的,而 Docusaurus 使用的是 React,因此我们需要安装 Giscus React 组件

npm i @giscus/react

使用组件

<Giscus id="comments" repo="[在此输入仓库]" repoId="[在此输入仓库 ID]" category="[在此输入分类名]" categoryId="[在此输入分类 ID]" mapping="pathname" reactionsEnabled="1" emitMetadata="0" inputPosition="top" theme="light" lang="zh-CN" loading="lazy" />

因为是由 Github 的 discussions 来实现的,因此目前只允许有 Github 账号的小伙伴评论 😊, 在本页最下方即可发表评论。