前言
当我们提到线程时,就会联想到线程不安全,如何保线程安全以及多线程之间数据访问如何保证不出问题呢,带着这些疑问,我们来介绍一下锁的原理。
1 Synchronized的比较和测试
有人说synchronized锁的耗费的性能是最强的,测试代如下:
int ro_runTimes = 100000;
/** OSSpinLock 性能 */
{
OSSpinLock ro_spinlock = OS_SPINLOCK_INIT;
double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < ro_runTimes; i++) {
OSSpinLockLock(&ro_spinlock); //解锁
OSSpinLockUnlock(&ro_spinlock);
}
double_t kc_endTime = CFAbsoluteTimeGetCurrent() ;
NSLog(@"OSSpinLock: %f ms",(kc_endTime - ro_beginTime)*1000);
}
/** dispatch_semaphore_t 性能 */
{
dispatch_semaphore_t ro_sem = dispatch_semaphore_create(1);
double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < ro_runTimes; i++) {
dispatch_semaphore_wait(ro_sem, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(ro_sem);
}
double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
NSLog(@"dispatch_semaphore_t: %f ms",(ro_endTime - ro_beginTime)*1000);
}
/** os_unfair_lock_lock 性能 */
{
os_unfair_lock ro_unfairlock = OS_UNFAIR_LOCK_INIT;
double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < ro_runTimes; i++) {
os_unfair_lock_lock(&ro_unfairlock);
os_unfair_lock_unlock(&ro_unfairlock);
}
double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
NSLog(@"os_unfair_lock_lock: %f ms",(ro_endTime - ro_beginTime)*1000);
}
/** pthread_mutex_t 性能 */
{
pthread_mutex_t ro_metext = PTHREAD_MUTEX_INITIALIZER;
double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < ro_runTimes; i++) {
pthread_mutex_lock(&ro_metext);
pthread_mutex_unlock(&ro_metext);
}
double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
NSLog(@"pthread_mutex_t: %f ms",(ro_endTime - ro_beginTime)*1000);
}
/** NSlock 性能 */
{
NSLock *ro_lock = [NSLock new];
double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < ro_runTimes; i++) {
[ro_lock lock];
[ro_lock unlock];
}
double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
NSLog(@"NSlock: %f ms",(ro_endTime - ro_beginTime)*1000);
}
/** NSCondition 性能 */
{
NSCondition *ro_condition = [NSCondition new];
double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < ro_runTimes; i++) {
[ro_condition lock];
[ro_condition unlock];
}
double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
NSLog(@"NSCondition: %f ms",(ro_endTime - ro_beginTime)*1000);
}
/** PTHREAD_MUTEX_RECURSIVE 性能 */
{
pthread_mutex_t ro_metext_recurive;
pthread_mutexattr_t attr;
pthread_mutexattr_init (&attr);
pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init (&ro_metext_recurive, &attr);
double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < ro_runTimes; i++) {
pthread_mutex_lock(&ro_metext_recurive);
pthread_mutex_unlock(&ro_metext_recurive);
}
double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
NSLog(@"PTHREAD_MUTEX_RECURSIVE: %f ms",(ro_endTime - ro_beginTime)*1000);
}
/** NSRecursiveLock 性能 */
{
NSRecursiveLock *ro_recursiveLock = [NSRecursiveLock new];
double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < ro_runTimes; i++) {
[ro_recursiveLock lock];
[ro_recursiveLock unlock];
}
double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
NSLog(@"NSRecursiveLock: %f ms",(ro_endTime - ro_beginTime)*1000);
}
/** NSConditionLock 性能 */
{
NSConditionLock *ro_conditionLock = [NSConditionLock new];
double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < ro_runTimes; i++) {
[ro_conditionLock lock];
[ro_conditionLock unlock];
}
double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
NSLog(@"NSConditionLock: %f ms",(ro_endTime - ro_beginTime)*1000);
}
/** @synchronized 性能 */
{
double_t ro_beginTime = CFAbsoluteTimeGetCurrent();
for (int i=0 ; i < ro_runTimes; i++) {
@synchronized(self) {}
}
double_t ro_endTime = CFAbsoluteTimeGetCurrent() ;
NSLog(@"@synchronized: %f ms",(ro_endTime - ro_beginTime)*1000);
}
经过测试,在不同的环境表现不一样,真机表现比较好,模拟器表现要差点,这说明synchronized这个锁在真机上有一定的优化。synchronized锁在我们的应用中使用率相对比较高。下面我们分析下原理。
2 Synchronized的分析
2.1 Synchronized初探
我们来一份售票的代码,如下:
- (void)ro_testSaleTicket{
/// 异步售票
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
/// 异步售票
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 5; i++) {
[self saleTicket];
}
});
/// 异步售票
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 3; i++) {
[self saleTicket];
}
});
/// 异步售票
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (int i = 0; i < 10; i++) {
[self saleTicket];
}
});
}
- (void)saleTicket{
if (self.ticketCount > 0) {
self.ticketCount--;
sleep(0.1);
NSLog(@"当前余票还剩:%lu张",(unsigned long)self.ticketCount);
}else{
NSLog(@"当前车票已售罄");
}
}
ro_testSaleTicket方法中有几个异步售票,这个时候就会产生线程安全的问题,我们可以在saleTicket这个方法中加入
@synchronized (self) {
}
来处理
- 圆括号里面,我们传的self,为什么要传self
- 我们可不可以传nil
- 大括号的代码块到底是什么
- 加锁的效果
- 递归可重入(可以递归,继续synchronized)
- synchronized的数据结构是什么
我们带着这些疑问,我们来分析synchronized的原理。
我们使用命令把我们的demo工程传换cpp,如下:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m。
我们打开main.cpp文件,我们找到发下代码:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
{
id _rethrow = 0;
id _sync_obj = (id)appDelegateClassName;
objc_sync_enter(_sync_obj);
try {
struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {}
~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
id sync_exit;
} _sync_exit(_sync_obj);
} catch (id e) {_rethrow = e;}
{ struct _FIN { _FIN(id reth) : rethrow(reth) {}
~_FIN() { if (rethrow) objc_exception_throw(rethrow); }
id rethrow;
} _fin_force_rethow(_rethrow);}
}
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
其中
id _rethrow = 0;
id _sync_obj = (id)appDelegateClassName;
objc_sync_enter(_sync_obj);
try {
struct _SYNC_EXIT { _SYNC_EXIT(id arg) : sync_exit(arg) {}
~_SYNC_EXIT() {objc_sync_exit(sync_exit);}
id sync_exit;
} _sync_exit(_sync_obj);
这段代码加锁成功的代码,_SYNC_EXIT是一个结构体,我们整理代码就成了以下:
objc_sync_enter(_sync_obj);
_sync_exit(_sync_obj);
_sync_exit相当于_SYNC_EXIT的析构,也就是objc_sync_exit(sync_exit);这个函数,相当于:
objc_sync_enter(_sync_obj);
objc_sync_exit(_sync_obj)
我们通过符号断点objc_sync_enter,查看它的汇编流程,如图:
从这里可以看出是在libobjc.A.dylib中,那我们需要打开objc(818)的源码进分析了,我们在源码中搜下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;
}
这里如果obj不存在就走调用else代码,我们看到注释* @synchronized(nil) does nothing这里的synchronized传的nil,什么事情都没做,objc_sync_nil*最终调用以下代码:
# define BREAKPOINT_FUNCTION(prototype) \
OBJC_EXTERN __attribute__((noinline, used, visibility("hidden"))) \
prototype { asm(""); }
这里什么都没实现。
if (obj) {
SyncData* data = id2data(obj, ACQUIRE);
ASSERT(data);
data->mutex.lock();
}
这段代码才是核心,我们来分析下。
我们先搜下objc_sync_exit这个函数,看下它的源码,如下 :
int objc_sync_exit(id obj)
{
int result = OBJC_SYNC_SUCCESS;
if (obj) {
SyncData* data = id2data(obj, RELEASE);
if (!data) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
} else {
bool okay = data->mutex.tryUnlock();
if (!okay) {
result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR;
}
}
} else {
// @synchronized(nil) does nothing
}
return result;
}
- // @synchronized(nil) does nothing这里的传nil同样什么也没做。
objc_sync_enter和objc_sync_exit*一比较,可以发现就是一进一出,lock跟unlock。
objc_sync_enter函数中
SyncData* data = id2data(obj, ACQUIRE);
与
objc_sync_exit函数中
SyncData* data = id2data(obj, RELEASE);
一比较发现,id2data一个传的是ACQUIRE,一个传的RELEASE,一个加锁,一个释放锁,是对称的。
这两个函数都对SyncData进行操作判断,那么SyncData是什么呢,我们来看下。
经过搜索发现
typedef struct alignas(CacheLineSize) SyncData {
struct SyncData* nextData;
DisguisedPtr object;
int32_t threadCount; // number of THREADS using this block
recursive_mutex_t mutex;
} SyncData;
这里可以看出是一个结构体。
- struct SyncData nextData;*这里很显眼是一个单向链接表结构。
- DisguisedPtr
object; ,我们看下它的源码
class DisguisedPtr {
uintptr_t value;
static uintptr_t disguise(T* ptr) {
return -(uintptr_t)ptr;
}
static T* undisguise(uintptr_t val) {
return (T*)-val;
}
public:
DisguisedPtr() { }
DisguisedPtr(T* ptr)
: value(disguise(ptr)) { }
DisguisedPtr(const DisguisedPtr& ptr)
: value(ptr.value) { }
DisguisedPtr& operator = (T* rhs) {
value = disguise(rhs);
return *this;
}
DisguisedPtr& operator = (const DisguisedPtr& rhs) {
value = rhs.value;
return *this;
}
operator T* () const {
return undisguise(value);
}
T* operator -> () const {
return undisguise(value);
}
T& operator * () const {
return *undisguise(value);
}
T& operator [] (size_t i) const {
return undisguise(value)[i];
}
// pointer arithmetic operators omitted
// because we don't currently use them anywhere
};
这里进行了封装,把ptr按照unsigned long类型转换。
- *int32_t threadCount; * 多条线程保护
- recursive_mutex_t mutex; 递归锁,不能多线程递归使用,不然会BUG,后面演示。
2.2 synchronized整个数据结构
我们来看下objc_sync_enter这个函数
SyncData* data = id2data(obj, ACQUIRE);
这行代码到底了什么操作,objc_sync_enter与objc_sync_exit都调用了id2data,那么它肯定是我们的重心研究对象,我们就顺藤摸瓜分析一下。
我们找下它的源码,如下:
static SyncData* id2data(id object, enum usage why)
{
spinlock_t *lockp = &LOCK_FOR_OBJ(object);
SyncData **listp = &LIST_FOR_OBJ(object);
SyncData* result = NULL;
#if SUPPORT_DIRECT_THREAD_KEYS
// Check per-thread single-entry fast cache for matching object
bool fastCacheOccupied = NO;
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
if (data) {
fastCacheOccupied = YES;
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
if (result->threadCount <= 0 || lockCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
switch(why) {
case ACQUIRE: {
lockCount++;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
case RELEASE:
lockCount--;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
#endif
// Check per-thread cache of already-owned locks for matching object
SyncCache *cache = fetch_cache(NO);
if (cache) {
unsigned int i;
for (i = 0; i < cache->used; i++) {
SyncCacheItem *item = &cache->list[i];
if (item->data->object != object) continue;
// Found a match.
result = item->data;
if (result->threadCount <= 0 || item->lockCount <= 0) {
_objc_fatal("id2data cache is buggy");
}
switch(why) {
case ACQUIRE:
item->lockCount++;
break;
case RELEASE:
item->lockCount--;
if (item->lockCount == 0) {
// remove from per-thread cache
cache->list[i] = cache->list[--cache->used];
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
}
}
// Thread cache didn't find anything.
// Walk in-use list looking for matching object
// Spinlock prevents multiple threads from creating multiple
// locks for the same new object.
// We could keep the nodes in some hash table if we find that there are
// more than 20 or so distinct locks active, but we don't do that now.
lockp->lock();
{
SyncData* p;
SyncData* firstUnused = NULL;
for (p = *listp; p != NULL; p = p->nextData) {
if ( p->object == object ) {
result = p;
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);
goto done;
}
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// no SyncData currently associated with object
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it
if ( firstUnused != NULL ) {
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
goto done;
}
}
// Allocate a new SyncData and add to list.
// XXX allocating memory with a global lock held is bad practice,
// might be worth releasing the lock, allocating, and searching again.
// But since we never free these guys we won't be stuck in allocation very often.
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
result->nextData = *listp;
*listp = result;
done:
lockp->unlock();
if (result) {
// Only new ACQUIRE should get here.
// All RELEASE and CHECK and recursive ACQUIRE are
// handled by the per-thread caches above.
if (why == RELEASE) {
// Probably some thread is incorrectly exiting
// while the object is held by another thread.
return nil;
}
if (why != ACQUIRE) _objc_fatal("id2data is buggy");
if (result->object != object) _objc_fatal("id2data is buggy");
#if SUPPORT_DIRECT_THREAD_KEYS
if (!fastCacheOccupied) {
// Save in fast thread cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
} else
#endif
{
// Save in thread cache
if (!cache) cache = fetch_cache(YES);
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
}
return result;
}
代码较复杂,我们一块一块的来分析。
这个函数整个大的代码块
/// 1
if (data) {
/// 逻辑
}
///21
if (cache) {
/// 逻辑
}
/// 3
lockp->lock();{
/// 逻辑
}
/// 4
lockp->unlock();
if (result) {
/// 逻辑
}
我们接下来,一段一段的来分析。
#if SUPPORT_DIRECT_THREAD_KEYS需要支持TLS,什么是TLS, 我们来解释下。
线程局部存储(Thread Local Storage, TLS)是操作系统为线程单独提供的私有空间,通常只有有限的容量。Linux系统下通常通过pthread库中的
pthread_key_create()、
pthread_getspecific()、
pthread_setspecific()、
pthread_key_delete()
if(data)和if(cache)有两个地方存储SyncData。
接着执行
sizeof(SyncData));
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
result->nextData = *listp;
*listp = result;
这段代码对result赋值操作,继续走,
done:
lockp->unlock();
done了之后,又进行了unlock(),这是为什么呢?
这是因为在内部操作的时候,开辟内存空间,内存空间的相关处理,保证线程安全。
上面我们通过分析SyncData,知道它是一个单向链表,为什么呢,为什么会有两次存储?
spinlock_t *lockp = &LOCK_FOR_OBJ(object);是通过object获取了一把锁,#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock这是这个宏定义。
SyncData **listp = &LIST_FOR_OBJ(object);也是通过个获取的。
#define LOCK_FOR_OBJ(obj) sDataLists[obj].lock
#define LIST_FOR_OBJ(obj) sDataLists[obj].data
static StripedMap sDataLists;
这里为什么会有sDataLists?
sDataLists是一个静态全局变量。
StripedMap是哈希map,通过下标,哈希有时候会出现冲突,通常我们再次哈希来解决,如果再冲突,我们再次哈希,这是常规做法,这次我们介绍一下拉链法。
sDataLists是静态全局变量,StripedMap其实是哈希结构,也就是全局的哈希表,SyncList的结构如下:
struct SyncList {
SyncData *data;
spinlock_t lock;
constexpr SyncList() : data(nil), lock(fork_unsafe_lock) { }
};
我们通过LLDB调试下,如图:
这里就是sDataLists结构,一共64个数据。
第4个数据,说明我们来了一次,这也间接证明这是哈希结构。
synData数据来源于object。
假如我们锁的是self,又来了self锁,又会创建一个synData存储到哈希表中,这个时候关键词都是self,这时候怎么办,这时候会存在哈希冲突的结构,在我们的SyncList不在是一个数据结构,而是通过链表形式存储,因为两两不会冲突,链表结构,不方便查询,但是SyncData是不需要我们查询的,我们只需要加锁,解锁,只需要增删,不需要查询,这就形成了拉链法。
2.3 synchronized的原理分析
我们来分析下synchronized的存储原理。
SyncData *data = (SyncData *)tls_get_direct(SYNC_DATA_DIRECT_KEY);
如果这里TLS(栈存储)存在,会走到if(data)里面的逻辑,如果不存在往下走,因为第一次来,会走到
posix_memalign((void **)&result, alignof(SyncData), sizeof(SyncData));
result->object = (objc_object *)object;
result->threadCount = 1;
new (&result->mutex) recursive_mutex_t(fork_unsafe_lock);
result->nextData = *listp;
*listp = result;
这里创建一个SyncData,并存起来,
*result->nextData = listp; *listp = result;这里的nextData指向的Listp,这是链表的头插法,也就是从头部插入数据。
/// 如果支持TLS走这个
#if SUPPORT_DIRECT_THREAD_KEYS
if (!fastCacheOccupied) {
// Save in fast thread cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, result);
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)1);
} else
#endif
/// 如果不支持TLS走这个
{
// Save in thread cache
if (!cache) cache = fetch_cache(YES);
cache->list[cache->used].data = result;
cache->list[cache->used].lockCount = 1;
cache->used++;
}
接着执行这里存储。
后面进来这里就是走正常的加锁,解锁流程,我们来看下。
if (data->object == object) {
// Found a match in fast cache.
uintptr_t lockCount;
result = data;
lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);
if (result->threadCount <= 0 || lockCount <= 0) {
_objc_fatal("id2data fastcache is buggy");
}
switch(why) {
case ACQUIRE: {
lockCount++;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
break;
}
case RELEASE:
lockCount--;
tls_set_direct(SYNC_COUNT_DIRECT_KEY, (void*)lockCount);
if (lockCount == 0) {
// remove from fast cache
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
}
break;
case CHECK:
// do nothing
break;
}
return result;
SyncData data = (SyncData )tls_get_direct(SYNC_DATA_DIRECT_KEY);这行代码是获取上一次的数据。
这里if (data->object == object)这里跟上一次的object对比,如果等于,就会走加锁解锁的流程,如果不等于就执行以下代码:
lockp->lock();
{
SyncData* p;
SyncData* firstUnused = NULL;
for (p = *listp; p != NULL; p = p->nextData) {
if ( p->object == object ) {
result = p;
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);
goto done;
}
if ( (firstUnused == NULL) && (p->threadCount == 0) )
firstUnused = p;
}
// no SyncData currently associated with object
if ( (why == RELEASE) || (why == CHECK) )
goto done;
// an unused one was found, use it
if ( firstUnused != NULL ) {
result = firstUnused;
result->object = (objc_object *)object;
result->threadCount = 1;
goto done;
}
}
if ( p->object == object )这时的判断照样不会执行,因为我们换一个新的对象,如果是相同的对象必然会进去。
这也证明,虽然syncData不同,但是objc相同,同一个对象会重复加锁,这就是它的可递归可重入。
我们再来分析下,是不是这样的,if (data->object == object)如果这里相等,执行lockCount = (uintptr_t)tls_get_direct(SYNC_COUNT_DIRECT_KEY);这里拿到锁了多少次,如果ACQUIRE,lockCount++,再锁一次,如果是RELEASE,执行lockCount--;锁的次数减一,之后进行解锁,if (lockCount == 0)这里有这样的一个判断,如果lockCount==0,当前被解释完成了,执行
tls_set_direct(SYNC_DATA_DIRECT_KEY, NULL);
// atomic because may collide with concurrent ACQUIRE
OSAtomicDecrement32Barrier(&result->threadCount);
在线程的栈存空间对objc的加锁没有了,完成了,这也说明在其它的线程同样有这个操作。这也证实了可多线程加锁。
这样一个案例
1 线程加锁
2 线程加锁
3 线程加锁
4 线程加锁
那么解锁的过程,必然是
4 线程解锁
3 线程解锁
2 线程解锁
1 线程解锁
4线程是不可能解锁3线程,4线程是在自己的线程存储空间,线程之间的存储空间是独立的,所以不可能解没3线程
所以这里threadCount(默认标识是1)具备多线程的,lockCount说明可递归。
(p = *listp; p != NULL; p = p->nextData) {
if ( p->object == object ) {
result = p;
// atomic because may collide with concurrent RELEASE
OSAtomicIncrement32Barrier(&result->threadCount);
goto done;
}
这段代码,如果对象相同,进行内存平栈。
Sync总结:
- sync全局哈希表-采用拉链法
- 形成sDataList array存储了synclist,synclist又绑定了objc(需要加锁的对象)
-
- objc又封装了objc_sync_enter/objc_sync_exit这两个对称,递归锁
- 支持两种存储,一种TLS,一种是cache
- 第一次创建,然后头插法创建链表结构,标记为threadCount为1
- 第二次判断是不是同一个对象进来
- 如果是同一个对象,TLS的lockCount++操作
- 如果在TLS找不到,重新创建一个SyncData并对threadCount++
- **lock--,threadCount-- **
Synchronized:具备可重入,可递归,可多线程,有TLS的保障了threadCount,lockCount标识进来多少条线程对这个对象加锁
2.4 synchronized的注意事项
synchronized锁的对象不要出现空,原因上述源码已说明。
synchronized锁的对象的生命周期至少要跟线程代码的对象一样或者比它长,所以一般我们使用self
synchronized锁对象使用self,可以防止多次创建,方便存储和释放。
synchronized锁对象使用self,如果当前的对象锁的很长,这个链表就会很大,就会对拉链有一定的负担,不过一般不会操作的很频繁。
synchronized锁,模拟器与真机耗时之间相差比较大是因为,在模拟器是有64个大小的限制
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
enum { StripeCount = 8 };
#else
enum { StripeCount = 64 };
#endif
因为这里的判断,如果是真机就是8,64个要耗费长,查的也长时间长。
结语
这篇文章介绍了synchronized锁的原理以及注意事项和坑点,因为Synchronized使用率较高,我们分析的比较详细,后续还会推出锁的相关文章。