├── P139 走进Thread 修正.md ├── P17 页 从汇编语言角度理解结构体 修正.md ├── README.md ├── p87 获取写锁流程、释放写锁流程修正.md └── 文字符号编写错误汇总.md /P139 走进Thread 修正.md: -------------------------------------------------------------------------------- 1 | # P139 走进Thread 修正 2 | 3 | 接下来我们来看看线程的运行状态有哪些。 4 | 5 | 1. NEW:未启动状态 6 | 7 | 2. RUNNABLE:运行状态 8 | 9 | 3. BLOCKED:线程正在等待监视器锁;正在synchronized块/方法上等待获取锁,或调用了Object.wait(),等待重新获取锁(修正为:线程正在等待监视器锁,也即正在synchronized块/方法上等待获取锁。) 10 | 11 | 4. WAITING:等待线程的状态; 12 | 13 | a) 调用Object.wait()、Thread.join()、LockSupport.park()会进入该状态,这里都没设置超时。 14 | 15 | b) 处于等待状态的线程正在等待另一个线程执行特定操作。 16 | 17 | c) 调用Object.wait()的线程等待另一个线程调用Object.notify()、Object.notifyAll() 18 | 19 | d) 调用了Thread.join()的线程等待指定线程终止。 20 | 21 | 5. TIMED_WAITING:具有指定等待时间的等待线程的线程状态 22 | 23 | 调用Thread.sleep(超时时间)、Object.wait(超时时间)、LockSupport.parkNanos、LockSupport.parkUntil会进入该状态,这里均设置了超时 24 | 25 | 6. TERMINATED:线程已经终止,已经执行完成的线程状态 -------------------------------------------------------------------------------- /P17 页 从汇编语言角度理解结构体 修正.md: -------------------------------------------------------------------------------- 1 | # P17 页 从汇编语言角度理解结构体 修正 2 | 3 | ```c 4 | #include 5 | struct User{ 6 | int age; 7 | char *name; 8 | long money; 9 | }; 10 | int main(){ 11 | struct User user = {17,"lisa",10000}; 12 | struct User *p = &user; // 创建结构体指针 13 | printf("user 变量的地址: %p\n",&user); // user结构体的地址 14 | printf("p 指针访问值age:%d\n",p->age); // 使用指针访问age值 15 | printf("age的地址:%p\n",&(p->age)); // age的地址 16 | printf("p 指针访问值name:%s\n",p->name); // 使用指针访问值name值 17 | printf("name的地址:%p\n",&(p->name)); // name的地址 18 | } 19 | ``` 20 | 21 | 输出结果如下。 22 | 23 | ```tex 24 | user 变量的地址: 0x7ffe3c50b740 25 | p 指针访问值age:17 26 | age的地址:0x7ffe3c50b740 27 | p 指针访问值name:lisa 28 | name的地址:0x7ffe3c50b748 29 | ``` 30 | 31 | 可以看到name的地址和age的地址正好相差8个字节,正好是一个整形4字节加上4个char字节。那么我们将它反汇编,看看汇编代码的实现。(修正:我们这里讨论的是64位机。所以:这里的age为整形4字节、*name指针地址8字节,money 长整形8字节,所以共:4+8+8 = 20,由于结构体按8字节对齐,所以这里的strut大小为24,各位读者可以用sizeof操作符来测试,而在下面开辟栈帧时,subq $32, %rsp,开辟了32字节,是由于gcc默认对齐为16byte,所以这里需要对齐到32字节,而代码中我们使用了User *p = &user,这里保存了一个p指针,正好利用了这个对齐的8字节。读者可以只写一个int age,开辟的将大小为subq $16, %rsp。所以修改为 --------> 可以看到name的地址和age的地址正好相差8个字节,为一个整形4字节加上4个字节的对齐填充(gcc结构体64位机按8字节对齐,4字节的age需要对齐到8字节,这样总的结构体大小为24字节) )。 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 《深入理解Java高并发编程》勘误 2 | 笔者水平有限,全文均为手打,难免出现错别字、错误解释、错误代码等现象,本repository用于对这些内容进行勘误,也感谢各位读者的指正,让我们共同把这本书的问题进行修正,我会在第二版里加上图片描述、每节总结更新~各位读者可以在这里提Issue,我会通过markdown的形式进行修正 3 | -------------------------------------------------------------------------------- /p87 获取写锁流程、释放写锁流程修正.md: -------------------------------------------------------------------------------- 1 | # p87 获取写锁流程、释放写锁流程修正 2 | 3 | 5. 获取写锁流程 4 | 我们首先原子性减0xffff0001(修正:原子性加0xffff0001。注:相当于减0x0000ffff。读者可以看原码到补码的计算过程),然后判断原来的状态是否为0,如果是,那么表明获取写锁成功,否则我们需要调用rwsem_down_write_failed进行阻塞排队操作。 5 | 6 | ```c 7 | 8 | static inline void _down_write(struct rw_semaphore *sem) 9 | { 10 | int tmp = RWSEM_ACTIVE_WRITE_BIAS; 11 | _asm_ _volatile_( 12 | // 原子性减0xffff0001(修正:原子性加0xffff0001) 也即写锁偏移量, 返回旧值放到edx寄存器中 13 | LOCK_PREFIX " xadd %%edx,(%%eax)\n\t" 14 | // 看之前的count值是否为0,因为只有为0才是无锁态 15 | " testl %%edx,%%edx\n\t" 16 | // 如果不为0,则获取锁失败跳到标号2处执行 17 | " jnz 2f\n\t" 18 | "1:\n\t" 19 | LOCK_SECTION_START("") 20 | // 保存ecx调用rwsem_down_write_failed处理 21 | "2:\n\t" 22 | " pushl %%ecx\n\t" 23 | " call rwsem_down_write_failed\n\t" 24 | " popl %%ecx\n\t" 25 | " jmp 1b\n" 26 | LOCK_SECTION_END 27 | : "=m"(sem->count), "=d"(tmp) 28 | : "a"(sem), "1"(tmp), "m"(sem->count) 29 | : "memory", "cc"); 30 | } 31 | 32 | // 处理写锁上锁失败逻辑 33 | struct rw_semaphore *rwsem_down_write_failed(struct rw_semaphore *sem) 34 | { 35 | // 创建等待节点 36 | struct rwsem_waiter waiter; 37 | waiter.flags = RWSEM_WAITING_FOR_WRITE; 38 | // 调用公共处理逻辑执行等待操作。-RWSEM_ACTIVE_BIAS =0xffff ffff 39 | rwsem_down_failed_common(sem,&waiter,-RWSEM_ACTIVE_BIAS); 40 | return sem; 41 | } 42 | ``` 43 | 44 | 6. 释放写锁流程 45 | 46 | ```c 47 | static inline void _up_write(struct rw_semaphore *sem) 48 | { 49 | _asm_ _volatile_( 50 | " movl %2,%%edx" // 将写锁偏移量取负数后的值,也即0x0000 ffff放入edx 51 | // 尝试从0xffff0001(这也是持有写锁且无等待任务的状态,因为写写,读写互斥)变为 0x00000000 52 | LOCK_PREFIX " xaddl %%edx,(%%eax)" 53 | " jnz 2f" // 如果之前count值不为0(修正:前面相加之后count值不为0,JNZ用于判断EFLAGS的ZF标志位是否为0),则有任务正在等待,那么跳到标号2处执行 54 | "1:" 55 | LOCK_SECTION_START("") 56 | "2:" 57 | " decw %%dx" // 对dx也就是释放前的lock值低16位自减,看看是否为0,也就是看看是否有活动的任务 58 | " jnz 1b" // 如果不为0,这时是在写锁释放后,有任务获得了锁,则退出。否则调用rwsem_wake唤醒等待任务 59 | " pushl %%ecx" 60 | " call rwsem_wake" 61 | " popl %%ecx" 62 | " jmp 1b" 63 | LOCK_SECTION_END 64 | : "=m"(sem->count) 65 | : "a"(sem), "i"(-RWSEM_ACTIVE_WRITE_BIAS), "m"(sem->count) 66 | : "memory", "cc", "edx"); 67 | } 68 | ``` 69 | 70 | -------------------------------------------------------------------------------- /文字符号编写错误汇总.md: -------------------------------------------------------------------------------- 1 | # 文字符号编写错误汇总 2 | 3 | ## p10 4 | 5 | SUB 指令 的指令格式应该为:SUB A, Rn/direct/#data 6 | 7 | ## p84 8 | 9 | 自旋锁相对简单,不管是普通自旋锁还是读写自旋锁都是通过原子性命令来加减操作。而对于读写锁而言我们通过将lock变量变为0x01000000值,然后将第六位(修正:第七位)作为写锁标志位来使用。 10 | 11 | ## P100 12 | 13 | 前面介绍了自旋锁、互斥锁、信号量、读写锁、req顺序锁。(修正:seq顺序锁)。 14 | 15 | ## P140 16 | 17 | 该方法是最初设计用来在没有任何清除的情况下销毁线程,它持有的所有显示器(修正:监视器)已经上锁,但是这个方法没有被实现 --------------------------------------------------------------------------------