iOS 多线程(2)-线程安全(锁)

1. 关于锁的讨论

说到锁,大家可能对于这个问题都比较迷茫,都有什么锁,每个锁都有什么作用,我们在开发中应该在对应的情况下应该使用什么种类的锁?接下来我们大家一起研究一下这些问题,我们先来看下为什么要使用锁。

多线程是一把双刃剑,他即可以提高我们的运行效率,但是当资源共享多个线程同时存取同一块资源的时候,可能会造成引发数据错乱和数据安全问题。

安全隐患分析.png

通过上图我们发现,当线程A访问数据并对数据进行操作的同时,线程B访问的数据还是没有更新的数据,线程B同样对数据进行操作,当两个线程结束返回时,就会发生数据错乱的问题。
那遇到这样的数据错乱的问题我们应该怎么解决这种数据的错乱问题,我们想的是只要我们在操作数据从开始到结束的过程中没有线程对数据进行的任何增删改查的操作,所以我们得把这段过程加上一把锁,不让别人去操作就可以了.

安全隐患解决.png

我们再看一个实际的例子,下面通过一个售票实例来看一下线程安全的重要性;

#import "ViewController.h"

@interface ViewController ()

@property(nonatomic,strong)NSThread *thread01;
@property(nonatomic,strong)NSThread *thread02;
@property(nonatomic,strong)NSThread *thread03;
@property(nonatomic,assign)NSInteger numTicket;

//@property(nonatomic,strong)NSObject *obj;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 总票数为30
    self.numTicket = 30;
//开启线程的方式有很多,此处用NSThread开启.
    self.thread01 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread01.name = @"售票员01";
    self.thread02 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread02.name = @"售票员02";
    self.thread03 = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    self.thread03.name = @"售票员03";
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self.thread01 start];
    [self.thread02 start];
    [self.thread03 start];
}
// 售票
-(void)saleTicket
{
    while (1) {
        // 创建对象
        // self.obj = [[NSObject alloc]init];
        // 锁对象,本身就是一个对象,所以self就可以了
        // 锁定的时候,其他线程没有办法访问这段代码
        @synchronized (self) {
            // 模拟售票时间,我们让线程休息0.05s 
            [NSThread sleepForTimeInterval:0.05];
            if (self.numTicket > 0) {
                self.numTicket -= 1;
                NSLog(@"%@卖出了一张票,还剩下%zd张票",[NSThread currentThread].name,self.numTicket);
            }else{
                NSLog(@"票已经卖完了");
                break;
            }
        }
    }
}
@end

当没有加锁的时候我们看一下输出

没有加锁的输出.png

我们发现第29张,第27张都被销售了3次,这显然是不允许的,这就是数据错乱.

2. 线程锁

为了避免出现数据错乱这种情况,我们需要用到工具线程锁。

一. 线程锁有以下几个基本类型:

①. 自旋锁: atomic 即OSSpinLock 在ios中已经不是线程安全的了,如果共享数据已经有其他线程加锁了,线程会以死循环的方式等待锁,一旦被访问的资源被解锁,则等待资源的线程会立即执行。(效率最高,如果一直等不到锁会较占用cpu资源)
②. 信号锁:dispatch_semaphore是gcd中通过信号量来实现共享数据的数据安全。(效率第二)
③. 互斥锁:pthread_mutex ,nslock ,synchronized都是互斥锁。如果共享数据已经有其他线程加锁了,线程会进入休眠状态等待锁。一旦被访问的资源被解锁,则等待资源的线程会被唤醒。(synchronized效率最低)
④. 递归锁:pthread_mutex(recursive)与NSRecursiveLock , 多次调用不会阻塞已获取该锁的线程。
⑤. 条件锁:nsconditionlock 满足一定的条件的加锁和解锁,可以实现依赖关系。俗称线程依赖nscondition条件锁,也是通过信号量来解锁,主要用来实现生产者消费者模式。

二. 线程锁的几种同步方案

①. OSSpinLock
②. os_unfair_lock
③. pthread_mutex
④. dispatch_semaphore
⑤. dispatch_queue(DISPATCH_QUEUE_SERIAL)
⑥. NSLock
⑦. NSRecursiveLock
⑧. NSCondition
⑨. NSConditionLock
⑩. @synchronized
注意:加锁之后一定要记得解锁,避免造成死锁的情况.

①.OSSpinLock(自旋锁)

OSSpinLock叫做”自旋锁”,等待锁的线程会处于忙等(busy-wait)状态(进入死循环等待属性解锁),一直占用着CPU资源.目前已经不再安全,可能会出现优先级反转问题.如果等待锁的线程优先级较高,它会一直占用着CPU资源,优先级低的线程就无法释放锁.一旦解锁立即读取资源,因此效率高.
使用时应该导入头文件#import <libkern/OSAtomic.h>.

//能否共用一把锁的关键在于事件之间是否可以同时进行.
//初始化
OSSpinLock lock = OS_SPINLOCK_INIT;
//加锁
OSSpinLockLock(&lock);
//解锁
OSSpinLockUnlock(&lock);
//尝试加锁
OSSpinLockTry(&lock);

demo:

#import "OSSpinLockDemo.h"
#import <libkern/OSAtomic.h>
@interface OSSpinLockDemo()
@property (assign, nonatomic) OSSpinLock ticketLock;
@end

@implementation OSSpinLockDemo

- (instancetype)init
{
self = [super init];
if (self) {
self.ticketLock = OS_SPINLOCK_INIT;
}
return self;
}


//卖票
- (void)sellingTickets{
OSSpinLockLock(&_ticketLock);

[super sellingTickets];

OSSpinLockUnlock(&_ticketLock);
}

@end

OSSpinLock在iOS10.0以后就被弃用了,可以使用os_unfair_lock_lock替代。

②. os_unfair_lock(互斥锁)

os_unfair_lock用于取代不安全的OSSpinLock ,从iOS10开始才支持.从底层调用看,等待os_unfair_lock锁的线程会处于休眠状态,并非忙等.
使用的时候需要导入头文件#import <os/lock.h>.

//初始化
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
//加锁
os_unfair_lock_lock(&lock);
//解锁
os_unfair_lock_unlock(&lock);

demo

#import "os_unfair_lockDemo.h"
#import <os/lock.h>
@interface os_unfair_lockDemo()
@property (assign, nonatomic) os_unfair_lock ticketLock;
@end

@implementation os_unfair_lockDemo
- (instancetype)init
{
self = [super init];
if (self) {
self.ticketLock = OS_UNFAIR_LOCK_INIT;
}
return self;
}


//卖票
- (void)sellingTickets{
os_unfair_lock_lock(&_ticketLock);

[super sellingTickets];

os_unfair_lock_unlock(&_ticketLock);
}
@end
③. pthread_mutex(互斥锁)

mutex叫做”互斥锁”,等待锁的线程会处于休眠状态.
使用的时候需要导入头文件#import <pthread.h>:

// 静态初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
/*
 * Mutex type attributes
   #define PTHREAD_MUTEX_NORMAL         0
   #define PTHREAD_MUTEX_ERRORCHECK 1
   #define PTHREAD_MUTEX_RECURSIVE  2
   #define PTHREAD_MUTEX_DEFAULT        PTHREAD_MUTEX_NORMAL
 */
// 初始化锁
pthread_mutex_init(mutex, &attr);
//尝试加锁
pthread_mutex_trylock(&mutex);
// 加锁
pthread_mutex_lock(&mutex);
//解锁
pthread_mutex_unlock(&mutex);
// 销毁属性
pthread_mutexattr_destroy(&attr);
//销毁锁
pthread_mutex_destroy(&mutex);

备注:我们可以不初始化属性,在传属性的时候直接传NULL,表示使用默认属性PTHREAD_MUTEX_NORMAL。pthread_mutex_init(mutex, NULL);
demo

#import "pthread_mutexDemo.h"
#import <pthread.h>
@interface pthread_mutexDemo()
@property (assign, nonatomic) pthread_mutex_t ticketMutex;
@end

@implementation pthread_mutexDemo

- (instancetype)init
{
self = [super init];
if (self) {
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// 初始化锁
pthread_mutex_init(&(_ticketMutex), &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);


}
return self;
}

//卖票
- (void)sellingTickets{
pthread_mutex_lock(&_ticketMutex);

[super sellingTickets];

pthread_mutex_unlock(&_ticketMutex);
}
@end

这个代码暑促的时候会造成死锁,所以我们稍微修改一下代码.

//卖票
- (void)sellingTickets{
pthread_mutex_lock(&_ticketMutex);
[super sellingTickets];
[self sellingTickets2];
pthread_mutex_unlock(&_ticketMutex);
}


- (void)sellingTickets2{
pthread_mutex_lock(&_ticketMutex);
NSLog(@"%s",__func__);
pthread_mutex_unlock(&_ticketMutex);
}
④. pthread_mutex(属性修改为PTHREAD_MUTEX_RECURSIVE) – 递归锁

上面的代码就会造成线程死锁,因为方法sellingTickets的结束需要sellingTickets2解锁,方法sellingTickets2的结束需要sellingTickets解锁,相互引用造成死锁.
但是pthread_mutex_t里面有一个属性可以解决这个问题PTHREAD_MUTEX_RECURSIVE
PTHREAD_MUTEX_RECURSIVE递归锁:允许同一个线程对同一把锁进行重复加锁。要考重点同一个线程和同一把锁.

- (instancetype)init
{
self = [super init];
if (self) {
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(&(_ticketMutex), &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);


}
return self;
}

对于上面的问题还有一个解决方案就是在方法sellingTickets2中重新在创建一把新的锁,两个方法的锁对象不同,就不会造成线程死锁了。

⑤. pthread_cond – 条件锁

可以通过条件锁可以解决线程之间的依赖的问题.(例如生产者消费者的问题)

// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(&_mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);

// 初始化条件
pthread_cond_t condition
pthread_cond_init(&_cond, NULL);

// 等待条件
pthread_cond_wait(&_cond, &_mutex);

//激活一个等待该条件的线程
pthread_cond_signal(&_cond);
//激活所有等待该条件的线程
pthread_cond_broadcast(&_cond);

//销毁资源
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);

使用案例:假设我们有一个数组,里面有两个线程,一个是添加数组,一个是删除数组,我们先调用删除数组,在调用添加数组,但是在数组为空的时候不调用删除数组。

#import "pthread_mutexDemo1.h"
#import <pthread.h>

@interface pthread_mutexDemo1()
@property (assign, nonatomic) pthread_mutex_t mutex;
@property (assign, nonatomic) pthread_cond_t cond;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation pthread_mutexDemo1

- (instancetype)init
{
if (self = [super init]) {
// 初始化属性
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// 初始化锁
pthread_mutex_init(&_mutex, &attr);
// 销毁属性
pthread_mutexattr_destroy(&attr);

// 初始化条件
pthread_cond_init(&_cond, NULL);

self.data = [NSMutableArray array];
}
return self;
}
- (void)otherTest
{
[[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];

[[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

// 线程1
// 删除数组中的元素
- (void)__remove
{
pthread_mutex_lock(&_mutex);
NSLog(@"__remove - begin");

if (self.data.count == 0) {
// 等待
pthread_cond_wait(&_cond, &_mutex);
}

[self.data removeLastObject];
NSLog(@"删除了元素");

pthread_mutex_unlock(&_mutex);
}

// 线程2
// 往数组中添加元素
- (void)__add
{
pthread_mutex_lock(&_mutex);

sleep(1);

[self.data addObject:@"Test"];
NSLog(@"添加了元素");

// 激活一个等待该条件的线程
pthread_cond_signal(&_cond);

pthread_mutex_unlock(&_mutex);
}

- (void)dealloc
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
}
⑥. NSLock

NSLock是对mutex普通锁的封装。相当于pthread_mutex_init(mutex, NULL);.
NSLock 遵循 NSLocking协议。Lock 方法是加锁,unlock 是解锁,tryLock 是尝试加锁,如果失败的话返回 NO,lockBeforeDate: 是在指定Date之前尝试加锁,如果在指定时间之前都不能加锁,则返回NO.

@protocol NSLocking
- (void)lock;
- (void)unlock;
@end

@interface NSLock : NSObject <NSLocking> {
@private
void *_priv;
}

- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name
@end

demo:

#import "LockDemo.h"
@interface LockDemo()
@property (strong, nonatomic) NSLock *ticketLock;
@end
@implementation LockDemo
//卖票
- (void)sellingTickets{
[self.ticketLock lock];
[super sellingTickets];
[self.ticketLock unlock];
}

@end
⑦. NSRecursiveLock(递归锁)

NSRecursiveLock是对mutex递归锁的封装,API跟NSLock基本一致.

#import "RecursiveLockDemo.h"
@interface RecursiveLockDemo()
@property (nonatomic,strong) NSRecursiveLock *ticketLock;
@end
@implementation RecursiveLockDemo
//卖票
- (void)sellingTickets{
[self.ticketLock lock];
[super sellingTickets];
[self.ticketLock unlock];
}
@end
⑧. NSCondition

NSCondition是对mutex和cond的封装,更加面向对象,我们使用起来也更加的方便简洁.

@interface NSCondition : NSObject <NSLocking> {
- (void)wait;
- (BOOL)waitUntilDate:(NSDate *)limit;
- (void)signal;
- (void)broadcast;
@property (nullable, copy) NSString *name 
@end

demo

// 线程1
// 删除数组中的元素
- (void)__remove
{
[self.condition lock];
if (self.data.count == 0) {
// 等待
[self.condition wait];
}
[self.data removeLastObject];
NSLog(@"删除了元素");
[self.condition unlock];
}

// 线程2
// 往数组中添加元素
- (void)__add
{
[self.condition lock];
sleep(1);
[self.data addObject:@"Test"];
NSLog(@"添加了元素");
// 信号
[self.condition signal];
[self.condition unlock];
}
⑨. NSConditionLock

NSConditionLock是对NSCondition的进一步封装,可以设置具体的条件值.

@interface NSConditionLock : NSObject <NSLocking> {
 
- (instancetype)initWithCondition:(NSInteger)condition;

@property (readonly) NSInteger condition;
- (void)lockWhenCondition:(NSInteger)condition;
- (BOOL)tryLock;
- (BOOL)tryLockWhenCondition:(NSInteger)condition;
- (void)unlockWithCondition:(NSInteger)condition;
- (BOOL)lockBeforeDate:(NSDate *)limit;
- (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit;
@property (nullable, copy) NSString *name;
@end

里面有三个常用的方法:

  1. initWithCondition:初始化Condition,并且设置状态值.
  2. lockWhenCondition: (NSInteger)condition:当状态值为condition的时候加锁.
  3. unlockWithCondition: (NSInteger)condition当状态值为condition的时候解锁.

demo

@interface NSConditionLockDemo()
@property (strong, nonatomic) NSConditionLock *conditionLock;
@end
@implementation NSConditionLockDemo
- (instancetype)init
{
if (self = [super init]) {
self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
}
return self;
}

- (void)otherTest
{
[[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
[[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
}

- (void)__one
{
[self.conditionLock lock];
NSLog(@"__one");
sleep(1);
[self.conditionLock unlockWithCondition:2];
}

- (void)__two
{
[self.conditionLock lockWhenCondition:2];
NSLog(@"__two");
[self.conditionLock unlockWithCondition:3];
}
@end
⑩. dispatch_semaphore
  • semaphore叫做”信号量”.
  • 信号量的初始值,可以用来控制线程并发访问的最大数量.
  • 信号量的初始值为1,代表同时只允许1条线程访问资源,保证线程同步.
//表示最多开启5个线程
dispatch_semaphore_create(5);
// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
// 让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
@interface dispatch_semaphoreDemo()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@end
@implementation dispatch_semaphoreDemo
- (instancetype)init
{
if (self = [super init]) {
self.semaphore = dispatch_semaphore_create(1);
}
return self;
}
- (void)otherTest
{
for (int i = 0; i < 20; i++) {
[[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start];
}
}
- (void)test
{
// 如果信号量的值 > 0,就让信号量的值减1,然后继续往下执行代码
// 如果信号量的值 <= 0,就会休眠等待,直到信号量的值变成>0,就让信号量的值减1,然后继续往下执行代码
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);

sleep(2);
NSLog(@"test - %@", [NSThread currentThread]);

// 让信号量的值+1
dispatch_semaphore_signal(self.semaphore);
}
@end

我们在运行代码打印的时候发现,每隔一秒出现一次打印。虽然我们同时开启20个线程,但是一次只能访问一条线程的资源

⑪. dispatch_queue(串行队列)

使用GCD的串行队列也可以实现线程同步的.

dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
// 追加任务1
for (int i = 0; i < 2; ++i) {
NSLog(@"1---%@",[NSThread currentThread]);
}
});

dispatch_sync(queue, ^{
// 追加任务2
for (int i = 0; i < 2; ++i) {
NSLog(@"2---%@",[NSThread currentThread]);
}
});
@synchronized(递归锁)

@synchronized是对mutex递归锁的封装, @synchronized(obj)内部会生成obj对应的递归锁,然后进行加锁、解锁操作.

//卖票
- (void)sellingTickets{
@synchronized ([self class]) {
[super sellingTickets];
}
}

对是实现底层我们可以在objc4的objc-sync.mm文件中找到 synchronized就是在开始和结束的时候调用了objc_sync_enter&objc_sync_exit方法。
objc_sync_enter实现

int objc_sync_enter(id obj)
{
int result = OBJC_SYNC_SUCCESS;

if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
assert(data);
data->mutex.lock();
} else {
// @synchronized(nil) does nothing
if (DebugNilSync) {
_objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
}
objc_sync_nil();
}

return result;
}

就是根据id2data方法找到一个data对象,然后在对data对象进行mutex.lock()加锁操作。我们点击进入id2data方法继续查找.

#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap<SyncList> sDataLists;

发现获取data对象的方法其实就是根据sDataLists[obj].data这个方法来实现的,也就是一个哈希表。

三. 线程锁的对比

1.线程锁的对比

我们对一般的情况下各种不同锁的性能做对比.

性能对比:

  1. os_unfair_lock
  2. OSSpinLock
  3. dispatch_semaphore
  4. pthread_mutex
  5. dispatch_queue(DISPATCH_QUEUE_SERIAL)
  6. NSLock
  7. NSCondition
  8. pthread_mutex(recursive)
  9. NSRecursiveLock
  10. NSConditionLock
  11. @synchronized
2.自旋锁和互斥锁的对比

通过上面对于锁的介绍我们可知11种锁中只有OSSpinLock是自旋锁,他的替代方案os_unfair_lock也是互斥锁,既然是自旋锁和互斥锁的比较也就是OSSpinLock和其他种类的锁作比较.

自旋锁使用起来比较划算的方案 互斥锁使用起来比较划算的方案
1.预计线程等待锁的时间很短.
2.加锁的代码(临界区)经常被调用,
但竞争情况很少发生.
3.CPU资源不紧张.
4.多核处理器
1.预计线程等待锁的时间较长.
2.单核处理器.
3.临界区有IO操作.
临界区代码复杂或者循环量大.
临界区竞争非常激烈.
                            想了解更多iOS学习知识请联系:QQ(814299221)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 线程安全是怎么产生的 常见比如线程内操作了一个线程外的非线程安全变量,这个时候一定要考虑线程安全和同步。 - (v...
    幽城88阅读 647评论 0 0
  • demo下载 建议一边看文章,一边看代码。 声明:关于性能的分析是基于我的测试代码来的,我也看到和网上很多测试结果...
    炸街程序猿阅读 777评论 0 2
  • 前言 一块资源可能会被多个线程共享,也就是多个线程可能会访问同一块资源,比如多个线程访问同一个对象、同一个变量、同...
    WQ_UESTC阅读 853评论 0 5
  • 一、前言 前段时间看了几个开源项目,发现他们保持线程同步的方式各不相同,有@synchronized、NSLock...
    稻春阅读 464评论 0 0
  • 锁是一种同步机制,用于多线程环境中对资源访问的限制iOS中常见锁的性能对比图(摘自:ibireme): iOS锁的...
    LiLS阅读 1,505评论 0 6