文章链接:http://www.hcoding.com/?p=121

  个人站点:JC&hcoding.com

  memcached是什么呢?memcached是一个优秀的、高性能的内存缓存工具。

  memcached具有以下的特点:

  • 协议简单:memcached的服务器客户端通信并不使用复杂的MXL等格式,而是使用简单的基于文本的协议。
  • 基于libevent的事件处理:libevent是个程序库,他将Linux 的epoll、BSD类操作系统的kqueue等时间处理功能封装成统一的接口。memcached使用这个libevent库,因此能在Linux、BSD、Solaris等操作系统上发挥其高性能。(libevent是什么)
  • 内置内存存储方式:为了提高性能,memcached中保存的数据都存储在memcached内置的内存存储空间中。由于数据仅存在于内存中,因此重启memcached,重启操作系统会导致全部数据消失。另外,内容容量达到指定的值之后memcached回自动删除不适用的缓存。
  • Memcached不互通信的分布式:memcached尽管是“分布式”缓存服务器,但服务器端并没有分布式功能。各个memcached不会互相通信以共享信息。他的分布式主要是通过客户端实现的。

  本文主要讲解memcached的连接模型,memcached由一条主线程(连接线程)监听连接,然后把成功的连接交给子线程(工作线程)处理读写操作。N条【启动memcached通过-t命令指定】子线程(工作线程)负责读写数据,一条子线程(工作线程)维护着多个连接。一个conn结构体对象对应着一个连接,主线程(连接线程)成功连接后,会把连接的内容赋值到一个conn结构体对象,并把这个conn结构体对象传递给一条子线程(工作线程)处理。

conn结构体:

  1. typedef struct conn conn;
  2. struct conn {
  3. int sfd;
  4. sasl_conn_t *sasl_conn;
  5.  
  6. // 连接状态
  7. enum conn_states state;
  8. enum bin_substates substate;
  9. struct event event;
  10. short ev_flags;
  11.  
  12. // 刚刚出发的事件
  13. short which; /** which events were just triggered */
  14.  
  15. // read buffer
  16. char *rbuf; /** buffer to read commands into */
  17.  
  18. // 已经解析了一部分的命令, 指向已经解析结束的地方
  19. char *rcurr; /** but if we parsed some already, this is where we stopped */
  20.  
  21. // rbuf 已分配的大小
  22. int rsize; /** total allocated size of rbuf */
  23.  
  24. // 尚未解析的命令大小
  25. int rbytes; /** how much data, starting from rcur, do we have unparsed */
  26.  
  27. // buffer to write
  28. char *wbuf;
  29.  
  30. // 指向已经返回的地方
  31. char *wcurr;
  32.  
  33. // 写大小
  34. int wsize;
  35.  
  36. // 尚未写的数据大小
  37. int wbytes;
  38.  
  39. /** which state to go into after finishing current write */
  40. // 当写回结束后需要即刻转变的状态
  41. enum conn_states write_and_go;
  42.  
  43. void *write_and_free; /** free this memory after finishing writing */
  44.  
  45. char *ritem; /** when we read in an item's value, it goes here */
  46. int rlbytes;
  47.  
  48. /* data for the nread state */
  49.  
  50. /**
  51. * item is used to hold an item structure created after reading the command
  52. * line of set/add/replace commands, but before we finished reading the actual
  53. * data. The data is read into ITEM_data(item) to avoid extra copying.
  54. */
  55.  
  56. // 指向当下需要完成的任务
  57. void *item; /* for commands set/add/replace */
  58.  
  59. /* data for the swallow state */
  60. int sbytes; /* how many bytes to swallow */
  61.  
  62. /* data for the mwrite state */
  63. struct iovec *iov;
  64. int iovsize; /* number of elements allocated in iov[] */
  65. int iovused; /* number of elements used in iov[] */
  66.  
  67. // msghdr 链表, 一个连接可能有多个 msghdr
  68. // 如果是 UDP, 需要为每一个 msghdr 填写一个 UDP 头部
  69. struct msghdr *msglist;
  70. int msgsize; /* number of elements allocated in msglist[] */
  71. int msgused; /* number of elements used in msglist[] */
  72. int msgcurr; /* element in msglist[] being transmitted now */
  73. int msgbytes; /* number of bytes in current msg */
  74.  
  75. item **ilist; /* list of items to write out */
  76. int isize;
  77. item **icurr;
  78.  
  79. // 记录任务数量
  80. int ileft;
  81.  
  82. char **suffixlist;
  83. int suffixsize;
  84. char **suffixcurr;
  85. int suffixleft;
  86.  
  87. enum protocol protocol; /* which protocol this connection speaks */
  88. enum network_transport transport; /* what transport is used by this connection */
  89.  
  90. /* data for UDP clients */
  91. int request_id; /* Incoming UDP request ID, if this is a UDP "connection" */
  92. struct sockaddr request_addr; /* Who sent the most recent request */
  93. socklen_t request_addr_size;
  94.  
  95. unsigned char *hdrbuf; /* udp packet headers */
  96. int hdrsize; /* number of headers' worth of space is allocated */
  97.  
  98. bool noreply; /* True if the reply should not be sent. */
  99. /* current stats command */
  100. struct {
  101. char *buffer;
  102. size_t size;
  103. size_t offset;
  104. } stats;
  105.  
  106. /* Binary protocol stuff */
  107. /* This is where the binary header goes */
  108. protocol_binary_request_header binary_header;
  109. uint64_t cas; /* the cas to return */
  110. short cmd; /* current command being processed */
  111.  
  112. // ? 不透明
  113. int opaque;
  114. int keylen;
  115.  
  116. // 可见是一个链表
  117. conn *next; /* Used for generating a list of conn structures */
  118.  
  119. // 指向服务于此连接的线程
  120. LIBEVENT_THREAD *thread; /* Pointer to the thread object serving this connection */
  121. };
  1. //memcached.c
  2. int main{
  3.  
  4. // ......
  5.  
  6. // 第一步:初始化主线程的事件机制
  7. /* initialize main thread libevent instance */
  8. // libevent 事件机制初始化
  9. main_base = event_init();
  10.  
  11. // ......
  12.  
  13. // 第二步:初始化 N 个 (初始值200,当连接超过200个的时候会往上递增) conn结构体对象
  14. // 空闲连接数组初始化
  15. conn_init();
  16.  
  17. // ......
  18.  
  19. // 第三步:启动工作线程
  20. /* start up worker threads if MT mode */
  21. thread_init(settings.num_threads, main_base);
  22.  
  23. // ......
  24.  
  25. // 第四步:初始化socket,绑定监听端口,为主线程的事件机制设置连接监听事件(event_set、event_add)
  26. /**
  27. memcached 有可配置的两种模式: unix 域套接字和 TCP/UDP, 允许客户端以两种方式向 memcached 发起请求. 客户端和服务器在同一个主机上的情况下可以用 unix 域套接字, 否则可以采用 TCP/UDP 的模式. 两种模式是不兼容的.
  28. 以下的代码便是根据 settings.socketpath 的值来决定启用哪种方式.
  29. */
  30. /**
  31. 第一种, unix 域套接字.
  32. */
  33. /* create unix mode sockets after dropping privileges */
  34. if (settings.socketpath != NULL) {
  35. errno = ;
  36. if (server_socket_unix(settings.socketpath,settings.access)) {
  37. vperror("failed to listen on UNIX socket: %s", settings.socketpath);
  38. exit(EX_OSERR);
  39. }
  40. }
  41.  
  42. /**
  43. 第二种, TCP/UDP.
  44. */
  45. /* create the listening socket, bind it, and init */
  46. if (settings.socketpath == NULL) {
  47. const char *portnumber_filename = getenv("MEMCACHED_PORT_FILENAME");
  48. char temp_portnumber_filename[PATH_MAX];
  49. FILE *portnumber_file = NULL;
  50.  
  51. // 读取端口号文件
  52. if (portnumber_filename != NULL) {
  53. snprintf(temp_portnumber_filename,
  54. sizeof(temp_portnumber_filename),
  55. "%s.lck", portnumber_filename);
  56.  
  57. portnumber_file = fopen(temp_portnumber_filename, "a");
  58. if (portnumber_file == NULL) {
  59. fprintf(stderr, "Failed to open \"%s\": %s\n",
  60. temp_portnumber_filename, strerror(errno));
  61. }
  62. }
  63.  
  64. // TCP
  65. errno = ;
  66. if (settings.port && server_sockets(settings.port, tcp_transport,
  67. portnumber_file)) {
  68. vperror("failed to listen on TCP port %d", settings.port);
  69. exit(EX_OSERR);
  70. }
  71.  
  72. /*
  73. * initialization order: first create the listening sockets
  74. * (may need root on low ports), then drop root if needed,
  75. * then daemonise if needed, then init libevent (in some cases
  76. * descriptors created by libevent wouldn't survive forking).
  77. */
  78.  
  79. // UDP
  80. /* create the UDP listening socket and bind it */
  81. errno = ;
  82. if (settings.udpport && server_sockets(settings.udpport, udp_transport,
  83. portnumber_file)) {
  84. vperror("failed to listen on UDP port %d", settings.udpport);
  85. exit(EX_OSERR);
  86. }
  87.  
  88. if (portnumber_file) {
  89. fclose(portnumber_file);
  90. rename(temp_portnumber_filename, portnumber_filename);
  91. }
  92. }
  93.  
  94. // ......
  95.  
  96. // 第五步:主线程进入事件循环
  97. /* enter the event loop */
  98. // 进入事件循环
  99. if (event_base_loop(main_base, ) != ) {
  100. retval = EXIT_FAILURE;
  101. }
  102.  
  103. // ......
  104.  
  105. }

  LIBEVENT_THREAD 结构体:

  1. // 多个线程, 每个线程一个 event_base
  2. typedef struct {
  3. pthread_t thread_id; /* unique ID of this thread */
  4. struct event_base *base; /* libevent handle this thread uses */
  5.  
  6. // event 结构体, 用于管道读写事件的监听
  7. struct event notify_event; /* listen event for notify pipe */
  8.  
  9. // 读写管道文件描述符
  10. int notify_receive_fd; /* receiving end of notify pipe */
  11. int notify_send_fd; /* sending end of notify pipe */
  12.  
  13. // 线程的状态
  14. struct thread_stats stats; /* Stats generated by this thread */
  15.  
  16. // 这个线程需要处理的连接队列
  17. struct conn_queue *new_conn_queue; /* queue of new connections to handle */
  18. cache_t *suffix_cache; /* suffix cache */
  19. uint8_t item_lock_type; /* use fine-grained or global item lock */
  20. } LIBEVENT_THREAD;

  第三步工作线程的详细启动过程:

  1. /*
  2. * thread.c
  3. *
  4. * 初始化线程子系统, 创建工作线程
  5. * Initializes the thread subsystem, creating various worker threads.
  6. *
  7. * nthreads Number of worker event handler threads to spawn
  8. * 需准备的线程数
  9. * main_base Event base for main thread
  10. * 分发线程
  11. */
  12. void thread_init(int nthreads, struct event_base *main_base) {
  13. int i;
  14. int power;
  15.  
  16. // 互斥量初始化
  17. pthread_mutex_init(&cache_lock, NULL);
  18. pthread_mutex_init(&stats_lock, NULL);
  19.  
  20. pthread_mutex_init(&init_lock, NULL);
  21. //条件同步
  22. pthread_cond_init(&init_cond, NULL);
  23.  
  24. pthread_mutex_init(&cqi_freelist_lock, NULL);
  25. cqi_freelist = NULL;
  26.  
  27. /* Want a wide lock table, but don't waste memory */
  28. if (nthreads < ) {
  29. power = ;
  30. } else if (nthreads < ) {
  31. power = ;
  32. } else if (nthreads < ) {
  33. power = ;
  34. } else {
  35. // 2^13
  36. /* 8192 buckets, and central locks don't scale much past 5 threads */
  37. power = ;
  38. }
  39.  
  40. // hashsize = 2^n
  41. item_lock_count = hashsize(power);
  42.  
  43. item_locks = calloc(item_lock_count, sizeof(pthread_mutex_t));
  44. if (! item_locks) {
  45. perror("Can't allocate item locks");
  46. exit();
  47. }
  48. // 初始化
  49. for (i = ; i < item_lock_count; i++) {
  50. pthread_mutex_init(&item_locks[i], NULL);
  51. }
  52. //item_lock_type_key设置为线程的私有变量的key
  53. pthread_key_create(&item_lock_type_key, NULL);
  54. pthread_mutex_init(&item_global_lock, NULL);
  55.  
  56. // LIBEVENT_THREAD 是结合 libevent 使用的结构体, event_base, 读写管道
  57. threads = calloc(nthreads, sizeof(LIBEVENT_THREAD));
  58. if (! threads) {
  59. perror("Can't allocate thread descriptors");
  60. exit();
  61. }
  62.  
  63. // main_base 是分发任务的线程, 即主线程
  64. dispatcher_thread.base = main_base;
  65. dispatcher_thread.thread_id = pthread_self();
  66.  
  67. // 管道, libevent 通知用的
  68. // 一个 LIBEVENT_THREAD 结构体对象对应由一条子线程维护
  69. // 子线程通过读管道来接收主线程的命令(例如主线程接收到新连接,会往子线程的读管道写入字符'c',子线程接收到命令就会做出相应的处理)
  70. for (i = ; i < nthreads; i++) {
  71. int fds[];
  72. if (pipe(fds)) {
  73. perror("Can't create notify pipe");
  74. exit();
  75. }
  76.  
  77. // 读管道
  78. threads[i].notify_receive_fd = fds[];
  79. // 写管道
  80. threads[i].notify_send_fd = fds[];
  81.  
  82. // 初始化线程信息数据结构, 其中就将 event 结构体的回调函数设置为 thread_libevent_process(),此时线程还没有创建
  83. setup_thread(&threads[i]);
  84. /* Reserve three fds for the libevent base, and two for the pipe */
  85. stats.reserved_fds += ;
  86. }
  87.  
  88. /* Create threads after we've done all the libevent setup. */
  89. // 创建并初始化线程, 线程的代码都是 work_libevent()
  90. for (i = ; i < nthreads; i++) {
  91. // 调用 pthread_attr_init() 和 pthread_create() 来创建子线程
  92. // 子线程的函数入口 worker_libevent ,负责启动子线程的事件循环
  93. create_worker(worker_libevent, &threads[i]);
  94. }
  95.  
  96. /* Wait for all the threads to set themselves up before returning. */
  97. pthread_mutex_lock(&init_lock);
  98. // wait_for_thread_registration() 是 pthread_cond_wait 的调用
  99. wait_for_thread_registration(nthreads);
  100. pthread_mutex_unlock(&init_lock);
  101. }
  102.  
  103. /*
  104. * Set up a thread's information.
  105. */
  106. // 填充 LIBEVENT_THREAD 结构体, 其中包括:
  107. // 填充 struct event
  108. // 初始化线程工作队列
  109. // 初始化互斥量
  110. // 等
  111. static void setup_thread(LIBEVENT_THREAD *me) {
  112. // 子线程的事件机制,每条子线程都有一个事件机制
  113. me->base = event_init();
  114. if (! me->base) {
  115. fprintf(stderr, "Can't allocate event base\n");
  116. exit();
  117. }
  118.  
  119. /* Listen for notifications from other threads */
  120. // 在线程数据结构初始化的时候, 为 me->notify_receive_fd 读管道注册读事件, 回调函数是 thread_libevent_process()
  121. // 为子线程的事件机制添加事件
  122. event_set(&me->notify_event, me->notify_receive_fd,
  123. EV_READ | EV_PERSIST, thread_libevent_process, me);
  124. event_base_set(me->base, &me->notify_event);
  125.  
  126. if (event_add(&me->notify_event, ) == -) {
  127. fprintf(stderr, "Can't monitor libevent notify pipe\n");
  128. exit();
  129. }
  130.  
  131. // ......
  132. }
  133.  
  134. /*
  135. * Worker thread: main event loop
  136. * 线程函数入口, 启动事件循环
  137. */
  138. static void *worker_libevent(void *arg) {
  139. LIBEVENT_THREAD *me = arg;
  140.  
  141. // ......
  142.  
  143. // 进入事件循环
  144. event_base_loop(me->base, );
  145. return NULL;
  146. }

  子线程读管道回调函数:

  1. /*
  2. * Processes an incoming "handle a new connection" item. This is called when
  3. * input arrives on the libevent wakeup pipe.
  4. *
  5. * 当管道有数据可读的时候会触发此函数的调用
  6. */
  7. static void thread_libevent_process(int fd, short which, void *arg) {
  8. LIBEVENT_THREAD *me = arg;
  9. CQ_ITEM *item;
  10. char buf[];
  11.  
  12. if (read(fd, buf, ) != )
  13. if (settings.verbose > )
  14. fprintf(stderr, "Can't read from libevent pipe\n");
  15.  
  16. switch (buf[]) {
  17. case 'c':
  18. // 表示主线程把一个新的连接分发给该子线程处理
  19. // 取出一个任务
  20. item = cq_pop(me->new_conn_queue);
  21.  
  22. if (NULL != item) {
  23. // 为新的请求建立一个连接结构体. 连接其实已经建立, 这里只是为了填充连接结构体. 最关键的动作是在 libevent 中注册了事件, 回调函数是 event_handler()
  24. conn *c = conn_new(item->sfd, item->init_state, item->event_flags,
  25. item->read_buffer_size, item->transport, me->base);
  26. if (c == NULL) {
  27. if (IS_UDP(item->transport)) {
  28. fprintf(stderr, "Can't listen for events on UDP socket\n");
  29. exit();
  30. } else {
  31. if (settings.verbose > ) {
  32. fprintf(stderr, "Can't listen for events on fd %d\n",
  33. item->sfd);
  34. }
  35. close(item->sfd);
  36. }
  37. } else {
  38. c->thread = me;
  39. }
  40. cqi_free(item);
  41. }
  42. break;
  43.  
  44. /* we were told to flip the lock type and report in */
  45. case 'l':
  46. me->item_lock_type = ITEM_LOCK_GRANULAR;
  47. register_thread_initialized();
  48. break;
  49.  
  50. case 'g':
  51. me->item_lock_type = ITEM_LOCK_GLOBAL;
  52. register_thread_initialized();
  53. break;
  54. }
  55. }

  第四步主要是初始化socket、绑定服务器端口和IP、为主线程事件机制添加监听连接事件:

  1. // memcached.c
  2. // server_sockets()->server_socket()
  3.  
  4. static int server_socket(const char *interface,
  5. int port,
  6. enum network_transport transport,
  7. FILE *portnumber_file) {
  8.  
  9. // ......
  10.  
  11. // getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个addrinfo的结构(列表)指针而不是一个地址清单。
  12. error= getaddrinfo(interface, port_buf, &hints, &ai);
  13.  
  14. if (error != ) {
  15. if (error != EAI_SYSTEM)
  16. fprintf(stderr, "getaddrinfo(): %s\n", gai_strerror(error));
  17. else
  18. perror("getaddrinfo()");
  19. return ;
  20. }
  21.  
  22. for (next= ai; next; next= next->ai_next) {
  23. conn *listen_conn_add;
  24.  
  25. // new_socket() 申请了一个 UNIX 域套接字,通过调用socket()方法创建套接字,并设置把套接字为非阻塞
  26. if ((sfd = new_socket(next)) == -) {
  27.  
  28. // ......
  29.  
  30. }// if
  31.  
  32. // ......
  33.  
  34. // bind() 绑定源IP的端口
  35. if (bind(sfd, next->ai_addr, next->ai_addrlen) == -) {
  36.  
  37. // ......
  38.  
  39. } else {
  40. success++;
  41. // bind()调用成功后,调用listen()
  42. if (!IS_UDP(transport) && listen(sfd, settings.backlog) == -) {
  43.  
  44. // ......
  45.  
  46. }
  47.  
  48. // ......
  49.  
  50. }
  51.  
  52. // UDP 和 TCP 区分对待, UDP 没有连接概念, 只要绑定服务器之后, 直接读取 socket 就好了, 所以与它对应 conn 的初始状态应该为 conn_read; 而 TCP 对应的 conn 初始状态应该为 conn_listening
  53. if (IS_UDP(transport)) {
  54. // UDP
  55. int c;
  56.  
  57. for (c = ; c < settings.num_threads_per_udp; c++) {
  58. /* this is guaranteed to hit all threads because we round-robin */
  59. // 分发新的连接到线程池中的一个线程中
  60. dispatch_conn_new(sfd, conn_read, EV_READ | EV_PERSIST,
  61. UDP_READ_BUFFER_SIZE, transport);
  62. }
  63. } else {
  64. // TCP 要建立连接
  65. if (!(listen_conn_add = conn_new(sfd, conn_listening,
  66. EV_READ | EV_PERSIST, ,
  67. transport, main_base))) {
  68. fprintf(stderr, "failed to create listening connection\n");
  69. exit(EXIT_FAILURE);
  70. }
  71.  
  72. // 放在头部, listen_conn 是头指针
  73. listen_conn_add->next = listen_conn;
  74. listen_conn = listen_conn_add;
  75. }
  76. }
  77.  
  78. freeaddrinfo(ai);
  79.  
  80. /* Return zero iff we detected no errors in starting up connections */
  81. return success == ;
  82. }
  83.  
  84. // 填写 struct conn 结构体, 包括 struct conn 中的 event 结构, 并返回
  85. conn *conn_new(const int sfd, enum conn_states init_state,
  86. const int event_flags,
  87. const int read_buffer_size, enum network_transport transport,
  88. struct event_base *base) {
  89. // c 指向一个新的 conn 空间
  90. // 可能是出于性能的考虑, memcached 预分配了若干个 struct conn 空间
  91. {
  92. /* data */
  93. };
  94. conn *c = conn_from_freelist();
  95.  
  96. if (NULL == c) {
  97. // 可能分配失败了, 因为默认数量有限. 进行新的扩展,conn_init()中初始数量是200
  98. if (!(c = (conn *)calloc(, sizeof(conn)))) {
  99. fprintf(stderr, "calloc()\n");
  100. return NULL;
  101. }
  102.  
  103. // ......
  104. // 填充conn结构体
  105.  
  106. }// if
  107.  
  108. // ......
  109.  
  110. // libevent 操作: 设置事件, 设置回调函数 event_handler()
  111. event_set(&c->event, sfd, event_flags, event_handler, (void *)c);
  112.  
  113. // libevent 操作:设置 c->event 的 event_base
  114. event_base_set(base, &c->event);
  115.  
  116. c->ev_flags = event_flags;
  117.  
  118. // libevent 操作: 添加事件
  119. if (event_add(&c->event, ) == -) {
  120.  
  121. // ......
  122.  
  123. }
  124.  
  125. // ......
  126.  
  127. return c;
  128. }

  

memcached学习笔记——连接模型的更多相关文章

  1. memcached学习笔记——存储命令源码分析上篇

    原创文章,转载请标明,谢谢. 上一篇分析过memcached的连接模型,了解memcached是如何高效处理客户端连接,这一篇分析memcached源码中的process_update_command ...

  2. memcached学习笔记——存储命令源码分析下篇

    上一篇回顾:<memcached学习笔记——存储命令源码分析上篇>通过分析memcached的存储命令源码的过程,了解了memcached如何解析文本命令和mencached的内存管理机制 ...

  3. Memcached 学习笔记(二)——ruby调用

    Memcached 学习笔记(二)——ruby调用 上一节我们讲述了怎样安装memcached及memcached常用命令.这一节我们将通过ruby来调用memcached相关操作. 第一步,安装ru ...

  4. ArcGIS案例学习笔记2_2_模型构建器和山顶点提取批处理

    ArcGIS案例学习笔记2_2_模型构建器和山顶点提取批处理 计划时间:第二天下午 背景:数据量大,工程大 目的:自动化,批处理,定制业务流程,不写程序 教程:Pdf/343 数据:chap8/ex5 ...

  5. Django:学习笔记(7)——模型进阶

    Django:学习笔记(7)——模型进阶 模型的继承 我们在面向对象的编程中,一个很重要的的版块,就是类的继承.父类保存了所有子类共有的内容,子类通过继承它来减少冗余代码并进行灵活扩展. 在Djang ...

  6. Django:学习笔记(6)——模型

    Django:学习笔记(6)——模型 快速上手 模型到底是什么呢?我们可以想,如果一张数据表的各个字段可以自动映射到一个类的各个属性,则每条记录对应这个类的一个对象.那我们通过类方法来操作对象(即表记 ...

  7. JVM学习笔记——内存模型篇

    JVM学习笔记--内存模型篇 在本系列内容中我们会对JVM做一个系统的学习,本片将会介绍JVM的内存模型部分 我们会分为以下几部分进行介绍: 内存模型 乐观锁与悲观锁 synchronized优化 内 ...

  8. JUC学习笔记——共享模型之管程

    JUC学习笔记--共享模型之管程 在本系列内容中我们会对JUC做一个系统的学习,本片将会介绍JUC的管程部分 我们会分为以下几部分进行介绍: 共享问题 共享问题解决方案 线程安全分析 Monitor ...

  9. JUC学习笔记——共享模型之内存

    JUC学习笔记--共享模型之内存 在本系列内容中我们会对JUC做一个系统的学习,本片将会介绍JUC的内存部分 我们会分为以下几部分进行介绍: Java内存模型 可见性 模式之两阶段终止 模式之Balk ...

随机推荐

  1. Web开发-各状态码的意思

    常见的HTTP 1.1状态码以及它们对应的状态信息和含义 100 Continue 初始的请求已经接受,客户应当继续发送请求的其余部分.(HTTP 1.1新) 101 Switching Protoc ...

  2. git操作的各种命令整理

    1.常用的Git命令   命令 简要说明 git add 添加至暂存区 git add–interactive 交互式添加 git apply 应用补丁 git am 应用邮件格式补丁 git ann ...

  3. AE-模板替换->愉快今日--视频样片!

  4. shell脚本实例一,移动文件夹中大于2000B的文件到另一个文件夹

    shell脚本能帮我们简化linux下的一些工作,现在有个需求,把TMPA文件夹下大于2000B的文件都移动到TMPB下 #! /bin/bash function movefiles() { ` d ...

  5. Android之GPS应用开发

    LocationManager--------------->Context.LOCATION_SERVICE LocationProvider--------------->Locati ...

  6. 完整的开发一个ContentProvider步骤

    1.定义自己的ContentProvider类,该类需要继承Android提供的ContentProvider基类.2.向Android系统注册这个"网站",也就是在Android ...

  7. Local System/Network Service/Local Service

    // The name of the account under which the service should run// 1 NT AUTHORITY\\SYSTEM 2 NT AUTHORIT ...

  8. 几种开源SIP协议栈对比OPAL,VOCAL,sipX,ReSIProcate,oSIP

    随着VoIP和NGN技术的发展,H.323时代即将过渡到SIP时代,在H.323的开源协议栈中,Openh323占统治地位,它把一个复杂而又先进 的H.323协议栈展现在普通程序员的眼前,为H.323 ...

  9. bzoj2741【FOTILE模拟赛】L

    http://www.lydsy.com/JudgeOnline/problem.php?id=2741 分块或可持久化trie 可以先看看这个:高斯消元解XOR方程组 分块做法: 我们先求出前i个数 ...

  10. Github开源Java项目(Disconf)上传到Maven Central Repository方法详细介绍

    最近我做了一个开源项目 Disconf:Distributed Configuration Management Platform(分布式配置管理平台) ,简单来说,就是为所有业务平台系统管理配置文件 ...