整理自知乎:
https://www.zhihu.com/question/26595480/answer/33533759
https://www.zhihu.com/question/23244293/answer/24032098
线程安全有不止一种定义,而且互不兼容。
比较认可的是在《Java concurrency inpractice》一书中的定义:
一个不论运行时(
Runtime
)如何调度线程都不需要调用方提供额外的同步和协调机制还能正确地运行的类是线程安全的。
一个线程安全的
对象
应当满足以下三个特点:
•
无论操作系统如何调度这些线程,
无论这些线程的执行顺序如何交织(
interleaving
)。
•
调用端代码无须额外的同步或其他协调动作。
•
多个线程同时访问时,其表现出正确的行为。
依据这个定义,C++ 标准库里的大多数 class 都不是线程安全的,包括 std::string、std::vector、std::map、std::shared_ptr 等等。
而 C 系统库大多数函数是线程安全的,包括malloc/free/printf/gettimeofday 等等。
这个定义太过于严格,也可定义为有条件的线程安全类,对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。
线程不安全就是如果不提供额外的同步和协调机制,进行数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
多线程的场景很多很复杂,难以穷尽地说那些条件下是或者不是线程安全的,但是有一些常用的肯定线程安全的场景:
1.
无状态的一定是线程安全的。
这个很好理解,因为所谓线程不安全也就是一个线程修改了状态,而另一个线程的操作依赖于这个被修改的状态。
2.
只有一个状态,而且这个状态是由一个线程安全的对象维护的,那这个类也是线程安全的。(可以理解为操作具有原子性)
比如你在数据结构里只用一个AtomicLong来作为计数器,那递增计数的操作都是线程安全的,不会漏掉任何一次计数,而如果你用普通的long做++操作则不一样,因为++操作本身涉及到
取数、递增、赋值
三个操作,某个线程可能取到了另外一个线程还没来得及写回的数就会导致上一次写入丢失。
3.
有多个状态的情况下,维持不变性(
invariant
)的所有可变(
mutable
)状态都用同一个锁来守护的类是线程安全的。
这一段有些拗口,首先类不变性的意思是指这个类在多线程状态下能正确运行的状态,
其次用锁守护的意思是所有对该状态的操作都需要获取这个锁,而用同一个锁守护的作用就是所有对这些状态的修改实际最后都是串行的,不会存在某个操作中间状态被其他操作可见,继而导致线程不安全。
所以这里的关键在于如何确定不变性,可能你的类的某些状态对于类的正确运行是无关紧要的,那就不需要用和其他状态一样的锁来守护。因此我们常可以看到有的类里面会创建一个新的对象作为锁来守护某些和原类本身不变性无关的状态。
上面这三种只是一种归纳,具体到实际应用时,要看你的类哪些状态是必须用锁来守护的,灵活变通。
我对上述3点总结如下:
1、无状态,即访问对象不会更改。
2、对访问对象的操作具有原子性。
3、对对象资源加锁保护,所有对该对象资源的操作都需要获取这个锁,作用就是所有对这些资源的修改实际最后都是串行的,不会存在某个操作中间状态被其他操作可见,继而导致线程不安全。
整理自知乎:https://www.zhihu.com/question/26595480/answer/33533759https://www.zhihu.com/question/23244293/answer/24032098 线程安全有不止一种定义,而且互不兼容。 比较认可的是在《Java concurrency inpractice》一书中的定义:
原型: time_t time(time_t *calptr)
得到自1970-1-1, 00:00:00以来经过的秒数,结果可以通过返回值,也可以通过参数得到,见实例
头文件 <time.h>
成功:秒数
失败:-1
time_t now;
最近在项目中使用到了gettimeofday这个函数来计算系统当前毫秒数,发现这里面有个问题需要特别注意一下。
1、首先来看一下函数原型:int gettimeofday(struct timeval *tv, struct timezone *tz)
其中结构体struct timeval定义如下:struct timeval {
time_t tv_sec; /* s
1、常用的时间存储方式
1)time_t类型,这本质上是一个长整数,表示从1970-01-01 00:00:00到目前计时时间的秒数,如果需要更精确一点的,可以使用timeval精确到毫秒。
2)tm结构,这本质上是
在perl中可以通过use函数引入你需要使用的函数名称,以下是此次会用到的函数。
#引用时间函数,包括sleep函数,gettimeofday 函数,tv_interval时间差函数
use Time::HiRes qw(sleep gettimeofday tv_interval);
ArrayList 是一个非
线程安全
的集合类,即当多个线程同时访问同一个 ArrayList 对象时,可能会导致数据不一致或抛出异常。这是因为 ArrayList 内部并没有做线程同步的处理。
如果需要在多线程环境下使用 ArrayList,可以通过以下两种方式实现
线程安全
:
1. 使用 Collections 类的 synchronizedList 方法将 ArrayList 转换为
线程安全
的集合:
```java
List<Object> synchronizedList = Collections.synchronizedList(new ArrayList<>());
该方法返回一个
线程安全
的 List 对象,通过对其进行操作可以保证
线程安全
。但是需要注意的是,虽然这个集合对象是
线程安全
的,但是在遍历等复合操作时仍然需要额外的同步措施。
2. 使用并发集合类(Concurrent Collections)代替 ArrayList,例如 CopyOnWriteArrayList:
```java
List<Object> concurrentList = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList 是一个
线程安全
的并发列表,它通过在修改操作时创建底层数组的副本来实现
线程安全
。这样可以避免对原始数组进行修改,从而避免了多线程并发访问时出现的数据不一致问题。
综上所述,如果需要在
线程安全
的环境中使用 ArrayList,可以使用 Collections.synchronizedList 方法或者使用并发集合类 CopyOnWriteArrayList 来替代。