我告诉你一个 AtomicInteger 的大秘密

软件测试7年之痒的工程师,讲述当初是怎么突破的瓶颈?少走弯路 能力固然重要,但并不能代表全部 阅读详情

i++ 不是线程安全的操作,因为它不是一个原子性操作。

那么,如果我想要达到类似 i++ 的这种效果,我应该使用哪些集合或者说工具类呢?

在 JDK1.5 之前,为了确保在多线程下对某基本数据类型或者引用数据类型运算的原子性,必须依赖于外部关键字 synchronized,但是这种情况在 JDK1.5 之后发生了改观,当然你依然可以使用 synchronized 来保证原子性,我们这里所说的一种线程安全的方式是原子性的工具类,比如 AtomicInteger、AtomicBoolean 等。这些原子类都是线程安全的工具类,他们同时也是 Lock-Free 的。下面我们就来一起认识一下这些工具类以及 Lock - Free 是个什么概念。

了解 AtomicInteger

AtomicInteger 是 JDK 1.5 新添加的工具类,我们首先来看一下它的继承关系

与 int 的包装类 Integer 一样,都是继承于 Number 类的。

这个 Number 类是基本数据类型的包装类,一般和数据类型有关的对象都会继承于 Number 类。

它的继承体系很简单,下面我们来看一下它的基本属性和方法

AtomicInteger 的基本属性

AtomicInteger 的基本属性有三个

Unsafe 是 sun.misc 包下面的类,AtomicInteger 主要是依赖于 sun.misc.Unsafe 提供的一些 native 方法保证操作的原子性。

Unsafe 的 objectFieldOffset 方法可以获取成员属性在内存中的地址相对于对象内存地址的偏移量。说得简单点就是找到这个变量在内存中的地址,便于后续通过内存地址直接进行操作,这个值就是 value

这个我们后面会再细说

value 就是 AtomicIneger 的值。

AtomicInteger 的构造方法

继续往下看,AtomicInteger 的构造方法只有两个,一个是无参数的构造方法,无参数的构造方法默认的 value 初始值是 0 ,带参数的构造方法可以指定初始值。

AtomicInteger 中的方法

下面我们就来聊一下 AtomicInteger 中的方法。

Get 和 Set

我们首先来看一下最简单的 get 、set 方法:

get() : 获取当前 AtomicInteger 的值

set() : 设置当前 AtomicInteger 的值

get() 可以原子性的读取 AtomicInteger 中的数据,set() 可以原子性的设置当前的值,因为 get() 和 set() 最终都是作用于 value 变量,而 value 是由 volatile 修饰的,所以 get 、set 相当于都是对内存进行读取和设置。如下图所示

我们上面提到了 i++ 和 i++ 的非原子性操作,我们说可以使用 AtomicInteger 中的方法进行替换。

Incremental 操作

AtomicInteger 中的 Incremental 相关方法可以满足我们的需求

  • getAndIncrement() : 原子性的增加当前的值,并把结果返回。相当于 i++ 的操作。

为了验证是不是线程安全的,我们用下面的例子进行测试

public class TAtomicTest implements Runnable{

    AtomicInteger atomicInteger = new AtomicInteger();

    @Override
    public void run() {
        for(int i = 0;i < 10000;i++){
            System.out.println(atomicInteger.getAndIncrement());
        }
    }
    public static void main(String[] args) {

        TAtomicTest tAtomicTest = new TAtomicTest();

        Thread t1 = new Thread(tAtomicTest);
        Thread t2 = new Thread(tAtomicTest);
        t1.start();
        t2.start();
    }

}

通过输出结果你会发现它是一个线程安全的操作,你可以修改 i 的值,但是最后的结果仍然是 i - 1,因为先取值,然后再 + 1,它的示意图如下。

  • incrementAndGet 与此相反,首先执行 + 1 操作,然后返回自增后的结果,该操作方法能够确保对 value 的原子性操作。如下图所示

Decremental 操作

与此相对,x-- 或者 x = x - 1 这样的自减操作也是原子性的。我们仍然可以使用 AtomicInteger 中的方法来替换

  • getAndDecrement : 返回当前类型的 int 值,然后对 value 的值进行自减运算。下面是测试代码
class TAtomicTestDecrement implements Runnable{

    AtomicInteger atomicInteger = new AtomicInteger(20000);

    @Override
    public void run() {
        for(int i = 0;i < 10000 ;i++){
            System.out.println(atomicInteger.getAndDecrement());
        }
    }

    public static void main(String[] args) {

        TAtomicTestDecrement tAtomicTest = new TAtomicTestDecrement();

        Thread t1 = new Thread(tAtomicTest);
        Thread t2 = new Thread(tAtomicTest);
        t1.start();
        t2.start();

    }

}

下面是 getAndDecrement 的示意图

  • decrementAndGet:同样的,decrementAndGet 方法就是先执行递减操作,然后再获取 value 的值,示意图如下

LazySet 方法

volatile 有内存屏障你知道吗?

内存屏障是啥啊?

内存屏障,也称内存栅栏,内存栅障,屏障指令等, 是一类同步屏障指令,是 CPU 或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。也是一个让CPU 处理单元中的内存状态对其它处理单元可见的一项技术。

CPU 使用了很多优化,使用缓存、指令重排等,其最终的目的都是为了性能,也就是说,当一个程序执行时,只要最终的结果是一样的,指令是否被重排并不重要。所以指令的执行时序并不是顺序执行的,而是乱序执行的,这就会带来很多问题,这也促使着内存屏障的出现。

语义上,内存屏障之前的所有写操作都要写入内存;内存屏障之后的读操作都可以获得同步屏障之前的写操作的结果。因此,对于敏感的程序块,写操作之后、读操作之前可以插入内存屏障。

内存屏障的开销非常轻量级,但是再小也是有开销的,LazySet 的作用正是如此,它会以普通变量的形式来读写变量。

也可以说是:懒得设置屏障了

GetAndSet 方法

以原子方式设置为给定值并返回旧值。

它的源码就是调用了一下 unsafe 中的 getAndSetInt 方法,如下所示

就是先进行循环,然后调用 getIntVolatile 方法,这个方法我在 cpp 中没有找到,找到的小伙伴们记得及时告诉让我学习一下。

循环直到 compareAndSwapInt 返回 false,这就说明使用 CAS 并没有更新为新的值,所以 var5 返回的就是最新的内存值。

CAS 方法

我们一直常说的 CAS 其实就是 CompareAndSet 方法,这个方法顾名思义,就是 比较并更新 的意思,当然这是字面理解,字面理解有点偏差,其实人家的意思是先比较,如果满足那么再进行更新。

上面给出了 CAS Java 层面的源码,JDK 官方给它的解释就是 如果当前值等于 expect 的值,那么就以原子性的方式将当前值设置为 update 给定值,这个方法会返回一个 boolean 类型,如果是 true 就表示比较并更新成功,否则表示失败。

CAS 同时也是一种无锁并发机制,也称为 Lock Free,所以你觉得 Lock Free 很高大上吗?并没有。

下面我们构建一个加锁解锁的 CASLock

class CASLock {

    AtomicInteger atomicInteger = new AtomicInteger();
    Thread currentThread = null;

    public void tryLock() throws Exception{

        boolean isLock = atomicInteger.compareAndSet(0, 1);
        if(!isLock){
            throw new Exception("加锁失败");
        }

        currentThread = Thread.currentThread();
        System.out.println(currentThread + " tryLock");

    }

    public void unlock() {

        int lockValue = atomicInteger.get();
        if(lockValue == 0){
            return;
        }
        if(currentThread == Thread.currentThread()){
            atomicInteger.compareAndSet(1,0);
            System.out.println(currentThread + " unlock");
        }
    }

    public static void main(String[] args) {

        CASLock casLock = new CASLock();

        for(int i = 0;i < 5;i++){

            new Thread(() -> {
                try {
                    casLock.tryLock();
                    Thread.sleep(10000);
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    casLock.unlock();
                }
            }).start();
        }

    }
}

在上面的代码中,我们构建了一个 CASLock,在 tryLock 方法中,我们先使用 CAS 方法进行更新,如果更新不成功则抛出异常,并把当前线程设置为加锁线程。在 unLock 方法中,我们先判断当前值是否为 0 ,如果是 0 就是我们愿意看到的结果,直接返回。否则是 1,则表示当前线程还在加锁,我们再来判断一下当前线程是否是加锁线程,如果是则执行解锁操作。

那么我们上面提到的 compareAndSet,它其实可以解析为如下操作

// 伪代码

// 当前值
int v = 0;
int a = 0;
int b = 1;

if(compare(0,0) == true){
  set(0,1);
}
else{
  // 继续向下执行
}

也可以拿生活场景中的买票举例子,你去景区旅游肯定要持票才能进,如果你拿着是假票或者不符合景区的票肯定是能够被识别出来的,如果你没有拿票拿你也肯定进不去景区。

废话少说,这就祭出来 compareAndSet 的示意图

  • weakCompareAndSet: 妈的非常认真看了好几遍,发现 JDK1.8 的这个方法和 compareAndSet 方法完全一摸一样啊,坑我。。。

但是真的是这样么?并不是,JDK 源码很博大精深,才不会设计一个重复的方法,你想想 JDK 团队也不是会犯这种低级团队,但是原因是什么呢?

《Java 高并发详解》这本书给出了我们一个答案

AddAndGet

AddAndGet 和 getAndIncrement、getAndAdd、incrementAndGet 等等方法都是使用了 do … while + CAS 操作,其实也就相当于是一个自旋锁,如果 CAS 修改成功就会一直循环,修改失败才会返回。示意图如下

深入 AtomicInteger

我们上面探讨了 AtomicInteger 的具体使用,同时我们知道 AtomicInteger 是依靠 volatile 和 CAS 来保证原子性的,那么我们下面就来分析一下为什么 CAS 能够保证原子性,它的底层是什么?AtomicInteger 与乐观锁又有什么关系呢?

AtomicInteger 的底层实现原理

我们再来瞧瞧这个可爱的 compareAndSetL(CAS) 方法,为什么就这两行代码就保证原子性了?

我们可以看到,这个 CAS 方法相当于是调用了 unsafe 中的 compareAndSwapInt 方法,我们进到 unsafe 方能发中看一下具体实现。

compareAndSwapInt 是 sun.misc 中的方法,这个方法是一个 native 方法,它的底层是 C/C++ 实现的,所以我们需要看 C/C++ 的源码。

知道 C/C++ 的牛逼之处了么。使用 Java 就是玩应用和架构的,C/C++ 是玩服务器、底层的。

compareAndSwapInt 的源码在 jdk8u-dev/hotspot/src/share/vm/prims/unsafe.app 路径下,它的源码实现是

也就是 Unsafe_CompareAndSwapInt 方法,我们找到这个方法

C/C++ 源码我也看不懂,但是这不妨碍我们找到关键代码 Atomic::cmpxchg ,cmpxchg 是 x86 CPU 架构的汇编指令,它的主要作用就是比较并交换操作数。我们继续往下跟找一下这个指令的定义。

我们会发现对应不同的 os,其底层实现方式不一样

我们找到 Windows 的实现方式如下

我们继续向下找,它其实定义的是第 216 行的代码,我们找进去

此时就需要汇编指令和寄存器相关的知识了。

上面的 os::is-MP() 是多处理操作系统的接口,下面是 __asm ,它是 C/C++ 的关键字,用于调用内联汇编程序。

__asm 中的代码是汇编程序,大致来说就是把 dest、exchange_value 、compare_value 的值都放在寄存器中,下面的 LOCK_IF_MP 中代码的大致意思就是

如果是多处理器的话就会执行 lock,然后进行比较操作。其中的 cmp 表示比较,mp 表示的就是 MultiProcess,je 表示相等跳转,L0 表示的是标识位。

我们回到上面的汇编指令,我们可以看到,CAS 的底层就是 cmpxchg 指令。

乐观锁

你有没有这个疑问,为什么 AtomicInteger 可以获取当前值,那为什么还会出现 expectValue 和 value 不一致的情况呢?

因为 AtomicInteger 只是一个原子性的工具类,它不具有排他性,它不像是 synchronized 或者是 lock 一样具有互斥和排他性,还记得 AtomicInteger 中有两个方法 get 和 set 吗?它们只是用 volatile 修饰了一下,而 volatile 不具有原子性,所以可能会存在 expectValue 和 value 的当前值不一致的情况,因此可能会出现重复修改。

针对上面这种情况的解决办法有两种,一种是使用 synchronized 和 lock 等类似的加锁机制,这种锁具有独占性,也就是说同一时刻只能有一个线程来进行修改,这种方式能够保证原子性,但是相对开销比较大,这种锁也叫做悲观锁。另外一种解决办法是使用版本号或者是 CAS 方法。

版本号

版本号机制是在数据表中加上一个 version 字段来实现的,表示数据被修改的次数,当执行写操作并且写入成功后,version = version + 1,当线程 A 要更新数据时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。

CAS 方法

还有一种方式就是 CAS 了,我们上面用了大量的篇幅来介绍 CAS 方法,那么我们认为你现在已经对其运行机制有一定的了解了,我们就不再阐述它的运行机制了。

任何事情都是有利也有弊,软件行业没有完美的解决方案只有最优的解决方案,所以乐观锁也有它的弱点和缺陷,那就是 ABA 问题。

ABA 问题

ABA 问题说的是,如果一个变量第一次读取的值是 A,准备好需要对 A 进行写操作的时候,发现值还是 A,那么这种情况下,能认为 A 的值没有被改变过吗?可以是由 A -> B -> A 的这种情况,但是 AtomicInteger 却不会这么认为,它只相信它看到的,它看到的是什么就是什么。举个例子来说

假如现在有一个单链表,如下图所示

A.next = B ,B.next = null,此时有两个线程 T1 和 T2 分别从单链表中取出 A ,由于一些特殊原因,T2 把 A 改为 B ,然后又改为 A ,此时 T1 执行 CAS 方法,发现单链表仍然是 A ,就会执行 CAS 方法,虽然结果没错,但是这种操作会造成一些潜在的问题。

此时还是一个单链表,两个线程 T1 和 T2 分别从单链表中取出 A ,然后 T1 把链表改为 ACD 如下图所示

此时 T2,发现内存值还是 A ,就会把 A 的值尝试替换为 B ,因为 B 的引用是 null,此时就会造成 C、D 处于游离态

JDK 1.5 以后的 AtomicStampedReference类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前值是否等于预期值,判断的标准就是当前引用和邮戳分别和预期引用和邮戳相等,如果全部相等,则以原子方式设置为给定的更新值。

好了,上面就是 Java 代码流程了,看到 native 我们知道又要撸 cpp 了。开撸

简单解释一下就是 UnsafeWrapper 就是包装器,换个名字而已。然后经过一些 JNI 的处理,因为 compareAndSwapOject 比较的是引用,所以需要经过 C++ 面向对象的转换。最主要的方法是 atomic_compare_exchange_oop

可以看到,又出现了熟悉的词汇 cmpxchg ,也就是说 compareAndSwapOject 使用的还是 cmpxchg 原子性指令,只是它经过了一系列转换。

MSDN 我告诉你 整站镜像完整包 MSDN我告诉你 挂掉了 ,我这是整站备份包,使用说明压缩包里有,可以下载所有的以前站点的光盘镜像。 立即下载

相关推荐

STM32F103循迹小车PID调参实战:手把手教你整定舵机PD与电机PI参数

本文详细介绍了基于STM32F103的循迹小车PID调参实战,重点讲解舵机PD控制与电机PI参数的整定技巧。通过硬件选型、基础测试、四步调参法和分段控制策略,帮助开发者解决小车蛇形走位等问题,提升循迹性能。文章还分享了高级调试技巧和典型问题排查方法,适用于智能车竞赛和机器人开发。

weixin_42524226的博客 362

告诉你

我想我应该你解释一下,

stableboy的Blog 2039

模拟量传输4-20ma电流

大家可能会非常熟悉RS232,RS485,CAN等工业上常用的总线,他们都是传输数字信号的方式。那么,我们用什么方式来传输模拟信号呢?工业上普遍需要测量各类非电物理量,例如温度、压力、速度、角度等,这些都需要转换成模拟量电信号才能传输到几百米外的控制室或显示设备上。工业上最广泛采用的是用4~20mA电流来传输模拟量。 采用电流信号的原因是不容易受干扰,因为工业现场的噪声电压的幅度可能达到数V,但是噪声的功率很弱,所以噪声电流通常小于nA级别,因此给4-20mA传输带来的误差非常小;电流源内阻趋于无穷.

tangdongming的博客 1万+

关于优快云

优快云:你们天天用csdn,可你们知道csdn是什么吗?让我告诉你。 点击下方链接:记得回来点赞!!! https://baike.baidu.com/item/csdn/172150#2_1 ...

qq_41944612的博客 489

我看MFC的过去、现在和未来

首先说句题外话,虽然我现在的“主业”是Qt,但自己扪心自问,对MFC的熟悉程度要超过Qt。正是由于有了对MFC的熟悉,才能在使用Qt时,比较清醒的认清两者的优劣之处,在此暂且不提。 这个话题也可以说是由来已久,众说纷纭,今时我也谈谈自己的感悟。 一、对MFC的总体看法 首先看看云风在其《游戏之旅-我的编程感悟》一书中对MFC的看法: “我所见到的程序员中,很多把C+

qter_wd007的专栏 6117

qPCRtools | 神仙R包分分钟搞定你的qPCR实验结果!~

1写在前面 不知道大家都是怎么完成qPCR的计算的,在不会R的时候,我是用一个祖传的Excel表进行计算的。🤣 但是,一直有个缺点,如果需要计算的量比较大时,就不方便了,去搜了一下文献,发现了一个最近发表的R包,不仅可以计算反转录的RNA体积,还可以帮助选择定量方法,简直是神仙R包,本期就介绍一下它的使用吧。🥰 感谢原作者的开发,嘿嘿,文末有引用方法。👀 2用到的包 rm(list = ls())library(tidyverse)library(ggsci)library(qPCRtools)librar

m0_72224305的博客 3987

初见优快云,我想说......

一些展望,肯定不是flag,肯定不是!

The_upset_grass的博客 870

AVDepthData、LiDAR 深度数据与人像模式全解析:iOS 真实景深图采集与处理实战

在 iOS 高端影像系统中,真实景深信息的采集与利用已成为区分旗舰设备与基础设备的重要能力。Apple 自 iPhone 7 Plus 引入双摄虚化以来,逐步发展出基于 AVDepthData、TrueDepth 与 LiDAR 的多维深度感知技术,并开放接口供开发者获取高精度深度图,构建背景虚化、人像分割、3D 重建等多种功能。本文将深入解析 AVDepthData 的结构与使用方式,系统讲解 LiDAR 采集架构、人像模式的景深融合逻辑,以及如何在实际项目中采集、预览并处理深度图数据,实现具备工程可用性

努力分享一些人工智能、计算机视觉、影像等相关的知识干货! 1864

那些年优快云告诉我的话

调试的错误就是编程给你最好的东西,因为在每个错误上面都标志着前进的一步。

FFFLLLLLL的博客 1458

USB 基础学习:USB 电气信号介绍

USB电气信号技术解析 USB通信的核心是差分信号传输技术,通过D+和D-两根线传输相位相反的电压信号实现数据通信。USB信号系统包含三类关键信号:差分数据信号(抗干扰性强)、单端状态信号(用于设备识别)和电源信号(提供5V供电)。USB2.0采用400mV电压摆幅和NRZI编码,支持低速(1.5Mbps)至高速(480Mbps)传输;USB3.0则新增专用差分对,数据速率可达20Gbps。物理层通过上拉/下拉电阻检测设备连接,高速模式需特殊握手协议。抗干扰设计的关键在于保持差分线等长、阻抗匹配和使用共模扼

浩瀚之水的专栏 1785

YOLOv8/v9 遥感图像目标检测实战指南:建筑物检测与变化监测

遥感图像在城市规划、灾害评估、农业监管等领域应用广泛,但传统目标检测方法存在效率低、精度不足、难以适配遥感图像大尺寸、多尺度目标等问题。本文以“建筑物检测与变化监测”为核心,基于DOTA、NWPU VHR-10等公开数据集,系统讲解YOLOv8/v9在遥感图像中的实战流程。从数据获取、格式转换、模型训练优化,到大图切片推理、建筑物变化监测,全程提供可复现的代码与参数配置。通过针对性优化(旋转增强、切片推理等),模型在NWPU VHR-10数据集上mAP50达0.85以上,可适配卫星影像、无人机航拍等多场景

专注于人工智能、软件开发、工控自动化、工厂数字化及智能化等领域,希望和大家共同进步! 520

告诉你一个 AtomicInteger 的惊天大秘密!

作者 |cxuan来源 |Java建设者(ID:javajianshe)头图 | 优快云 下载自视觉中国i++ 不是线程安全的操作,因为它不是一个原子性操作。那么,如果我想要达到类...

优快云资讯 4053

手把手教程 | 使用 apktool 修改已发布 APK 的版本号与版本名称

摘要:本文介绍如何使用apktool工具直接修改APK版本信息,适用于版本回滚、多版本测试等场景。详细步骤包括反编译APK、修改版本号、重打包、对齐优化和重新签名,并分享了反编译相关实用知识。该方法无需源代码参与,但需注意修改后的APK必须重新签名才能安装。最后强调应在合法范围内使用反编译技术。

学无止境 829

真活久见,原来AtomicInteger还有这样的大秘密

i++ 不是线程安全的操作,因为它不是一个原子性操作。 那么,如果我想要达到类似 i++ 的这种效果,我应该使用哪些集合或者说工具类呢? 在 JDK1.5 之前,为了确保在多线程下对某基本数据类型或者引用数据类型运算的原子性,必须依赖于外部关键字 synchronized,但是这种情况在 JDK1.5 之后发生了改观,当然你依然可以使用 synchronized 来保证原子性,我们这里所说的一种线程安全的方式是原子性的工具类,比如AtomicInteger、AtomicBoolean等。这些原子类都..

weixin_51954021的博客 341

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

bat如何调用外部程序_我来告诉你,一个草根程序员如何进入BAT。

看到这个标题,有的同学可能会说,“LZ,你不是要告诉我们如何进入BAT吗?怎么一上来就写心态?”  没错,我第一个要说的,就是心态!  原因很简单,文章下面即将提到的学习内容,如果你没有一个好的心态,是不可能进行下去的。所以,先过了心态这一关,再来谈别的吧,否则你肯定会死在半路上的。  说起来,很多群里的新人都爱问我,“你是怎么学习的?为什么我学不进去呢?为什么我一看书就困呢?”  以上这种现象,...

weixin_33914777的博客 415

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

优快云 I Tell You

AI。

yunyanglive的博客 1050

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

没有人教你的重要的 Linux 命令,我告诉你

本文将记录并呈现我在工作中每天使用的命令速查表。这个策划清单超越了初学者的水平,而是关注于那些能帮助你进一步推进、更高效地管理 Linux 系统及其关键工具的命令。无论你是系统管理员、开发人员、DevOps、安全专家还是运维人员……有效地使用 Linux 及其工具是你可以学习的最基本技能。Linux 是全球大部分服务器和应用程序的支柱。在以前的大多数文章中,大家肯定读了许多类似于“你必须了解的 9 个 Linux 命令”或“Linux 命令入门指南”的文章。

xxxzzzqqq_的博客 391

我可以告诉你一个我20岁就知道的秘密

这么定有什么好处呢?而我爸,遇到哪怕天塌下来首先都会先思考乐观的那一面,还记得20多年前我爸一个肾脏萎缩,医生告知他之际他居然开心的说:太幸运了只有一个肾萎缩。我们的党也是目标管理高手,在井岗山、在长征路上、在延安哪怕没饭吃没水喝哪怕再困难,伟大的目标永远牢记于心,最终取得了伟大的胜利。要是你的目标就是考华科,那很容易放弃,毕竟你也不知道考上后能干啥有多大改变,但我不能放弃呀,放弃考华科就等于放弃改变世界呀。有的人说是工具和方法,比如工作管理软件,有的人说是少休息多干活,时间就是海绵里的水多挤一挤。

时清云的博客 289

【感悟总结】关于创作,我想告诉你

关于创作,我想告诉你

石墨博客 426

我告诉你msdn

MSDN,我告诉你,所有的软件都是纯净的,没有任何捆绑下载,与从官方渠道下载的一模一样,甚至更为纯净。今天又知道了一个新的东西,专门提供微软及其他操作系统的纯净镜像下载。此外,新版站点还提供了一些著名的 Linux 系统,如 UOS、Ubuntu、CentOS 等。Windows Server 的多个版本,如 2012、2016、2019 等。各种版本的 Windows 系统,如 Windows 10、8、7、XP 等。Office 套件的多个版本,如 2003、2007、2013、2019 等。

2301_76563517的博客 4711

优快云新手指南

链接区链接对应文中编号链接链接1《优快云社区创作内容规范》链接2《优快云社区提问规范》链接3GitHub网址链接4《博客帮助文档》链接5《2023博客之星入围规则》链接6优快云网址链接7优快云 app下载链接。

2301_78588271的博客 1万+

博客教程

博客教程

2202_75454356的博客 1万+

程序员35岁会失业吗? - 我告诉你:其实并不会

作为大龄程序员,当面对职场挑战时,我们需要调整工作心态。首先要保持积极主动,勇敢接受新的机会和挑战。不断学习新知识和技能也很重要,跟上科技行业的发展,保持竞争力。同时,要灵活适应工作环境的变化,愿意尝试新的工作方式和方法。建立自信心,相信自己的能力和经验能够为公司带来价值。与年轻同事交流,互相学习,共同进步。最重要的是保持工作生活平衡,关注身心健康,这样才能在职场中取得成功。

2302_78387246的博客 1065
上一篇: 今天我才知道,线程池也是可以有返回值的
下一篇: SpringBoot 实现自动登录时的安全风险控制
JavaShark
博客等级 码龄4年 184粉丝 438原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值