0%

2020年6月7日 下午12:25
网络字节序与主机字节序 - jacktu - 博客园

内存地址

1
2
3
     4000   4001    4002     4003
LE 04 03 02 01
BE 01 02 03 04

网络字节顺序

网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian排序方式。

进行转换

  • Bsd socket提供了转换的函数 有下面四个
    1. htons 把unsigned short类型从主机序转换到网络序
    2. htonl 把unsigned long类型从主机序转换到网络序
    3. ntohs 把unsigned short类型从网络序转换到主机序
    4. ntohl 把unsigned long类型从网络序转换到主机序

在使用little endian的系统中 这些函数会把字节序进行转换
在使用big endian类型的系统中 这些函数会定义成空宏

2020年6月3日 上午9:22

总结:

  1. 编程风格:
    1. C函数风格,注册三个全局函数到网络库,网络库通过函数指针进行回调
    2. 面向对象风格,用一个EchoServer继承TcpServer(抽象类),实现三个接口OnConnection,OnMessage,OnClose
    3. 基于对象的编程风格,用一个EchoServer包含一个TcpServer(具体类)对象,在构造函数中用boost::bind来注册三个成员函数OnConnection,OnMessage,OnClose
      1. ::要站在编程范式的角度理解:::
        1. Thread类 -> control
        2. 基于对象的类 -> logic
  2. static void* ThreadRoutine(void* arg);
    1. 为什么Thread类中要使用static成员函数?
      1. pthread_create(&threadId_, NULL, ThreadRoutine, this);
      2. 在pthread_create函数无法接受有隐藏参数this的成员函数
    2. 为什么成员函数式private:
      1. 防止用户直接在Thread_test.cpp中通过类对象去调用run,这样会造成主线程main来执行run函数,而不是新建的线程
  3. explicit Thread(const ThreadFunc& func);
    1. 在构造函数中传入logic部分,其实使用的是boost库
      1. typedef boost::function<void ()> ThreadFunc;
    2. 通过boost库的封装,将方法的传入就向变量的传入一样,并且还有对应的私有成员变量ThreadFunc func_;

1.Thread_test.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include "Thread.h"
#include <boost/bind.hpp>
#include <unistd.h>
#include <iostream>
using namespace std;

class Foo
{
public:
Foo(int count) : count_(count)
{
}

void MemberFun()
{
while (count_--)
{
cout<<"this is a test ..."<<endl;
sleep(1);
}
}

void MemberFun2(int x)
{
while (count_--)
{
cout<<"x="<<x<<" this is a test2 ..."<<endl;
sleep(1);
}
}

int count_;
};

void ThreadFunc()
{
cout<<"ThreadFunc ..."<<endl;
}

void ThreadFunc2(int count)
{
while (count--)
{
cout<<"ThreadFunc2 ..."<<endl;
sleep(1);
}
}


int main(void)
{
Thread t1(ThreadFunc);
Thread t2(boost::bind(ThreadFunc2, 3));
Foo foo(3);
Thread t3(boost::bind(&Foo::MemberFun, &foo));
Foo foo2(3);
Thread t4(boost::bind(&Foo::MemberFun2, &foo2, 1000));

t1.Start();
t2.Start();
t3.Start();
t4.Start();

t1.Join();
t2.Join();
t3.Join();
t4.Join();


return 0;
}

2.Thead.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#ifndef _THREAD_H_
#define _THREAD_H_

#include <pthread.h>
#include <boost/function.hpp>

class Thread
{
public:
typedef boost::function<void ()> ThreadFunc;
explicit Thread(const ThreadFunc& func);

void Start();
void Join();

void SetAutoDelete(bool autoDelete);

private:
static void* ThreadRoutine(void* arg);
void Run();
ThreadFunc func_;
pthread_t threadId_;
bool autoDelete_;
};

#endif // _THREAD_H_

3.Thread.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include "Thread.h"
#include <iostream>
using namespace std;


Thread::Thread(const ThreadFunc& func) : func_(func), autoDelete_(false)
{
}

void Thread::Start()
{
pthread_create(&threadId_, NULL, ThreadRoutine, this);
}

void Thread::Join()
{
pthread_join(threadId_, NULL);
}

void* Thread::ThreadRoutine(void* arg)
{
Thread* thread = static_cast<Thread*>(arg);
thread->Run();
if (thread->autoDelete_)
delete thread;
return NULL;
}

void Thread::SetAutoDelete(bool autoDelete)
{
autoDelete_ = autoDelete;
}

void Thread::Run()
{
func_();
}

2020年6月3日 上午8:33

总结:

基于对象的方式,能够将新建线程相关的函数方法、全局变量封装在一个对象中,并且主线程main也可以通过这个类对象和新建线程交互

方式1:新建线程组等待主线程唤醒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <muduo/base/CountDownLatch.h>
#include <muduo/base/Thread.h>

#include <boost/bind.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <string>
#include <stdio.h>

using namespace muduo;
/*
利用基于对象的编程方式,将主线程和创建的线程组进行逻辑上的分离
*/
class Test
{
public:
// 第二层新线程组:在构造函数中启动创建线程,并启动
Test(int numThreads)
: latch_(1),
threads_(numThreads)
{
for (int i = 0; i < numThreads; ++i)
{
char name[32];
snprintf(name, sizeof name, "work thread %d", i);
threads_.push_back(new muduo::Thread(
boost::bind(&Test::threadFunc, this), muduo::string(name)));
}
for_each(threads_.begin(), threads_.end(), boost::bind(&Thread::start, _1));
}
// 第二层新线程组:用于主线程中唤醒新线程
void run()
{
latch_.countDown();
}

void joinAll()
{
for_each(threads_.begin(), threads_.end(), boost::bind(&Thread::join, _1));
}

private:
// 第二层新线程组:多线程的共同执行体,会发生锁的争抢
void threadFunc()
{
latch_.wait();
printf("tid=%d, %s started\n",
CurrentThread::tid(),
CurrentThread::name());

printf("tid=%d, %s stopped\n",
CurrentThread::tid(),
CurrentThread::name());
}
// 第二层新线程组:三个新线程需要的共同的全局变量
CountDownLatch latch_;
boost::ptr_vector<Thread> threads_;
};

int main()
{
printf("pid=%d, tid=%d\n", ::getpid(), CurrentThread::tid());
// 第一层主线程:启动三个新线程
Test t(3);
sleep(3);
printf("pid=%d, tid=%d %s running ...\n", ::getpid(), CurrentThread::tid(), CurrentThread::name());
// 第一层主线程:唤醒那三个线程
t.run();
t.joinAll();

printf("number of created threads %d\n", Thread::numCreated());
}

方式2:新建线程组完成功能后将主线程进行唤醒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <muduo/base/CountDownLatch.h>
#include <muduo/base/Thread.h>

#include <boost/bind.hpp>
#include <boost/ptr_container/ptr_vector.hpp>
#include <string>
#include <stdio.h>

using namespace muduo;

class Test
{
public:
Test(int numThreads)
: latch_(numThreads),
threads_(numThreads)
{
for (int i = 0; i < numThreads; ++i)
{
char name[32];
snprintf(name, sizeof name, "work thread %d", i);
threads_.push_back(new muduo::Thread(
boost::bind(&Test::threadFunc, this), muduo::string(name)));
}
for_each(threads_.begin(), threads_.end(), boost::bind(&muduo::Thread::start, _1));
}

void wait()
{
latch_.wait();
}

void joinAll()
{
for_each(threads_.begin(), threads_.end(), boost::bind(&Thread::join, _1));
}

private:

void threadFunc()
{
sleep(3);
latch_.countDown();
printf("tid=%d, %s started\n",
CurrentThread::tid(),
CurrentThread::name());



printf("tid=%d, %s stopped\n",
CurrentThread::tid(),
CurrentThread::name());
}

CountDownLatch latch_;
boost::ptr_vector<Thread> threads_;
};

int main()
{
printf("pid=%d, tid=%d\n", ::getpid(), CurrentThread::tid());
Test t(3);
t.wait();
printf("pid=%d, tid=%d %s running ...\n", ::getpid(), CurrentThread::tid(), CurrentThread::name());
t.joinAll();

printf("number of created threads %d\n", Thread::numCreated());
}

#b计算机基础/f_并发/java并发
2020年5月25日 上午10:26
有了互斥锁,为什么还要条件变量? - 简书

有了互斥锁,为什么还要条件变量?

  • 假如我们没有“条件变量”这个概念,如果一个线程要等待某个“自定义的条件”满足而继续执行,而这个条件只能由另一个线程来满足,比如 T1不断给一个全局变量 x +1, T2检测到x 大于100时,将x 置0,如果我们没有条件变量,则只通过互斥锁则可以有如下实现:
  • 这种实现下,就算 lock 空闲,thread2需要不断重复<加锁,判断,解锁>这个流程,会给系统带来不必要的开销。有没有一种办法让 thread2先被 block,等条件满足的时候再唤醒 thread2?这样 thread2 就不用不断进行重复的加解锁操作了?这就要用到条件变量了

条件变量为什么需要配合互斥锁来使用:

  • 在thread 1 call pthread_cond_wait() 的时刻到 thread 1真正进入 wait 状态时,是存在着时间差的。如果在这段时间差内 thread2 调用了 pthread_cond_signal() 那这个 signal 信号就丢失了。给 wait 加锁可以防止同时有另一个线程在 signal。

#b计算机基础/f_并发/java并发
2020年5月25日 上午10:26
有了互斥锁,为什么还要条件变量? - 简书

有了互斥锁,为什么还要条件变量?

  • 假如我们没有“条件变量”这个概念,如果一个线程要等待某个“自定义的条件”满足而继续执行,而这个条件只能由另一个线程来满足,比如 T1不断给一个全局变量 x +1, T2检测到x 大于100时,将x 置0,如果我们没有条件变量,则只通过互斥锁则可以有如下实现:
  • 这种实现下,就算 lock 空闲,thread2需要不断重复<加锁,判断,解锁>这个流程,会给系统带来不必要的开销。有没有一种办法让 thread2先被 block,等条件满足的时候再唤醒 thread2?这样 thread2 就不用不断进行重复的加解锁操作了?这就要用到条件变量了

条件变量为什么需要配合互斥锁来使用:

  • 在thread 1 call pthread_cond_wait() 的时刻到 thread 1真正进入 wait 状态时,是存在着时间差的。如果在这段时间差内 thread2 调用了 pthread_cond_signal() 那这个 signal 信号就丢失了。给 wait 加锁可以防止同时有另一个线程在 signal。

#b计算机基础/c_计算机系统/b_linux系统/补充

2020年5月24日 上午10:19
注:这篇内容抓住重点:执行的顺序、概念的定义的理解

BIOS

  1. Bootstrapping
    1. 拽着自己的靴子,将自己提起来
  2. 第一步:BIOS,Memory mapping:Real Molde
    1. https://wiki.osdev.org/Memory_Map_(x86)#Extended_BIOS_Data_Area_.28EBDA.29
    2. When a typical x86 PC boots it will be in Real Mode , with an active BIOS
    3. When the IVT is activated by an IRQ , it will call a BIOS routine to handle the IRQ.
    4. After all the BIOS functions have been called, and your kernel is loaded into memory somewhere, the bootloader or kernel may exit Real Mode forever
  3. 第二步:Find MBR Sector
    1. Load system into RAM:
      1. your bootloader code is loaded and running in memory at physical addresses 0x7C00 through 0x7DFF
      2. So that memory area is likely to also be unusable until execution has been transferred to a second stage bootloader, or to your kernel.
    2. Boostloader vs MBR
      1. Your MBR (master boot record) is a physical location on your hard drive. GRUB (grand unified bootloader) is a bootloader that is frequently installed ON the MBR. You need a MBR and a bootloader of some sort
    3. Jump to the system code
  4. 第三步:Protected Model
    1. 教程 从零开始编写操作系统 第 0x01 课_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili
    2. cpu变更寻址方式:
      1. 在Real Mode下:
        1. segment register:0xfff0 + Offset << 4
      2. 在Protected Mode下:
        1. 0xffff -> Segment Descriptor -> Base Address -> (Privilege Check) BaseAddr + Offset -> Linear Address (Physical Address) -> page(可选)
      3. 为什么要使用分页代替段:
        1. 也能够更加精细的控制内存4k,进行读写等操作的控制,段的粒度太大
        2. 分页中,我们可以使用多级的页目录来吧页表的空间变得非常大
      4. segment register:
        1. 段寄存器存放的却不再是段基址
        2. 段寄存器在保护模式下存放的便是相当于一个数组索引的东西,通过这个索引,可以找到对应的GDT表项
      5. GDT:全局描述符表
        1. 段描述符存放了段基址、段界限、内存段类型属性(比如是数据段还是代码段,注意一个段描述符只能用来定义一个内存段)等许多属性
    3. CPU privilege rings:
      1. DPL:
        1. DPL存储在段描述符中,规定访问该段的权限级别(Descriptor Privilege Level),每个段的DPL固定
      2. RPL:
        1. RPL说明的是进程对段访问的请求权限(Request Privilege Level),是对于段选择子而言的,每个段选择子有自己的RPL,它说明的是进程对段访问的请求权限,有点像函数参数。而且RPL对每个段来说不是固定 的,两次访问同一段时的RPL可以不同。RPL可能会削弱CPL的作用,例如当前CPL=0的进程要访问一个数据段,它把段选择符中的RPL设为3,这样 虽然它对该段仍然只有特权为3的访问权限。
      3. CPL:
        1. CPL是当前进程的权限级别(Current Privilege Level),是当前正在执行的代码所在的段的特权级,存在于cs寄存器的低两位。
      4. 当进程访问一个段时,需要进程特权级检查,一般要求DPL >= max {CPL, RPL}
    4. Gain Privilege 和 security的角度:
      1. IDT:中断描述表
    5. Instructions & Register
      1. 指向描述符表的两个寄存器:
        1. GDTR:指向描述符,并且还存有描述符的大小
          1. LGDT(Load GDT)
        2. IDTR:指向描述符,并且还存有描述符的大小
          1. LIDT(Load IDT)
  5. segment mode:
    1. Flat Model :
      1. 用户数据段、用户代码段、内核程序段、内核代码段使用同一个地址空间
    2. Multi-Segment Model
      1. LDT TSS
    3. Page:分页
      1. 转帖为什么使用多级页表 - 济南小老虎 - 博客园
      2. 多级页表通过只为进程实际使用的那些虚拟地址内存区请求页表来减少内存使用量(出自《深入理解Linux内核》第三版51页)
        1. 举个例子:比如一个进程只是用4MB内存空间。对于以及页表,我们需要4M空间来存放页表,然后可以找到进程真正使用的4M内存空间。但是如果使用二级页表的话,一个页目录项可以定位4M内存空间,存放一个页目录项占4K,还需要一页用于存放进程使用的4M(4M=1024*4K,也就是用1024个页表项可以映射4M内存空间)内存空间对应的页表,总共需要4K+4K=8K来存放进程使用的这4M内存空间对应页表和页目录项,这比使用一级页表节省了很多内存空间。

上层应用:

  1. user space vs kernel space:
    1. User space vs kernel space · julia’s drawings
    2. 三种情况会导致用户态到内核态的切换
      1. 用户态和内核态的区别 - Gizing - 博客园
      2. 系统调用
      3. 异常
        1. 当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
      4. 外围设备的中断
        1. 当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,
      5. 总结:
        1. 这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。
  2. htop命令:
    1. priority value
      1. In linux system priorities are 0 to 139 in which 0 to 99 for real time and 100 to 139 for users.
    2. nice value
      1. nice value range is -20 to +19 where -20 is highest, 0 default and +19 is lowest.
    3. 转换公式:
      1. PR = 20 + NI
      2. the value of PR = 20 + (-20 to +19) is 0 to 39 that maps 100 to 139.
    4. linux指令设置nice value:
      1. A brief guide to priority and nice values in the linux ecosystem
      2. renice -n nice_val -p [pid]
      3. nice -n nice_val [command]
  3. time命令:
    1. real:程序从开始运行到结束的全部时间,这是用户能感知到的时间,包括 CPU 切换去执行其他任务的时间。
    2. user:程序在 User space 执行的时间
    3. sys:程序在 Kernel space 执行的时间
    4. 总结:
      1. user和sys之和,一般情况下,应该小于real。但如果是多核 CPU,这两个指标反映的是所有 CPU 的总耗时,所以它们之和可能大于real

参考文章:

User space 与 Kernel space - 阮一峰的网络日志
用户态和内核态的区别 - Gizing - 博客园
unix - What do ‘real’, ‘user’ and ‘sys’ mean in the output of time(1)? - Stack Overflow
User space vs kernel space · julia’s drawings
Understanding Linux CPU stats | Scout APM Blog
A brief guide to priority and nice values in the linux ecosystem
Process ‘niceness’ vs. ‘priority’ - Ask Ubuntu
CPU的实模式和保护模式(一) - 知乎
VOID001的个人空间 - 哔哩哔哩 ( ゜- ゜)つロ 乾杯~ Bilibili
教程 从零开始编写操作系统 第 0x01 课_哔哩哔哩 (゜-゜)つロ 干杯~-bilibili
保护模式特权级别DPL,RPL,CPL 之间的联系和区别 转 - 龙豆 - 博客园
x86 - what is the difference between IVT and IDT? - Stack Overflow
driver - What are Ring 0 and Ring 3 in the context of operating systems? - Stack Overflow
转帖为什么使用多级页表 - 济南小老虎 - 博客园
将操作系统分解,看看这个庞大的系统中包含哪些子系统
Memory Map (x86) - OSDev Wiki
what-is-the-difference-between-mbr-and-grub

2020年5月23日 下午12:09

单例模式的思路:

  1. 从需求的角度:
    1. 我们需要一个单独的方法来判断当前Singleton类是否有已有对象存在,如果存在就直接返回对象
      1. 注意:这里返回的必须是指向对象的指针,不能生成临时对象
      2. Singleton* getInstance()
    2. 这个好像很容易实现,但是如果我们为了安全要求用户不能使用构造函数
      1. private:Singleton(){};
    3. 在1,2这两个需求下,我们只能使用静态static成员方法和变量,才可能同时做到1,2,并且静态成员方法只能操作静态变量,所以我们要
      1. static Singleton* This;
      2. static const Singleton* getInstance()
  2. 两种模式的区别:
    1. C++ 线程安全的单例模式总结 - 掘金
    2. 单例模式可以分为懒汉式饿汉式,两者之间的区别在于创建实例的时间不同
      1. 懒汉式:指系统运行中,实例并不存在,只有当需要使用该实例时,才会去创建并使用实例。(这种方式要考虑线程安全)
      2. 饿汉式:指系统一运行,就初始化创建实例,当需要时,直接调用即可。(本身就线程安全,没有多线程的问题)
        1. Singleton* Singleton::This = new Singleton;

附录代码:懒汉模式,非线程安全

1
2
3
4
5
6
7
8
int main()
{
Singleton::getInstance()->DoSomething();

Singleton::getInstance()->DoSomething();

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#pragma once

#include <iostream>
using namespace std;
class Singleton
{
public:
static const Singleton* getInstance();
static void DoSomething()
{
cout << "Do Something" << endl;
}
// 将构造和析构函数私有化,防止外部访问
private:
Singleton(){};
~Singleton(){};

static Singleton* This; // 使用静态变量帮助解决资源的分配和释放
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "stdafx.h"
#include "Singleton.h"

Singleton* Singleton::This = nullptr;
const Singleton* Singleton::getInstance()
{
if (!This)
{
This = new Singleton();
}
return This;
}
//
//Singleton::Singleton()
//{
//}
//
//Singleton::~Singleton()
//{
//}

2020年5月22日 下午9:27

volatile:

  1. C++:
    1. 使用volatile声明变量,可以理解成把cpu和主内存之间的cache去掉了,读写都直接操作主内存
  2. Java:Java的内存屏障放在了volatile中实现
    1. 对volatile变量写操作时,会在写操作加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存。
    2. 对volatile变量读操作时,会在读操作加入一条load屏障指令,从主内存中读取共享变量。

C++原子量:

  1. c++中着重强调了原子量的操作,以及原子量和mutex互斥量,一定要明白他们两得职责是不一样的
  2. 原子量的实现是靠cas操作来保证,cas其实就是redis锁.从名字上也可以看出来所谓原子量,他的操作对象是一个对象,保证了对这个对象读写的时候得原子性,即不会出现银行存取款业务类似的问题,葱这个例子中可以看出,其实仅仅实现操作的原子性使用mutex也可以代替,只不过mutex也是使用redis锁来实现,没那么直接,多封装了一层而已
  3. 原子量中内存序的作用:
    1. 原子量的操作,除了简单的保证读写当前对象得原子性,在c++中还给原子量的读写加入了内存屏障的功能,这就从屏障这个词中我们就可以看出一些端倪,相当于原先一个班的同学在一起跑来跑去得玩闹,所有的人都可以随意的找其他同学玩,这个过程相当于编译器对执行顺序进行优化,
    2. 但是这是老师在班级中加入了一个栅栏,老师希望好学生和好学生玩,在好学生之间可以随意的组合,但你坏学生不能来影响好学生,其实就是粒度更加细得分组,原先得组别唯一,现在可以为2,当然在极端情况下每个人成为一个小组,这样任何人都不会影响其他人,也不会被其他人影响。
  4. 刚刚讨论的是分组,在分组之外还有一个就是各个组别的执行顺序,刚刚学生的例子就不好表述了。
    1. 计算机redis锁的本质是解决线程唯一资源的争抢问题,它只能保证有且只有唯一的一个人获取资源,不是用来解决多个线程的执行顺序问题,就需要我们程序员给线程执行加入if条件,只有满足什么条件才能继续执行内部的代码
    2. 什么是锁、互斥量、原子操作解决不了的并发问题

总结多线程编程的造成问题的原因 与 解决方法

  1. 从问题原因下手:
    1. 如果由于编译器指令重排,我们可以使用内存屏障来解决部分重拍问题,能够保证不因为重排造成程序异常执行就行
    2. 如果我们需要多个线程进行资源的争夺,这时候就要想到加锁,加什么锁
    3. 如果我们需要规定线程得执行顺序,对不起,这个问题的程序员你分析问题,加入可执行的条件,并使用上面得准则,判断是否会被指令重排,线程交差执行影响,如果有,使用上面的方法解决,再加入if判断即可

2020年5月22日 下午4:19

重新认识“锁”这个字:这点非常非常重要

  1. x锁 == 争抢锁的x规则:
    1. 我们这里涉及到悲观锁、乐观锁、自旋锁、适应性自旋锁、无锁、偏向锁、轻量级锁、重量级锁、公平锁、非公平锁、可重入锁、不可重入锁、共享锁、排他锁这十几种锁
    2. 什么什么锁,翻译过来就是:我们目前按什么什么规则进行争抢锁

锁知识的系统讲解与分类

不可不说的Java“锁”事 - 美团技术团队

synchronized锁的原理:这个问题要专门拉出来讲,考的人太多

  1. 涉及到的数据结构:
    1. 对象头中的:Mark Word(标记字段)Klass Pointer(类型指针)
    2. Monitor:
      1. Monitor可以理解为一个同步工具或一种同步机制。说白了就是synchronized是调用Monitor实现的
      2. synchronized通过Monitor来实现线程同步,Monitor是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的线程同步
  2. synchronized最初实现同步的方式
    1. 阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长

最最关键、基础的两种锁:

  1. 【极端】大家来抢,但只有一个人获得操作对象,不论这个人对这个对象进行何种操作,别人都无法获得这个对象,看看都不行
  2. 【平和】大家都可以来看到这个对象,但是有一点需要大家保证:你看看可以,一旦你想改这个对象,你必须满足一定的要求:
    1. 这里面又设计了两种场景需求
      1. 面试必备之乐观锁与悲观锁 - 掘金
      2. 外加数据的版本号手段:
        1. 在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功
      3. 不加版本号,直接根据数据值本身是否变更:
        1. CAS:compare and swap(比较与交换)
        2. 但是:
          1. 会导致ABA 问题,解决ABA我们又要使用版本号
          2. 循环时间长开销大
            1. 自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。
          3. 只能保证一个共享变量的原子操作
            1. CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效
  3. 上面是从人话的角度去切入真实的场景,计算机只不过在模拟我们日常的需求而已。但是,如果我们专业一点说的话:
    1. 极端 ==> 悲观锁
    2. 平和 ==> 乐观锁
  4. 当我们需要在多线程下保护一个对象时,我们如何选择保护它的模式?
    1. 这个可以理解成第三方保镖公司,这个公司有自己的套餐服务,你可以选择你保护的这个人的等级:有皇家护卫队等级,有退役海报突击队等级,有配枪的不配枪的….
    2. 这就等同于上面所说的极端、平和等等模式,不同的人我们选择不同的保护等级,保护领导人当然直接上皇家护卫队等级等级,保护你个平民当然随便请个健身教练就行了,这里强调的是适合!
    3. 这就涉及到乐观锁和悲观锁的使用场景:
      1. 具体来说:CAS与synchronized的使用情景
      2. 简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)