精华内容
下载资源
问答
  • 线程程序并发

    2012-12-08 23:40:27
    1.线程程序并发的执行它自身各个部分,线程程序主要问题是管理线程之间交互。所有进程都包含一个 执行线程,称之为主线程。主线程在程序开始时创建,然后主线程创建一个或者个子线程。c++没有包含...
     1.多线程程序并发的执行它自身的各个部分,多线程程序的主要问题是管理线程之间的交互。所有的进程都包含一个
    执行线程,称之为主线程。主线程在程序开始时创建,然后主线程创建一个或者多个子线程。c++没有包含任何对多线程应用程序的内建支持,它依赖于操作系统提供的特性。c++允许直接使用操作系统提供的多线程特性。
    
    
    
        2.windows线程函数
        (1)线程的创建和终止
         windows API提供了CreateThread()函数来创建一个线程。函数原型如下:
    HANDLE CreateThread(
      LPSECURITY_ATTRIBUTES lpsa, 
      DWORD cbStack, 
             LPTHREAD_START_ROUTINE lpStartAddr, 
      LPVOID lpvThreadParam, 
      DWORD fdwCreate, 
      LPDWORD lpIDThread
    ); 
        参数:
    lpThreadAttributes:指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,它被设为NULL,表示使用缺省值。
       dwStackSize,线程堆栈大小,一般=0,在任何情况下,Windows根据需要动态延长堆栈的大小。
       lpStartAddress,指向线程函数的指针,形式:@函数名,函数名称没有限制,但是必须以下列形式声明:
       DWORD WINAPI ThreadProc (LPVOID lpParam) ,格式不正确将无法调用成功。
       lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。
       dwCreationFlags :线程标志,可取值如下
       (1)CREATE_SUSPENDED-----创建一个挂起的线程,
       (2)0---------------------------------表示创建后立即激活。
       lpThreadId:保存新线程的id。
      返回值:
           函数成功,返回线程句柄;函数失败返回false。
    
    
        可以调用CloseThread()来显示销毁这个线程,否则在父进程结束时自动销毁。
        还可以使用以下函数:
    VOID ExitThread( 
      DWORD dwExitCode
    ); //用来终止调用了该函数的线程,早功能上等价于允许线程函数正常返回。
        BOOL TerminateThread(
      HANDLE hThread, //要终止的线程句柄
      DWORD dwExitCode
    );//立刻终止线程
    
    
    
        3.Visual C++对CreateThread()和ExitThread()的替换。
    在visual C++中使用CreateThread()和ExitThread()会泄漏少量内存,因此Visual C++用_beginthreadex()和
    _endthreadex()替换,需要头文件<process.h>,下面是函数原型:
    uintptr_t _beginthreadex( 
        void *security,
        unsigned stack_size,
        unsigned ( *start_address )( void * ),//回调函数
        void *arglist,//传递给回调函数的参数
        unsigned initflag,
        unsigned *thrdaddr 
    );
    void _endthreadex( 
        unsigned retval 
    );
    功能与参数含义大体与CreateThread()和ExitThread()相同。
    
    
    
         4.线程的挂起与恢复
    DWORD SuspendThread(
      HANDLE hThread); //挂起
    
    
    DWORD ResumeThread( 
      HANDLE hThread);//恢复 
            每个执行的线程都有与其相关的挂起计数,如果计数为0则不会挂起,计数为非0值则挂起。发生错误,反悔-1;
    
    
    
         5.改变线程优先级
    线程的优先级由进程的优先级以及各个线程的优先级的组合确定。
    (1)进程优先级别:
    ABOVE_NORMAL_PRIORITY_CLASS
    BELOW_NORMAL_PRIORITY_CLASS
    HIGH_PRIORITY_CLASS
    IDLE_PRIORITY_CLASS
    NORMAL_PRIORITY_CLASS
    REALTIME_PRIORITY_CLASS
    可以用一下函数获取或者设置进程优先级:
    DWORD WINAPI GetPriorityClass(
             HANDLE hProcess);
    BOOL WINAPI SetPriorityClass(
               HANDLE hProcess,
                 DWORD dwPriorityClass);
    
    
    (2)线程优先级别
    THREAD_PRIORITY_TIME_CRITICAL 
    THREAD_PRIORITY_HIGHEST  
    THREAD_PRIORITY_ABOVE_NORMAL 
    THREAD_PRIORITY_NORMAL
    THREAD_PRIORITY_BELOW_NORMAL
    THREAD_PRIORITY_LOWEST 
    THREAD_PRIORITY_ABOVE_IDLE
    THREAD_PRIORITY_IDLE 
    可以用一下函数获取或者设置线程优先级:
    int GetThreadPriority( 
      HANDLE hThread);
    BOOL SetThreadPriority( 
      HANDLE hThread, 
      int nPriority); 
    
          6.获取当前线程的句柄
    HANDLE GetCurrentThread(void);
    得到的是一个伪句柄
    

    展开全文
  • 1、并发(concurrency)指的是多执行单元同时、并行被执行,而并发的执行单元共享资源(硬件资源和软件上全局变量、静态变量等)访问则很容易导致竞态(race condition)。   2、在设计自己驱动...

    并发及其管理

    1、并发(concurrency)指的是多个执行单元同时、并行被执行,而并发的执行单元对共享资源(硬件资源和软件上的全局变量、静态变量等)的访问则很容易导致竞态(race condition)

     

    2、在设计自己的驱动程序时,第一个要记住的规则是,只要可能,就应该避免资源的共享。如果没有并发的访问,也就不会有竞态的产生。因此,仔细编写的内核代码应具有最少的共享。这种思想的最明显应用就是避免使用全局变量。

    但是资源的共享是不可避免的,如硬件资源的本质就是共享、指针传递等。

     

    3、资源共享的硬规则:

    (1)在单个执行线程之外共享硬件或软件资源的任何时候,因为另外一个线程可能产生对该资源的不一致观察,因此必须显示地管理对该资源的访问。

    (2)当内核代码创建了一个可能和其他内核部分共享的对象时,该对象必须在还有其他组件引用自己时保持存在(并正确工作)。

     

     

    信号量和互斥体

    接下来研究如何为scull添加锁定。我们的目的是使对scull数据结构的访问是原子的,这意味着在涉及到其他执行线程之前,整个操作就已经结束了。

    访问共享资源的代码区域称为临界区(critical section)

     

    一个信号量本质上是一个整数值,它和一对函数联合使用,这一对函数通常称为P和V。

    希望进入临界区的进程将在相关信号量上调用P;如果信号量的值大于零,则该值会减小一,而进程可以继续。相反,如果信号量的值为零(或者更小),进程必须等待直到其他人释放该信号量。对信号量的解锁通过调用V完成:该函数增加信号量的值,并在必要时唤醒等待的进程。

    当信号量用于互斥时(即避免多个进程同时在一个临界区中运行),信号量的值应初始化为1。这种信号量在任何给定时刻只能由单个进程或线程拥有。在这种使用模式下,一个信号量也称为一个“互斥体”(mutex),它是互斥(mutual exclusion)的简称,在Linux内核中几乎所有的信号量均用于互斥。

     

    Linux信号量的实现

    要使用信号量,内核代码必须包含<asm/semaphore.h>。相关的类型是struct semaphore

    Linux内核中与信号量相关的操作:

    1、定义信号量

    1 struct semaphore sem;

     

    2、初始化信号量

     直接创建信号量:

    1 void sema_init(struct semaphore *sem, int val);

    以计数值val初始化信号量sem。可将信号量初始化为大于1的值从而成为一个计数信号量

     

    运行时动态分配互斥信号量:

    1 init_MUTEX(struct semaphore *sem);

    以计数值1初始化动态创建的互斥信号量(二值信号量)

     

    1 init_MUTEX_LOCKED(struct semaphore *sem);

    以计数值0初始化动态创建的互斥信号量。初始为加锁状态。

     

    声明和初始化一个互斥信号量(声明+初始化宏)

    1 DECLARE_MUTEX(name);

    定义并以计数值1初始化信号量。DECLARE_MUTEX(name)实际上是定义一个semaphore,所以它的使用应该对应信号量的P,V函数。

     

    3、获得信号量

    P函数被称为down—或者这个名字的其他变种。这里,“down”指的是该函数减小了信号量的值,它也许会将信号量置于休眠状态,然后等待信号量变得可用,之后授予调用者对被保护资源的访问。down有三个版本:

    1 void down(struct semaphore *sem);

    减小信号量的值,并在必要时一直等待,也会导致睡眠,不能用在中断上下文中。

     

    1 int down_interruptible(struct semaphore *sem);

    完成相同的工作,但操作是可中断的,即在等待信号量的过程中可被信号中断。要小心,如果操作被中断,该函数会返回非零值,而调用者不会拥有该信号量。对其的正确使用需要始终检查返回值,并作出相应的响应。

     

    1 int down_trylock(struct semaphore *sem);

    永远不会睡眠。如果信号量在调用时不可获得,其会立即返回一个非零值。

     

    当线程成功调用上述down的某个版本后,就称为该线程“拥有”了信号量,这样,该线程就被赋予访问由该信号量保护的临界区的权利。

     

    4、 释放信号量

    当互斥操作完成后,必须返回该信号量。Linux等价于V的函数是up:

    1 void up(struct semaphore *sem);

    调用up之后,调用者不再拥有该信号量。

    任何拿到信号量的线程都必须通过一次(只有一次)对up的调用而释放该信号量。

     

    在出现错误的情况下,经常需要特别小心;如果在拥有一个信号量时发生错误,必须在将错误状态返回给调用者之前释放该信号量

     

    互斥体

    “互斥体(mutex)”这个称谓所指的是任何可以睡眠的强制互斥锁,比如使用计数是1的信号量。但在最新的Linux内核中,“互斥体(mutex)”这个称谓现在也用于一种实现互斥的特定睡眠锁。也就是说,互斥体是一种互斥信号。

    mutex在内核中对应的数据结构是mutex,其行为和使用计数为1的信号量类似,但操作接口更简单,实现也更高效,而且使用限制更强。使用互斥体需包含<linux/mutex.h>。

    1、定义及初始化互斥体

    静态定义互斥体(声明+初始化宏)

    1 DEFINE_MUTEX(mutexname); 

     

    运行时动态初始化互斥体

    1 mutex_init(&mutex); 

     

    2、获取互斥体

    1 void mutex_lock(struct mutex *lock);
    2 int mutex_lock_interruptible(struct mutex *lock);
    3 int mutex_trylock(struct mutex *lock);

    上述函数的操作行为和信号量的down有类似之处,_try函数永不睡眠。

     

    3、释放互斥体

    1 void mutex_unlock(struct mutex *lock);

     

    4、mutex的使用

    1 struct mutex my_mutex;  // 定义mutex
    2 mutex_init(&my_mutex); // 初始化mutex
    3
    4 mutex_lock(&my_mutex); // 获取mutex
    5 /* 临界区 */
    6 mutex_unlock(&my_mutex); // 释放mutex

    mutex的使用方法和信号量用于互斥的场合完全一样。

     

    在scull中使用信号量

    正确使用锁定机制的关键是,明确指定需要保护的资源,并确保每一个对这些资源的访问都正确使用了锁。

    在我们的实例程序中,所有的信息都包含在scull_dev结构体中,因此,该结构体就是我们锁定机构的逻辑范围:

    复制代码
     1 /* 定义scull_dev结构体用来描述scull设备 */
    2 struct scull_dev {
    3 struct scull_qset *data; /* 指向第一个scull_qset结构体 */
    4 int quantum; /* 量子大小,量子也是指针,指向的内存区域大小即为quantum */
    5 int qset; /* 量子集大小(指针数组元素个数),量子集即指针数组,其元素即量子 */
    6 unsigned long size; /* 数据总量,动态量,使用时由写入数据总量决定 */
    7 unsigned int access_key; /* used by sculluid and scullpriv */
    8 struct semaphore sem; /* 互斥信号量 */
    9 struct cdev cdev; /* 字符设备结构 */
    10 };
    复制代码

    scull例程中,为每个设备都使用单独的信号量,允许不同设备上的操作可以并行处理。从而可以提高性能。

     

    信号量使用前的初始化:

    复制代码
    1 /* Initialize each device. */
    2 /* 初始化每个设备的访问区块--struct scull_dev结构体 */
    3 for (i = 0; i < scull_nr_devs; i++) {
    4 scull_devices[i].quantum = scull_quantum;/* 设定量子大小 */
    5 scull_devices[i].qset = scull_qset;/* 设定量子集大小 */
    6 init_MUTEX(&scull_devices[i].sem);/* 初始化互斥信号量 */
    7 scull_setup_cdev(&scull_devices[i], i);/* 向内核注册字符设备 */
    8 }
    复制代码

    信号量必须在scull设备对其他设备可用之前被初始化。

     

    读取者/写入者信号量

    信号量对所有的调用者互斥,而不管每个线程到底想做什么。

    允许多个并发的读取者是可能的,Linux内核为这种情形提供了一种特殊的信号量类型,称为“rwsem”(或者reader/write semaphore,读取者/写入者信号量)。使用rwsem的代码必须包含<linux/rwsem.h>,rwsem相关的数据类型是struct rw_semaphore。

    1、初始化rwsem

    1 init_rwsem(struct rw_semaphore *sem);

     

    2、只读访问,可用接口

    1 void down_read(struct rw_semaphore *sem);
    2 int down_read_trylock(struct rw_semaphore *sem);
    3 void up_read(struct rw_semaphore *sem);

     

    3、针对写入者的接口

    1 void down_write(struct rw_semaphore *sem);  
    2 int down_write_trylock(struct rw_semaphore *sem);
    3 void up_write(struct rw_semaphore *sem);
    4 /* downgrade write lock to read lock */
    5 void downgrade_write(struct rw_semaphore *sem);

    在结束修改之后,可以调用downgrade_write,来允许其他读取者的访问。

     

    一个rwsem可允许一个写入者或无限多个读取者拥有该信号量。写入者具有更高优先级,其有可能导致读取者“饿死”。最好在很少需要写访问且写入者只会短期拥有信号量的时候使用rwsem。

     

     

    completion

    信号量用于同步

    如果信号量被初始化为0,则它可以用于同步,同步意味着一个执行的继续执行需等待另一执行单元完成某事,保证执行的先后顺序。

     

    完成量用于同步

    Linux内核提供了一种更好的同步机制,即完成量(completion),完成量允许一个线程告诉另一个线程某个工作已经完成,其声明在<linux/completion.h>中。

    1、创建和初始化completion

    1 DECLARE_COMPLETION(my_completion);  // 定义+初始化

     

    动态创建和初始化完成量

    1 struct completion my_completion;
    2 void init_completion(&my_completion);

     

    2、等待完成量

    1 void wait_for_completion(struct completion *);

    执行一个非中断的等待,如果调用了wait_for_completion且没有人会完成该任务,则会产生一个不可杀的进程。

     

    3、唤醒完成量

    1 void complete(struct completion *); //唤醒一个等待进程
    2 void complete_all(struct completion *); // 唤醒所有等待进程

    一个completion通常是个单次(one-shot)设备,它只会被使用一次,然后被丢弃。如果没有使用completion_all,则我们可以重复使用一个completion结构,但是,如果使用了completion_all,则必须在重复使用该结构体前重新初始化它。下面这个宏用来快速执行重新初始化:

    1 INIT_COMPLETION(struct completion c);

     

    4、完成量用于同步

     

    自旋锁

    自旋锁(spinlock)可在不能睡眠的代码中使用,比如中断例程。

    一个自旋锁是一个互斥设备,它只有两个值:“锁定”和“解锁”。它通常实现为某个整数值中的单个位。希望获得某特定锁的代码测试相关的位。如果锁可用,则“锁定”位被设置,而代码继续进入临界区。相反,如果锁被其他人获得,则代码进入忙循环并重复检查这个锁,直到该锁可用为止,这个循环就是自旋锁的“自旋”部分

    “测试并设置”的操作必须以原子的方式完成。

    适用于自旋锁的核心规则是

    1、任何拥有自旋锁的代码都必须是原子的。它不能休眠,事实上,它不能因为任何原因放弃处理器,除了服务中断以外(某些情况下也不能放弃CPU,如果在中断服务例程中,也需要该自旋锁,则会发生“死锁”,因此,在拥有自旋锁时会禁止本地CPU的中断)。任何时候,只要内核代码拥有自旋锁,在相关处理器上的抢占就会被禁止。当我们编写在自旋锁下执行的代码时,必须注意每一个所调用的函数,他们不能休眠。

    2、自旋锁必须在可能的最短时间内拥有。拥有自旋锁的时间越长,其他处理器不得不自旋的时间就越长,而它不得不自旋的可能性就越大。

     

    自旋锁API

    要使用自旋锁原语,需要包含头文件<linux/spinlock.h>

    复制代码
     1 spinlock_t my_lock = SPIN_LOCK_UNLOCKED;/* 编译时初始化spinlock*/
    2 void spin_lock_init(spinlock_t *lock);/* 运行时初始化spinlock*/
    3
    4 /* 所有spinlock等待本质上是不可中断的,一旦调用spin_lock,在获得锁之前一直处于自旋状态*/
    5 void spin_lock(spinlock_t *lock);/* 获得spinlock*/
    6 void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);/* 获得spinlock,禁止本地cpu中断,保存中断标志于flags*/
    7 void spin_lock_irq(spinlock_t *lock);/* 获得spinlock,禁止本地cpu中断*/
    8 void spin_lock_bh(spinlock_t *lock)/* 获得spinlock,禁止软件中断,保持硬件中断打开*/
    9
    10 /* 以下是对应的锁释放函数*/
    11 void spin_unlock(spinlock_t *lock);
    12 void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
    13 void spin_unlock_irq(spinlock_t *lock);
    14 void spin_unlock_bh(spinlock_t *lock);
    15
    16 /* 以下非阻塞自旋锁函数,成功获得,返回非零值;否则返回零*/
    17 int spin_trylock(spinlock_t *lock);
    18 int spin_trylock_bh(spinlock_t *lock);
    19
    20
    21 /*新内核的<linux/spinlock.h>包含了更多函数*/
    复制代码

     

    读取者/写入者自旋锁

    允许任意数量的读取者进入临界区,但写入者必须互斥访问。读取者/写入者具有rwlock_t类型,在<linux/spinkock.h>中定义。

    读取者/写入者自旋锁API

    复制代码
     1 rwlock_t my_rwlock = RW_LOCK_UNLOCKED;/* 编译时初始化*/
    2
    3
    4 rwlock_t my_rwlock;
    5 rwlock_init(&my_rwlock); /* 运行时初始化*/
    6
    7
    8 void read_lock(rwlock_t *lock);
    9 void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
    10 void read_lock_irq(rwlock_t *lock);
    11 void read_lock_bh(rwlock_t *lock);
    12
    13
    14 void read_unlock(rwlock_t *lock);
    15 void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
    16 void read_unlock_irq(rwlock_t *lock);
    17 void read_unlock_bh(rwlock_t *lock);
    18
    19 /* 新内核已经有了read_trylock */
    20
    21 void write_lock(rwlock_t *lock);
    22 void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
    23 void write_lock_irq(rwlock_t *lock);
    24 void write_lock_bh(rwlock_t *lock);
    25 int write_trylock(rwlock_t *lock);
    26
    27
    28 void write_unlock(rwlock_t *lock);
    29 void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
    30 void write_unlock_irq(rwlock_t *lock);
    31 void write_unlock_bh(rwlock_t *lock);
    32
    33 /*新内核的<linux/spinlock.h>包含了更多函数*/
    复制代码

     

    在Linux使用读-写自旋锁时,这种锁机制照顾照顾读比写要多一点。当读锁被持有时,写操作为了互斥访问只能等待,但是读者可以继续成功占有锁。而自旋等待的写者在所有读者释放锁之前是无法获得锁的。所以,大量读者必然使挂起的写者处于饥饿状态。

     

    如果加锁时间不长且代码不会睡眠(比如中断处理程序),利用自旋锁是最佳选择。如果加锁时间可能很长或者代码在持有锁时有可能睡眠,那么最好使用信号量来完成加锁功能。

     

     

    陷阱锁

    锁定模式的设置必须在一开始就要设置好,否则其后的改进会非常困难。

    不明确的规则

    信号量和自旋锁是不可递归的。在scull中,我们的设计规则是:由系统调用直接调用的那些函数均要获得信号量,以便保护要访问的设备结构。而其他的内部函数,只会由其他的scull函数调用,则假定信号量已被正确获取。

     

    锁的顺序规则

    必须获取多个锁时,应该始终以相同的顺序获得。

    有帮助的两个规则是:

    1、 如果必须要获得一个局部锁(比如一个设备锁),以及一个属于内核更中心位置的锁,则应该首先获取自己的局部锁。

    2、 如果我们拥有自旋锁和信号量的组合,则必须首先获得信号量。

     

    细粒度和粗粒度的对比

    设备驱动程序中的锁通常相对直接,可以用单个锁来处理所有的事情,或者可以为每个设备建立一个锁。作为通常规则,我们应该在最初使用粗粒度的锁。

     

     

    除了锁之外的办法

    在某些情况下,原子的访问不需要完整的锁。

    免锁算法

    经常用于免锁的生产者/消费者任务的数据结构之一是循环缓冲区(circular buffer)。循环缓冲区的使用在设备驱动程序中相当普遍。特别是网络适配器,经常使用循环冲区和处理器交换数据。在Linux内核中,有一个通用的循环缓冲区实现,有关其使用可参阅<linux/kfifo.h>

     

    原子变量

    有时,共享的资源可能恰好是一个简单的整数,完整的锁机制对一个简单的整数来讲显得有些浪费。针对这种情况,内核提供了一种原子的整数类型,称为atomic_t,定义在<ams/atomic.h>中。

    一个atomic_t变量在所有内核支持的架构上保存了一个int值。但是,由于某些处理器上这种数据类型的工作方式有些限制,因此不能使用完整的整数范围,也就是说,在atomic_t变量中不能记录大于24位的整数。原子操作速度非常快,因为只要可能,它们就会被编译成单个机器指令。

    原子变量操作函数:

    复制代码
     1 void atomic_set(atomic_t *v, int i); /*设置原子变量 v 为整数值 i.*/
    2 atomic_t v = ATOMIC_INIT(0); /*编译时使用宏定义 ATOMIC_INIT 初始化原子值.*/
    3
    4 int atomic_read(atomic_t *v); /*返回 v 的当前值.*/
    5
    6 void atomic_add(int i, atomic_t *v);/*由 v 指向的原子变量加 i. 返回值是 void*/
    7 void atomic_sub(int i, atomic_t *v); /*从 *v 减去 i.*/
    8
    9 void atomic_inc(atomic_t *v);
    10 void atomic_dec(atomic_t *v); /*递增或递减一个原子变量.*/
    11
    12 int atomic_inc_and_test(atomic_t *v);
    13 int atomic_dec_and_test(atomic_t *v);
    14 int atomic_sub_and_test(int i, atomic_t *v);
    15 /*进行一个特定的操作并且测试结果; 如果, 在操作后, 原子值是 0, 那么返回值是真; 否则, 它是假. 注意没有 atomic_add_and_test.*/
    16
    17 int atomic_add_negative(int i, atomic_t *v);
    18 /*加整数变量 i 到 v. 如果结果是负值返回值是真, 否则为假.*/
    19
    20 int atomic_add_return(int i, atomic_t *v);
    21 int atomic_sub_return(int i, atomic_t *v);
    22 int atomic_inc_return(atomic_t *v);
    23 int atomic_dec_return(atomic_t *v);
    24 /*像 atomic_add 和其类似函数, 除了它们返回原子变量的新值给调用者.*/
    复制代码

    atomic_t类型数据必须只能通过上面的函数来访问。如果将原子变量传递给了需要整型参数的函数,则会遇到编译错误。只有原子变量的数目是原子的,atomic_t变量才能正常工作,需要多个atomic_t变量的操作,仍然需要某种类型的锁。

     

    原子位操作

    为了实现位操作,内核提供了一组可原子地修改和测试单个位的函数。

    原子位操作非常快,只要底层硬件允许,这种操作就可以使用单个机器指令来执行,并且不需要禁止中断。这些函数依赖于具体的架构,因此在<asm/bitops.h>中声明。即使是在SMP计算机上,这些函数也可确保为原子的,因此,能提供跨处理器的一致性。

    这些函数使用的数据类型也是依赖于具体架构的。nr参数(用来描述要操作的位)通常被定义为int,但在少数架构上被定义为unsigned long。要修改的地址通常是指向unsigned long指针,但在某些架构上却使用void *来代替。

    可用的位操作如下:

    复制代码
     1 void set_bit(nr, void *addr); /*设置第 nr 位在 addr 指向的数据项中。*/
    2
    3 void clear_bit(nr, void *addr); /*清除指定位在 addr 处的无符号长型数据.*/
    4
    5 void change_bit(nr, void *addr);/*翻转nr位.*/
    6
    7 test_bit(nr, void *addr); /*这个函数是唯一一个不需要是原子的位操作; 它简单地返回这个位的当前值.*/
    8
    9 /*以下原子操作如同前面列出的, 除了它们还返回这个位以前的值.*/
    10
    11 int test_and_set_bit(nr, void *addr);
    12 int test_and_clear_bit(nr, void *addr);
    13 int test_and_change_bit(nr, void *addr);
    复制代码

     

    seqlock

    2.6内核包含了一对新机制打算来提供快速地,无锁地存取一个共享资源。seqlock要保护的资源小,简单,并且常常被存取,并且很少写存取但是必须要快。seqlock 通常不能用在保护包含指针的数据结构。seqlock 定义在<linux/seqlock.h> 。

    1 /*两种初始化方法*/
    2 seqlock_t lock1 = SEQLOCK_UNLOCKED;
    3
    4 seqlock_t lock2;
    5 seqlock_init(&lock2);

     

    这个类型的锁常常用在保护某种简单计算,读存取通过在进入临界区入口获取一个(无符号的)整数序列来工作。在退出时, 那个序列值与当前值比较; 如果不匹配, 读存取必须重试。读者代码形式:

    1 unsigned int seq;
    2 do {
    3 seq = read_seqbegin(&the_lock);
    4 /* Do what you need to do */
    5 } while read_seqretry(&the_lock, seq);

     

    如果你的 seqlock可能从一个中断处理里存取,你应当使用IRQ安全的版本来代替:

    1 unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned long flags);
    2 int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq, unsigned long flags);

     

    写者必须获取一个排他锁来进入由一个seqlock保护的临界区,写锁由一个自旋锁实现,调用:

    1 void write_seqlock(seqlock_t *lock); 
    2 void write_sequnlock(seqlock_t *lock);

     

    因为自旋锁用来控制写存取, 所有通常的变体都可用:

    复制代码
    1 void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);
    2 void write_seqlock_irq(seqlock_t *lock);
    3 void write_seqlock_bh(seqlock_t *lock);
    4
    5 void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);
    6 void write_sequnlock_irq(seqlock_t *lock);
    7 void write_sequnlock_bh(seqlock_t *lock);
    复制代码

    还有一个write_tryseqlock在它能够获得锁时返回非零。

     

    读取-复制-更新

    读取-拷贝-更新(RCU) 是一个高级的互斥方法, 在合适的情况下能够有高效率。它在驱动中的使用很少。使用RCU的代码须包含<linux/rcupdate.h>

     

     

    开发板实验

    代码参考了Tekkaman的,然后加入了自己理解的一些注释。

    实验板是256M的mini2440。

    模块程序链接:http://files.cnblogs.com/ycz9999/complete.zip
    模块测试程序链接:http://files.cnblogs.com/ycz9999/complete_test.zip

    注意:在实验时,如果先执行读进程,其会阻塞,使得实验无法继续进行。因此在执行读取进程时,在其末尾加上&,使其在后台运行!

    复制代码
    [root@FriendlyARM complete]# ls
    complete.ko completion_testr completion_testw
    [root@FriendlyARM complete]# insmod complete.ko
    [root@FriendlyARM complete]# echo 8 > /proc/sys/kernel/printk
    [root@FriendlyARM complete]# cat /proc/devices
    Character devices:
    1 mem
    4 /dev/vc/0
    4 tty
    5 /dev/tty
    5 /dev/console
    5 /dev/ptmx
    7 vcs
    10 misc
    13 input
    14 sound
    21 sg
    29 fb
    81 video4linux
    89 i2c
    90 mtd
    116 alsa
    128 ptm
    136 pts
    180 usb
    188 ttyUSB
    189 usb_device
    204 s3c2410_serial
    253 complete
    254 rtc

    Block devices:
    259 blkext
    7 loop
    8 sd
    31 mtdblock
    65 sd
    66 sd
    67 sd
    68 sd
    69 sd
    70 sd
    71 sd
    128 sd
    129 sd
    130 sd
    131 sd
    132 sd
    133 sd
    134 sd
    135 sd
    179 mmc
    [root@FriendlyARM complete]# mknod -m 666 /dev/complete c 253 0
    [root@FriendlyARM complete]# ls
    complete.ko completion_testr completion_testw
    [root@FriendlyARM complete]# ./completion_testr&
    [root@FriendlyARM complete]# process 745 (completion_test) going to sleep

    [root@FriendlyARM complete]# ./completion_testr&
    [root@FriendlyARM complete]# process 746 (completion_test) going to sleep

    [root@FriendlyARM complete]# ps
    PID USER VSZ STAT COMMAND
    1 root 3068 S init
    2 root 0 SW [kthreadd]
    3 root 0 SW [ksoftirqd/0]
    4 root 0 SW [events/0]
    5 root 0 SW [khelper]
    11 root 0 SW [async/mgr]
    209 root 0 SW [sync_supers]
    211 root 0 SW [bdi-default]
    213 root 0 SW [kblockd/0]
    222 root 0 SW [khubd]
    228 root 0 SW [kmmcd]
    244 root 0 SW [rpciod/0]
    251 root 0 SW [kswapd0]
    298 root 0 SW [aio/0]
    302 root 0 SW [nfsiod]
    306 root 0 SW [crypto/0]
    416 root 0 SW [mtdblockd]
    580 root 0 SW [scsi_eh_0]
    581 root 0 SW [usb-storage]
    635 root 0 SW [usbhid_resumer]
    701 root 3068 S syslogd
    704 root 3328 S /usr/sbin/inetd
    708 root 1940 S /usr/sbin/boa
    711 root 1416 S /usr/bin/led-player
    720 root 9320 S /opt/Qtopia/bin/qpe
    721 root 3392 S -/bin/sh
    722 root 3068 S init
    723 root 3068 S init
    725 root 3068 S init
    733 root 2036 S ts_calibrate
    736 root 0 SW [flush-31:0]
    745 root 1408 D ./completion_testr
    746 root 1408 D ./completion_testr
    747 root 3392 R ps
    [root@FriendlyARM complete]# ./completion_testw
    process 748 (completion_test) awakening the readers...
    awoken 745 (completion_test)
    read ok! code=0
    write ok! code=0
    [1] - Done ./completion_testr
    [root@FriendlyARM complete]# ps
    PID USER VSZ STAT COMMAND
    1 root 3068 S init
    2 root 0 SW [kthreadd]
    3 root 0 SW [ksoftirqd/0]
    4 root 0 SW [events/0]
    5 root 0 SW [khelper]
    11 root 0 SW [async/mgr]
    209 root 0 SW [sync_supers]
    211 root 0 SW [bdi-default]
    213 root 0 SW [kblockd/0]
    222 root 0 SW [khubd]
    228 root 0 SW [kmmcd]
    244 root 0 SW [rpciod/0]
    251 root 0 SW [kswapd0]
    298 root 0 SW [aio/0]
    302 root 0 SW [nfsiod]
    306 root 0 SW [crypto/0]
    416 root 0 SW [mtdblockd]
    580 root 0 SW [scsi_eh_0]
    581 root 0 SW [usb-storage]
    635 root 0 SW [usbhid_resumer]
    701 root 3068 S syslogd
    704 root 3328 S /usr/sbin/inetd
    708 root 1940 S /usr/sbin/boa
    711 root 1416 S /usr/bin/led-player
    720 root 9320 S /opt/Qtopia/bin/qpe
    721 root 3392 S -/bin/sh
    722 root 3068 S init
    723 root 3068 S init
    725 root 3068 S init
    733 root 2036 S ts_calibrate
    736 root 0 SW [flush-31:0]
    746 root 1408 D ./completion_testr
    749 root 3392 R ps
    [root@FriendlyARM complete]# ./completion_testw
    process 750 (completion_test) awakening the readers...
    awoken 746 (completion_test)
    read ok! code=0
    write ok! code=0
    [2] + Done ./completion_testr
    [root@FriendlyARM complete]# ps
    PID USER VSZ STAT COMMAND
    1 root 3068 S init
    2 root 0 SW [kthreadd]
    3 root 0 SW [ksoftirqd/0]
    4 root 0 SW [events/0]
    5 root 0 SW [khelper]
    11 root 0 SW [async/mgr]
    209 root 0 SW [sync_supers]
    211 root 0 SW [bdi-default]
    213 root 0 SW [kblockd/0]
    222 root 0 SW [khubd]
    228 root 0 SW [kmmcd]
    244 root 0 SW [rpciod/0]
    251 root 0 SW [kswapd0]
    298 root 0 SW [aio/0]
    302 root 0 SW [nfsiod]
    306 root 0 SW [crypto/0]
    416 root 0 SW [mtdblockd]
    580 root 0 SW [scsi_eh_0]
    581 root 0 SW [usb-storage]
    635 root 0 SW [usbhid_resumer]
    701 root 3068 S syslogd
    704 root 3328 S /usr/sbin/inetd
    708 root 1940 S /usr/sbin/boa
    711 root 1416 S /usr/bin/led-player
    720 root 9320 S /opt/Qtopia/bin/qpe
    721 root 3392 S -/bin/sh
    722 root 3068 S init
    723 root 3068 S init
    725 root 3068 S init
    733 root 2036 S ts_calibrate
    736 root 0 SW [flush-31:0]
    751 root 3392 R ps
    [root@FriendlyARM complete]# ./completion_testw
    process 752 (completion_test) awakening the readers...
    write ok! code=0
    [root@FriendlyARM complete]# ./completion_testr
    process 753 (completion_test) going to sleep
    awoken 753 (completion_test)
    read ok! code=0
    [root@FriendlyARM complete]#
    复制代码

    实验表明:如果先读数据,读的程序会被阻塞(因为驱动在wait_for_completion,等待写的完成)。如果先写,读程序会比较顺利的执行下去(虽然也会休眠,但马上会被唤醒!)。

     

     

     

     

    参考:

    《Linux设备驱动程序(第三版)》

    Tekkaman Ninja: http://blog.chinaunix.net/uid/20543672.html 

    展开全文
  • 并发

    2017-01-30 13:49:19
    然而,对于某些问题,能够并行地执行程序的多个部分则非常必要。 本章主要介绍并发的基本知识,使得我们能够理解其概念并编写出合理的多线程程序。 21.1 并发的多面性 使用并发解决问题大体上可以分为...

    第21章 并发

    前面所学习的都是有关顺序编程的知识:程序中的所有事物在任意时刻都只能执行一个步骤。然而,对于某些问题,能够并行地执行程序中的多个部分则是非常必要的。

    本章主要介绍并发的基本知识,使得我们能够理解其概念并编写出合理的多线程程序。

    21.1 并发的多面性

    使用并发解决的问题大体上可以分为速度和设计可管理性两种。

    21.1.1 更快的执行

    通常情况下,我们对并发能够提高速度的理解会是,并发可以更好地运用多处理器。

    但是,其实并发也能提高单处理器上的程序性能。其原因主要是:阻塞。 即程序中的某个任务因为该程序控制范围之外的某些条件(如I/O)而导致无法继续执行。此时,该线程将被阻塞,整个程序都将停止下来,直至外部条件发生变化。

    实现并发最直接的方式是在操作系统级别使用进程。进程是运行在它自己的地址空间内的自包容的程序。多任务操作系统可以通过周期性地将CPU从一个进程切换到另一个进程,来实现同时运行多个进程。

    多文件复制是这种并发的理想示例:每个任务都作为进程在其自己的地址空间中执行,并且,它们之间没有任何彼此通信的需要,操作系统会处理确保文件正确复制的所有细节。

    像Java所使用的并发系统则会共享诸如内存和I/O这样的资源,因此编写多线程程序最基本的困难在于协调不同线程驱动的任务之间对这些资源的使用,以使得这些资源不会同时被多个任务访问。

    Java提供的是线程机制:在由执行程序表示的单一进程中创建任务。 这种方式的好处是操作系统的透明性,对于不支持多任务的操作系统,仍然可以运行Java多线程程序。

    21.1.2 改进代码设计

    Java的线程机制是抢占式的,这表示调度机制会周期性地中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都会分配到数量合理的时间去驱动它的任务。

    线程能够帮助我们创建更加松散耦合的设计。

    21.2 基本的线程机制

    并发编程使我们可以将程序划分为多个分离的、独立运行的任务。通过使用多线程机制,这些独立任务中的每一个都将由执行线程来驱动。一个线程就是在进程中的一个单一的顺序控制流,其底层机制是切分CPU时间。

    在使用线程时,CPU将轮流给每个任务分配其占用时间。而线程的好处是:我们在编写代码时,则不必关心它是运行在一个还是多个CPU的机器上。使用线程机制是一种建立透明的、可扩展的程序的方法,可以通过增肌CPU加快程序的运行速度。多任务和多线程往往是使用多处理器系统的最合理方式。

    21.2.1 定义任务

    线程可以驱动任务,下面是任务的定义方式:

    public class LiftOff implements Runnable {
        protected int countDown = 10;
        private static int taskCount = 0;
        private final int id = taskCount++;
        public LiftOff() {}
        public LiftOff(int countDown) {
            this.countDown = countDown;
        }
        public String status() {
            return "#" + id + "(" + (countDown > 0 ? countDown : "LiftOff!" + "), ");
        }
        public void run() {
            while(countDown-- > 0) {
                System.out.println(status());
                Thread.yield();
            }
        }
    }

    通过实现Runnable接口并编写run()方法,就成功定义了一个任务。标识符id可以用来区分任务的多个示例。Thread.yield():建议线程调度器切换线程。

    在下面的示例中,这个任务并非由单独的线程驱动的,而是被直接调用的:

    public class MainThread {
        public static void main(String[] args) {
            LiftOff launch = new LiftOff();
            launch.run();
        }
    }

    21.2.2 Thread类

    将Runnable对象转变为任务的传统方式是使用Thread类:

    public class BasicThreads {
        public static void main(String[] args) {
            Thread t = new Thread(new LiftOff());
            t.start();
            System.out.println("Waiting for LiftOff");
        }
    }

    从输出可以看到:打印的信息在任务完成之前被执行了,其原因是该任务被其他线程所执行,因此主线程能够继续顺序执行。 这种能力并不局限于主线程,任何线程都可以启动另一个线程。

    下面,我们可以添加更多的线程去驱动更多的任务:

    public class MoreBasicThreads {
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) 
                new Thread(new LiftOff()).start();
            System.out.println("Waiting for LiftOff");
        }
    }
    

    输出说明不同任务的执行在线程被换进换出时混在了一起。这种交换是由线程调度器自动控制的。如果运行程序的机器具有多处理器,线程调度器将会在这些处理器之间默默分发线程。并且,该程序的运行结果可能是变化的,因为线程调度机制是非确定性的。

    21.2.3 使用Executor

    Executor在客户端和任务执行之间提供了一个间接层,并且允许我们管理异步任务的执行。

    下面通过使用Excutor重写上述示例,展示了Excutor的用法:

    public class CachedThreadPool {
        public static void main(String[] args) {
            ExecutorService exec = Executors.newCachedThreadPool();
            for (int i = 0; i < 5; i++) 
                exec.execute(new LiftOff());
            exec.shutdown();
        }
    }

    ExecutorService:具有声明周期的Executor,通过构建恰当的上下文来执行Runnable对象。

    ExecutorService.shutdown():防止新任务提交,当前线程在完成shutdown()被调用前所提交的所有任务后,安全退出。

    并且,我们可以轻易地将前面示例中的CachedThreadPool替换为不同类型的Executor:

    public class FixedThreadPool {
        public static void main(String[] args) {
            ExecutorService exec = Executors.newFixedThreadPool(5);
            for (int i = 0; i < 5; i++) 
                exec.execute(new LiftOff());
            exec.shutdown();
        }
    }

    FixedThreadPool使用了有限的线程集来执行所提交的任务。通过它,可以一次性预先执行代价高昂的线程分配,从而不必为每个任务都固定地付出创建线程的开销。

    CachedThreadPool在程序执行过程中通常会创建与所需数量相同的线程,然后在它回收旧线程时停止创建新线程。

    SingleThreadExecutor:单线程执行器,如果有多个任务,那么这些任务将排队,在一个任务执行完成后执行下一个任务,所有的任务将使用相同的线程。 在下面的示例中,可以看到每个任务都是按照它们被提交的顺序依次执行:

    public class SingleThreadExecutor {
        public static void main(String[] args) {
            ExecutorService exec = Executors.newSingleThreadExecutor();
            for (int i = 0; i < 5; i++) 
                exec.execute(new LiftOff());
            exec.shutdown();
        }
    }

    SingleThreadExecutor会序列化所有提交给它的任务,并会维护它自己的悬挂任务队列。

    21.2.4 从任务中产生返回值

    Runnable是执行工作的独立任务,没有任何返回值。如果希望任务完成时能够返回一个值,则需要实现Callable接口而不是Runnable接口。下面是一个简单示例:

    class TaskWithResult implements Callable<String> {
        private int id;
        public TaskWithResult(int id) {
            this.id = id;
        }
        public String call() {
            return "result of TaskWithResult " + id;
        }
    }
    public class CallableDemo {
        public static void main(String[] args) {
            ExecutorService exec = Executors.newCachedThreadPool();
            List<Future<String>> results = new ArrayList<Future<String>>();
            for (int i = 0; i < 10; i++) 
                results.add(exec.submit(new TaskWithResult(i)));
            for (Future<String> future : results) {
                try {
                    System.out.println(future.get());
                } catch (InterruptedException e) {
                    System.out.println(e);
                    return;
                } catch (ExecutionException e) {
                    System.out.println(e);
                } finally {
                    exec.shutdown();
                }
            }
        }
    }
    
    • ExecutorService.submit(Callable):产生一个Future对象。
    • Future.get():如果任务已完成,获取执行任务后的返回值,否则,将堵塞。
    • Future.isDone():查询Future是否已完成。

    21.2.5 休眠

    影响任务行为的一种简单方法是调用sleep():

    public class SleepingTask extends LiftOff {
        public void run() {
            try {
                while(countDown-- > 0) {
                    System.out.print(status());
                    TimeUnit.MILLISECONDS.sleep(100);
                }
            } catch (InterruptedException e) {
                System.err.println("Interrupted");
            }
        }
        public static void main(String[] args) {
            ExecutorService exec = Executors.newCachedThreadPool();
            for (int i = 0; i < 5; i++) 
                exec.execute(new SleepingTask());
            exec.shutdown();
        }
    }

    对TimeUnit.sleep()的调用会抛出InterruptedException异常,由于异常无法跨线程传播回主线程,所以必须在当前执行任务的线程中处理所有异常信息。

    21.2.6 优先级

    线程的优先级将该线程的重要性传递给了调度器。尽管CPU处理现有线程集的顺序是不确定的,但是调度器将倾向于让优先权更高的线程先执行,而优先级较低的线程执行的频率则会较低。

    下面是一个演示优先级等级的示例:

    public class SimplePriorities implements Runnable {
        private int countDown = 5;
        private volatile double d;
        private int priority;
        public SimplePriorities(int priority) {
            this.priority = priority;
        }
        public String toString() {
            return Thread.currentThread() + ": " + countDown;
        } 
        public void run() {
            Thread.currentThread().setPriority(priority);
            while(true) {
                for (int i = 1; i < 1000000; i++) {
                    d += (Math.PI + Math.E) / (double)i;
                    if(i % 1000 == 0)
                        Thread.yield();
                }
                System.out.println(this);
                if(--countDown == 0) return;
            }
        }
        public static void main(String[] args) {
            ExecutorService exec = Executors.newCachedThreadPool();
            for (int i = 0; i < 5; i++) 
                exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));
            exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));
            exec.shutdown();
        }
    }
    • Thread.currentThread():获取当前线程信息。
    • Thread.setPriority():设置指定线程的优先级。
    • Thread.getPriority():获取指定线程的优先级。

    由于数学运算是可以中断,所以本例通过大量运算,使得线程调度机制有时间交换任务并关注优先级,使得最高优先级线程被优先选择。

    21.2.7 切换线程

    当run()方法的循环一次迭代的工作完成时,我们可以通过调用Thread.yield()方法通知线程调度器,不过没有任何机制保证它会被采纳。但也不能过度依赖yield(),使其被误用。

    21.2.8 后台线程

    后台线程是指:在程序运行时,在后退提供通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。当所有非后台线程结束时,程序才会终止,同时会杀死进程中的所有后台线程。

    下面的示例展示了在主线程中开启多个后台线程:

    public class SimpleDaemons implements Runnable {
        public void run() {
            try {
                while(true) {
                    TimeUnit.MILLISECONDS.sleep(100);
                    System.out.println(Thread.currentThread() + " " + this);
                }
            } catch (InterruptedException e) {
                System.out.println("sleep() interrupted");
            }
        }
        
        public static void main(String[] args) throws InterruptedException {
            for (int i = 0; i < 10; i++) {
                Thread daemon = new Thread(new SimpleDaemons());
                daemon.setDaemon(true);
                daemon.start();
            }
            System.out.println("All daemons started");
            TimeUnit.MILLISECONDS.sleep(99);
        }
    }

    Thread.setDaemon(true):在线程启动前,将该线程设置为后台线程。一旦主线程完成其工作,所有后台进程都将被终止。

    通过编写定制的ThreadFactory可以定制由Exector创建的线程的属性(后台、优先级、名称)。下面ThreadFactory产生的所有线程均为后台线程:

    public class DaemonThreadFactory implements ThreadFactory {
        public Thread newThread(Runnable r) {
            Thread t = new Thread();
            t.setDaemon(true);
            return t;
        }
    }

    现在,我们可以通过Executors.newCachedThreadPool(ThreadFactory)来创建指定ThreadFactory的执行器了:

    public class DaemonFromFactory implements Runnable {
        public void run() {
            try {
                while(true) {
                    TimeUnit.MILLISECONDS.sleep(100);
                    System.out.println(Thread.currentThread() + " " + this);
                }
            } catch (InterruptedException e) {
                System.out.println("sleep() interrupted");
            }
        }
        
        public static void main(String[] args) throws InterruptedException {
            ExecutorService exec = Executors.newCachedThreadPool(new DaemonThreadFactory());
            for (int i = 0; i < 10; i++) 
                exec.execute(new DaemonFromFactory());
            System.out.println("All daemons started");
            TimeUnit.MILLISECONDS.sleep(500);
        }
    }

    Thread.isDaemon()方法可以确定一个线程是否是一个后台线程:

    class Daemon implements Runnable {
        private Thread[] t = new Thread[10];
        public void run() {
            for (int i = 0; i < t.length; i++) {
                t[i] = new Thread(new DaemonSpawn());
                t[i].start();
                System.out.print("DaemonSpawn " + i + " started, ");
            }
            for (int i = 0; i < t.length; i++) 
                System.out.print("t[" + i + "].isDaemon() = " + t[i].isDaemon() + ", ");
            while(true)
                Thread.yield();
        }
    }
    class DaemonSpawn implements Runnable {
        public void run() {
            while(true) {
                Thread.yield();
            }
        }
    }
    public class Daemons {
        public static void main(String[] args) throws InterruptedException {
            Thread d = new Thread(new Daemon());
            d.setDaemon(true);
            d.start();
            System.out.print("d.isDaemon() = " + d.isDaemon() + ", ");
            TimeUnit.SECONDS.sleep(1);
        }
    }

    可以发现:一个后台线程的所有子线程都被自动设置为后台线程。

    无论在任何情况下,当程序中最后一个非后台线程结束时,后台进程都会被终止:

    class ADaemon implements Runnable {
        public void run() {
            try {
                System.out.println("Starting ADaemon");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                System.out.println("Exiting via InterruptedException");
            } finally {
                System.out.println("This should always run?");
            }
        }
    }
    public class DaemonsDontRunFinally {
        public static void main(String[] args) {
            Thread t = new Thread(new ADaemon());
            t.setDaemon(true);
            t.start();
        }
    }

    在本例中,finally子句并未执行。一旦main()结束,JVM就会立即关闭所有的后台进程。我们无法以优雅的方式来关闭后台进程,所以非后台线程通常会是一种更好的选择,我们可以通过有序的方式关闭非后台进程。

    21.2.9 编码的变体

    下面我们通过继承Thread的方式定义任务,并在构造器中启动线程:

    public class SimpleThread extends Thread {
        private int countDown = 5;
        private static int threadCount = 0;
        public SimpleThread() {
            super(Integer.toString(++threadCount));
            start();
        }
        public String toString() {
            return "#" + getName() + "(" + countDown + "), ";
        }
        public void run() {
            while(true) {
                System.out.print(this);
                if(--countDown == 0)
                    return;
            }
        }
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) 
                new SimpleThread();
        }
    }

    我们也可以定义一个自管理的Runnable:

    public class SelfManaged implements Runnable {
        private int countDown = 5;
        public SelfManaged() { new Thread(this).start(); }
        public String toString() {
            return Thread.currentThread().getName() + "(" + countDown + "), ";
        }
        public void run() {
            while(true) {
                System.out.print(this);
                if(--countDown == 0)
                    return;
            }
        }
        public static void main(String[] args) {
            for (int i = 0; i < 5; i++) 
                new SelfManaged();
        }
    }

    这与直接继承Thread看似没有差别,但实现接口使得我们可以继承其他的类。

    下面是通过内部类的形式将线程代码隐藏在类中:

    class InnerThread1 {
        private int countDown = 5;
        public InnerThread1(String name) {
            new Thread(name) {
                public void run() {
                    try {
                        while(true) {
                            System.out.println(this);
                            if(--countDown == 0) return;
                            sleep(10);
                        }
                    } catch (InterruptedException e) {
                        System.out.println("Interrupted");
                    }
                }
                public String toString() {
                    return getName() + ": " + countDown;
                }
            }.start();
        }
    }
    class InnerRunnable1 {
        private int countDown = 5;
        public InnerRunnable1(String name) {
            new Thread(new Runnable() {
                public void run() {
                    try {
                        while(true) {
                            System.out.println(this);
                            if(--countDown == 0) return;
                            TimeUnit.MILLISECONDS.sleep(10);
                        }
                    } catch (InterruptedException e) {
                        System.out.println("Interrupted");
                    }
                }
                public String toString() {
                    return Thread.currentThread().getName() + ": " + countDown;
                }
            }, name).start();;
        }
    }
    class ThreadMethod {
        private int countDown = 5;
        private String name;
        public ThreadMethod(String name) { this.name = name; }
        public void runTask() {
            new Thread(name) {
                public void run() {
                    try {
                        while(true) {
                            System.out.println(this);
                            if(--countDown == 0) return;
                            sleep(10);
                        }
                    } catch (InterruptedException e) {
                        System.out.println("Interrupted");
                    }
                }
                public String toString() {
                    return getName() + ": " + countDown;
                }
            }.start();
        }
    }
    public class ThreadVariations {
        public static void main(String[] args) {
            new InnerThread1("InnerThread1");
            new InnerRunnable1("InnerRunnable1");
            new ThreadMethod("ThreadMethod").runTask();
        }
    }

    本例分别演示了如何在构造器和方法中以内部类的形式创建任务并启动线程。

    21.2.10 术语

    通过前面的学习,我们可能会产生一个错误认知:线程就是任务。其实,它们的关系是:任务依附于线程,线程是负责驱动赋予它的任务。并且,我们似乎对Thread类实际没有任务控制权,在使用执行器时更是如此,因为执行器替我们处理了线程的创建和管理。

    21.2.11 调用其他线程

    如果在一个线程上调用另一个线程的join()方法,则此线程将被挂起,直到目标线程结束才继续执行。join(long millis)可以设置超时参数:

    class Sleeper extends Thread {
        private int duration;
        public Sleeper(String name, int sleepTime) {
            super(name);
            duration = sleepTime;
            start();
        }
        public void run() {
            try {
                sleep(duration);
            } catch (InterruptedException e) {
                System.out.println(getName() + " was interrupted. " + "isInterrupted(): " + isInterrupted());
                return;
            }
            System.out.println(getName() + " has awakened");
        }
    }
    class Joiner extends Thread {
        private Sleeper sleeper;
        public Joiner(String name, Sleeper sleeper) {
            super(name);
            this.sleeper = sleeper;
            start();
        }
        public void run() {
            try {
                sleeper.join();
            } catch (InterruptedException e) {
                System.out.println("Interrupted");
            }
            System.out.println(getName() + " join completed");
        }
    }
    public class Joining {
        public static void main(String[] args) {
            Sleeper sleepy = new Sleeper("Sleepy", 1500);
            Sleeper grumpy = new Sleeper("Grumpy", 1500);
            Joiner dopey = new Joiner("Dopey", sleepy);
            Joiner doc = new Joiner("Doc", grumpy);
            grumpy.interrupt();
            
        }
    }

    21.2.12 创建有响应的用户界面

    使用线程的动机之一就是建立由响应的用户界面。下面的例子通过使用线程和不使用线程进行对比,展现了线程的优点:

    class UnresponsiveUI { 
        private volatile double d = 1;
        public UnresponsiveUI() throws Exception {
            while(d > 0) 
                d = d + (Math.PI + Math.E) / d;
            System.in.read();
        }
    }
    public class ResponsiveUI extends Thread {
        private static volatile double d = 1;
        public ResponsiveUI() {
            setDaemon(true);
            start();
        }
        public void run() {
            while(true) 
                d = d + (Math.PI + Math.E) / d;
        }
        
        public static void main(String[] args) throws Exception {
            //  no response 
            //! new UnresponsiveUI();
            new ResponsiveUI();
            System.in.read();
            System.out.println(d);
        }
    }

    21.2.13 捕获异常

    由于线程的本质特性,使得我们无法捕获从线程中逃逸的异常。如果异常未在run()方法内被捕获,则会直接传播到控制台:

    public class ExceptionThread implements Runnable {
        public void run() {
            throw new RuntimeException();
        }
        public static void main(String[] args) {
            ExecutorService exec = Executors.newCachedThreadPool();
            exec.execute(new ExceptionThread());
        }
    }

    并且,在外界对其进行捕获是无效的:

    public class NaiveExceptionHandling {
        public static void main(String[] args) {
            try {
                ExecutorService exec = Executors.newCachedThreadPool();
                exec.execute(new ExceptionThread());
            } catch (RuntimeException e) {
                System.out.println("Exception has been handled");
            }
        }
    }

    不过,Java SE5提供了一个新接口:Thread.UncaughtExceptionHandler,它允许我们在每一个Thread对象上设置一个异常处理器。并且该接口中的uncaughtException()方法会在线程因为捕获的异常而临近死亡时被调用:

    class ExceptionThread2 extends Thread {
        public ExceptionThread2() {
            System.out.println("created " + this);
            this.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
            System.out.println("eh = " + this.getUncaughtExceptionHandler());
            start();
        }
        public void run() {
            Thread t = Thread.currentThread();
            System.out.println("run() by " + t);
            System.out.println("eh = " + t.getUncaughtExceptionHandler());
            throw new RuntimeException();
        }
    }
    
    class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
        public void uncaughtException(Thread t, Throwable e) {
            System.out.println("caught " + e);
        }
    }
    
    public class CaptureUncaughtException {
        public static void main(String[] args) {
            new ExceptionThread2();
        }
    }

    如果程序中处处都是用同一个异常处理器,则可以通过静态方法Thread.setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler)将指定处理器设为默认的未捕获异常处理器:

    public class SettingDefaultHandler {
        public static void main(String[] args) {
            Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
            ExecutorService exec = Executors.newCachedThreadPool();
            exec.execute(new ExceptionThread());
        }
    }

    这个处理器只有在线程专有的未捕获异常处理器不存在的情况下才会被调用。

    21.3 共享受限资源

    单线程程序每次只做一件事,自然不存在同一时间,一个资源被多个实体共同访问。有了并发则可以同时做多件事,两个或多个线程彼此互相干涉的问题也就出现了。

    21.3.1 不正确地访问资源

    下面的例子中,一个类负责产生偶数,我们创建了消费该类所产生的数字:

    public abstract class IntGenerator {
        private volatile boolean canceled = false;
        public abstract int next();
        public void cancel() { canceled = true; }
        public boolean isCanceled() { return canceled; }
    }
    
    public class EvenChecker implements Runnable {
        private IntGenerator generator;
        private final int id;
        public EvenChecker(IntGenerator generator, int id) {
            this.generator = generator;
            this.id = id;
        }
        public void run() {
            while(!generator.isCanceled()) {
                int val = generator.next();
                if(val % 2 != 0) {
                    System.out.println(val + " not even!");
                    generator.cancel();
                }
            }
        }
        public static void test(IntGenerator generator, int count) {
            System.out.println("Press Control-C to exit");
            ExecutorService exec = Executors.newCachedThreadPool();
            for (int i = 0; i < count; i++) 
                exec.execute(new EvenChecker(generator, i));
            exec.shutdown();
        }
        
        public static void test(IntGenerator generator) {
            test(generator, 10);
        }
    }

    在本例中,线程共享的资源是IntGenerator类,EvenChecker的任务是读取并测试从IntGenerator返回的值。

    下面是IntGenerator的一个实现类,可以产生一系列偶数值:

    public class EvenGenerator extends IntGenerator {
        private int currentEvenValue = 0;
        public int next() {
            ++currentEvenValue;
            ++currentEvenValue;
            return currentEvenValue;
        }
        public static void main(String[] args) {
            EvenChecker.test(new EvenGenerator());
        }   
    }

    当一个任务在另一个任务执行第一个递增操作之后,但没有执行第二个递增操作之前,调用了next()方法,这将使得该值处于不恰当的状态。并且,在Java中,递增程序自身也需要多个步骤,递增过程中任务也可能会被线程挂起,即递增不是原子性的操作。

    21.3.2 解决共享资源竞争

    对于并发工作,我们需要某种方式来防止多个任务访问相同的资源。防止这种冲突的方法就是当资源被任何一个任务使用时,都为其上锁。

    基本上所有的并发模式在解决线程冲突问题时,都是采用序列化访问共享资源的方案:在给定时刻只允许一个任务访问共享资源。这通常是通过在代码前面加上一条锁语句来实现,锁语句产生的是互相排斥的效果。

    由于线程机制并不是确定性的,所以我们也无法得知当锁被开启时,下一个执行的是哪个线程。当然,可以通过yield()和setPriority()给线程调度机制提供建议,但这并非十分有效,主要还是取决于具体平台和JVM实现。

    Java以关键字synchronized为防止资源冲突提供了内置支持。当任务要执行被synchronized关键字保护的代码片段时,它将检查锁是否可用,然后获取锁,执行代码,释放锁。

    共享资源一般是以对象形式存在的内存片段,也可以是文件、输入/输出端口,或是打印机。要控制对共享资源的访问,首先将其包装进一个对象,然后把所有要访问该对象的方法标记为synchronized。如果某个任务中调用了由synchronized标记的方法,那么在这个线程从该方法返回之前,其他所有要调用该类中任何synchronized方法的线程都会被阻塞。

    下面是声明synchronized方法的方式:

        public synchronized void f() {}
        public synchronized void g() {}

    所有对象都自动含有单一的锁,当在对象上调用其任意synchronized方法时,此对象都被加锁,此时对该对象上的其他synchronized方法的调用只能等到前一个方法调用完毕并释放了锁之后才能被调用。即:对于某个特定对象来说,其所有synchronized方法共享同一个锁。

    针对每个类,也有一个锁,作为类的Class对象的一部分,所以synchronized static方法可以针对在类的范围内防止对static数据的并发访问。

    在处理共享数据时需要注意:针对类中每一个操作共享数据的方法,都必须被同步。

    同步控制EvenGenerator

    通过在EvenGenerator.java中加入synchronized关键字,可以防止共享资源被多个线程同时操作:

    public class EvenGenerator extends IntGenerator {
        private int currentEvenValue = 0;
        public synchronized int next() {
            ++currentEvenValue;
            ++currentEvenValue;
            return currentEvenValue;
        }
        public static void main(String[] args) {
            EvenChecker.test(new EvenGenerator());
        }   
    }   

    使用显式的Lock对象

    Java SE5还提供了显式的互斥机制:Lock对象必须被显式地创建、锁定和释放。因此,它与内建的锁形式相比,代码缺失优雅性,但对于解决某些类型的问题来说,它更加灵活。

    下面用显式的Lock重写上述示例:

    public class MutexEvenGenerator extends IntGenerator{
        private int currentEvenValue = 0;
        private Lock lock = new ReentrantLock();
        public int next() {
            lock.lock();
            try {
                ++currentEvenValue;
                ++currentEvenValue;
                return currentEvenValue;
            } finally {
                lock.unlock();
            }
        }
        public static void main(String[] args) {
            EvenChecker.test(new MutexEvenGenerator());
        }
    }

    我们在紧跟着对lock()的调用处,放置了try-finally以确保unlock()一定会被调用。并且,在try子句中的return也保证了unlock()不会被过早的调用。

    尽管使用显式的Lock增加了代码量,但如果在使用synchronized时抛出了异常,则无法进行清理工作以维护系统使其处于良好状态,而使用显式的Lock对象,则可以通过finally子句将系统维护在正确的状态了。

    通常情况下,只有在解决特殊问题时,才使用显式的Lock对象:

    public class AttemptLocking {
        private ReentrantLock lock = new ReentrantLock();
        public void untimed() {
            boolean captured = lock.tryLock();
            try {
                System.out.println("tryLock(): " + captured);
            } finally {
                if(captured)
                    lock.unlock();
            }
        }
        public void timed() {
            boolean captured = false;
            try {
                captured = lock.tryLock(2, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            try {
                System.out.println("tryLock(2, TimeUnit.SECONDS): " + captured);
            } finally {
                if(captured)
                    lock.unlock();
            }
        }
        public static void main(String[] args) {
            final AttemptLocking al = new AttemptLocking();
            al.untimed();
            al.timed();
            new Thread() {
                {
                    setDaemon(true);
                }
                public void run() {
                    al.lock.lock();
                    System.out.println("acquired");
                }
            }.start();
            Thread.yield();
            al.untimed();
            al.timed();
        }
    }
    • lock():获取锁,如果锁被使用,该线程将一直等待,直至锁被释放。
    • tryLock():尝试获取锁,可以获取返回true,获取不到返回false。
    • tryLock(long timeout, TimeUnit unit):在一定时间内获取锁。

    显式的Lock对象在加锁和释放锁方面,相对于内建的synchronized锁来说,具有更细粒度的控制力。

    21.3.3 原子性与易变性

    原子操作是不能被线程调度机制中断的操作,一旦操作开始,那么它一定可以在切换到其他线程之前执行完毕。

    原子性可以应用于除long和double之外的所有基本类型之上的简单操作:读取和写入。由于JVM会将64位的读取和写入当作两个分离的32位操作来执行,所以long和double类型则不具备原子性。在Java SE5之后,我们也可以使用volatile关键字使它们具备原子性。

    原子操作可由线程机制来包装其不可中断,但有时看似安全的原子操作,实际上也可能不安全。

    在多处理器系统上,相对于单处理器系统而言,可视性问题比原子性问题要多得多。一个任务做出的修改,即使是原子性的,对其他任务也可能是不可视的。例如,修改只是暂时性地存储在本地处理器的缓存中。因此不同的任务对应用的状态有不同的视图。

    volatile关键字确保了应用中的可视性:如果一个域被声明为volatile,那么只要对该域产生了写操作,所有的读操作都可以看到这个修改。即便使用了本地缓存,volatile域会立即被写入主存中,而读取操作就发生在主存中。

    synchronized关键字也会导致向主存中刷新,如果一个域由synchronized方法或语句块来防护,则不必将其设置为volatile。

    一个任务所做的任何写入操作对该任务来说都是可视的。

    在Java中,赋值与返回操作是具有原子性的。

    如果盲目应用原子性概念,则会发生错误:

    public class AtomicityTest implements Runnable{
        private int i = 0;
        public int getValue() { return i; }
        private synchronized void evenIncrement() {
            i++;
            i++;
        }
        public void run() {
            while(true)
                evenIncrement();
        }
        
        public static void main(String[] args) {
            ExecutorService exec = Executors.newCachedThreadPool();
            AtomicityTest at = new AtomicityTest();
            exec.execute(at);
            while(true) {
                int val = at.getValue();
                if(val % 2 != 0) {
                    System.out.println(val);
                    System.exit(0);
                }
            }
        }
    }

    在本例中,尽管return是原子性操作,但是缺少同步使得其数值可以在evenIncrement()方法在执行第一个递增操作之后,第二个递增操作之前被获取到。解决方案是:getValue()和evenIncrement()都声明为synchronized。这样,由于锁的机制,同一时间只有一个线程可以访问到该类中的被synchronized的修饰的其中一个方法。

    21.3.4 原子类

    Java SE5引入了诸如AtomicInteger、AtomicLong、AtomicReference等特殊的原子性变量类,它们提供了下面形式的原子性条件更新操作:

    boolean compareAndSet(expect, update);

    这些类具备在机器级别上的原子性,使用它们可以进行性能调优:

    public class AtomicIntegerTest implements Runnable{
        private AtomicInteger i = new AtomicInteger(0);
        public int getValue() { return i.get(); }
        private void evenIncrement() { i.addAndGet(2); }
        public void run() {
            while(true)
                evenIncrement();
        }
        
        public static void main(String[] args) {
            new Timer().schedule(new TimerTask() {
                public void run() {
                    System.err.println("Aborting");
                    System.exit(0);
                }
            }, 5000);
            ExecutorService exec = Executors.newCachedThreadPool();
            AtomicIntegerTest at = new AtomicIntegerTest();
            exec.execute(at);
            while(true) {
                int val = at.getValue();
                if(val % 2 != 0) {
                    System.out.println(val);
                    System.exit(0);
                }
            }
        }
    }

    我们通过使用AtomicInteger重写了AtomicityTest.java,即使没有用synchronized关键字,程序也不会失败,而使用Timer则可以在5秒钟后关闭程序。但通常情况下,使用锁要更安全一些。不管是synchronized关键字,还是显式的Lock对象。

    21.3.5 临界区

    有时,我们只是希望防止多个线程同时访问方法内部的部分代码而不是整个方法,而这部分代码所形成的代码片段被称为临界区,它也可以使用synchronized关键字建立。

    synchronized被用来指定某个对象,此对象的锁被用来对花括号内的代码进行同步控制:

        synchronized(syncObject) {
            // This code can be accessed
            // by only one tash at a time
        }

    这也被称为同步控制块:线程在进入此段代码前,必须得到syncObject对象的锁,如果其他线程已经得到了这个锁,那么就得等到锁被释放后,该线程才能进入临界区。

    通过使用同步控制块,可以使多个任务访问对象的时间性能得到显著提高:

    class Pair {
        private int x, y;
        public Pair(int x, int y) {
            this.x = x;
            this.y = y;
        }
        public Pair() { this(0, 0); }
        public int getX() { return x; }
        public int getY() { return y; }
        public void incrementX() { x++; }
        public void incrementY() { y++; }
        public String toString() { return "x: " + x + ", y: " + y; }
        public class PairValuesNotEqualException extends RuntimeException {
            public PairValuesNotEqualException() {
                super("Pair values not equal: " + Pair.this);
            }
        }
        public void checkState() {
            if(x != y)
                throw new PairValuesNotEqualException();
        }
    }
    
    abstract class PairManager { 
        AtomicInteger checkCounter = new AtomicInteger(0);
        protected Pair p = new Pair();
        private List<Pair> storage = Collections.synchronizedList(new ArrayList<Pair>());
        public synchronized Pair getPair() {
            return new Pair(p.getX(), p.getY());
        }
        protected void store(Pair p) {
            storage.add(p);
            try {
                TimeUnit.MILLISECONDS.sleep(20);
            } catch (InterruptedException e) {}
        }
        public abstract void increment();
    }
    
    class PairManaer1 extends PairManager {
        public synchronized void increment() {
            p.incrementX();
            p.incrementY();
            store(getPair());
        }
    }
    class PairManaer2 extends PairManager {
        public void increment() {
            Pair temp;
            synchronized (this) {
                p.incrementX();
                p.incrementY();
                temp = p;
            }
            store(temp);
        }
    }
    
    class PairManipulator implements Runnable {
        private PairManager pm;
        public PairManipulator(PairManager pm) {
            this.pm = pm;
        }
        public void run() {
            while(true) 
                pm.increment();
        }
        public String toString() {
            return "Pair: " + pm.getPair() + " checkCounter = " + pm.checkCounter.get(); 
        }
    }
    
    class PairChecker implements Runnable {
        private PairManager pm;
        public PairChecker(PairManager pm) {
            this.pm = pm;
        }
        public void run() {
            while(true) {
                pm.checkCounter.incrementAndGet();
                pm.getPair().checkState();
            }
        }
        
    }
    
    public class CriticalSection {
        static void testApproaches(PairManager pm1,PairManager pm2) {
            ExecutorService exec = Executors.newCachedThreadPool();
            PairManipulator p1 = new PairManipulator(pm1);
            PairManipulator p2 = new PairManipulator(pm2);
            PairChecker pc1 = new PairChecker(pm1);
            PairChecker pc2 = new PairChecker(pm2);
            exec.execute(p1);
            exec.execute(p2);
            exec.execute(pc1);
            exec.execute(pc2);
            try {
                TimeUnit.MILLISECONDS.sleep(100);
            } catch (InterruptedException e) {
                System.out.println("Sleep interrupted");
            }
            System.out.println("p1: " + p1 + "\np2: " + p2);
            System.exit(0);
        }
        public static void main(String[] args) {
            PairManager pm1 = new PairManaer1();
            PairManaer2 pm2 = new PairManaer2();
            testApproaches(pm1, pm2);
        }
    }

    在本例中,Pair不是线程安全的,为了使用该非线程安全的现有类,我们通过创建PairManager来持有Pair对象,并控制对它的一切访问。PairManager使用了模板方法设计模式,将变化的部分定义为抽象方法。

    PairManager的两个实现类分别以同步方法和同步控制块进行线程安全的管理。我们通过睡眠的方式模拟耗时操作,两种方式的区别是:耗时操作分别在临界区内和临界区外。这样前者对锁的控制时间在递增任务上较长,而在检测任务上对锁的控制时间自然就短了。

    我们也可以使用显式的Lock对象来创建临界区:

    class ExplicitPairManager1 extends PairManager {
        private Lock lock = new ReentrantLock();
        public void increment() {
            lock.lock();
            try {
                p.incrementX();
                p.incrementY();
                store(getPair());
            } finally {
                lock.unlock();
            }
        }
    }
    
    class ExplicitPairManager2 extends PairManager {
        private Lock lock = new ReentrantLock();
        public void increment() {
            Pair pair;
            lock.lock();
            try {
                p.incrementX();
                p.incrementY();
                pair = getPair();
            } finally {
                lock.unlock();
            }
            store(pair);
        }
    }
    
    public class ExplicitCriticalSection {
        public static void main(String[] args) {
            PairManager pm1 = new ExplicitPairManager1();
            PairManager pm2 = new ExplicitPairManager2();
            CriticalSection.testApproaches(pm1, pm2);
        }
    }

    21.3.6 在其他对象上同步

    在CriticalSection.java的示例中,我们使用synchronized (this)对被调用的当前对象进行加锁,那么该对象中的其他同步方法和临界区在该对象被锁时将无法被调用。

    当然,我们可以对其他对象进行同步,那么不同的任务则可以同时访问该对的同步方法和临界区

    class DualSynch {
        private Object syncObject = new Object();
        public synchronized void f() {
            for (int i = 0; i < 5; i++) {
                System.out.println("f()");
                Thread.yield();
            }
        }
        public void g() {
            for (int i = 0; i < 5; i++) {
                System.out.println("g()");
                Thread.yield();
            }
        }
    }
    public class SyncObject {
        public static void main(String[] args) {
            final DualSynch ds = new DualSynch();
            new Thread() {
                public void run() {
                    ds.f();
                }
            }.start();
            ds.g();
        }
    }

    可以看到,这两个同步是相互独立的,并不会因为一个线程对其中一个同步块进行访问导致另一个线程堵塞。

    21.3.7 线程本地存储

    防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享:线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。

    创建和管理线程本地存储可以由java.lang.ThreadLocal类来实现:

    class Accessor implements Runnable {
        private final int id;
        public Accessor(int id) { this.id = id; }
        public void run() {
            while(!Thread.currentThread().isInterrupted()) {
                ThreadLocalVariableHolder.increment();
                System.out.println(this);
                Thread.yield();
            }
        }
        public String toString() {
            return "#" + id + ": " + ThreadLocalVariableHolder.get();
        }
    }
    
    public class ThreadLocalVariableHolder {
        private static ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
            private Random rand = new Random(66);
            protected synchronized Integer initialValue() {
                return rand.nextInt(10000);
            }
        };
        public static void increment() {
            value.set(value.get() + 1);
        }
        public static int get() { return value.get(); }
        public static void main(String[] args) throws Exception {
            ExecutorService exec = Executors.newCachedThreadPool();
            for (int i = 0; i < 5; i++) 
                exec.execute(new Accessor(i));
            TimeUnit.SECONDS.sleep(3);
            exec.shutdownNow();
        }
    }

    ThreadLocal对象通常当作静态域存储:

    • ThreadLocal.get():返回与线程相关联的对象的副本。
    • ThreadLocal.set(T value):将参数插入到为其线程存储的对象中,并返回原有对象。

    当我们对ThreadLocal变量进行操作时,并不需要对其同步,ThreadLocal保证了它不会冲突。可以看到,每个单独的线程跟踪自己的计数值,即被分配了独立的存储区域。

    21.4 终结任务

    在前面的示例中,我们通过变量canceled来标记是否终止任务。下面的示例也通过此方式来终止任务,并演示了如何进行资源共享。

    21.4.1 装饰性花园

    下面的程序为了记录每天通过多个大门进入公园的总人数:

    class Count {
        private int count = 0;
        public synchronized int increment() {
            return ++count;
        }
        public synchronized int value() {
            return count;
        }
    }
    
    class Entrance implements Runnable {
        private final int id;
        private static Count count = new Count();
        private static List<Entrance> entrances = new ArrayList<Entrance>();
        private int number = 0;
        private static volatile boolean canceled = false;
        public static void cancel() { canceled = true; }
        public Entrance(int id) {
            this.id = id;
            entrances.add(this);
        }
        public void run() {
            while(!canceled) {
                synchronized (this) {
                    ++number;
                }
                System.out.println(this + " Total: " + count.increment());
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    System.out.println("sleep interrupted");
                }
            }
            System.out.println("Stopping " + this);
        }
        public synchronized int getValue() { return number; }
        public String toString() {
            return "Entrance " + id + ": " + getValue();
        }
        public static int getTotalCount() {
            return count.value();
        }
        public static int sumEntrances() {
            int sum = 0;
            for (Entrance entrance : entrances) 
                sum += entrance.getValue();
            return sum;
        }
    }
    
    public class OrnamentalGarden {
        public static void main(String[] args) throws Exception {
            ExecutorService exec = Executors.newCachedThreadPool();
            for (int i = 0; i < 5; i++) 
                exec.execute(new Entrance(i));
            TimeUnit.SECONDS.sleep(3);
            Entrance.cancel();
            exec.shutdown();
            if(!exec.awaitTermination(250, TimeUnit.MILLISECONDS))
                System.out.println("Some tasks were not terminated!");
            System.out.println("Total: " + Entrance.getTotalCount());
            System.out.println("Sum of Entrances: " + Entrance.sumEntrances());
        }
    }

    在本例中,Count对象用于记录花园参观者总人数,而变量number则记录从某个大门进入花园的人数。 

    • 静态方法Entrance.cancel():将终止条件设置为true。
    • ExecutorService.shutdown():防止新任务提交,当前线程在完成shutdown()被调用前所提交的所有任务后,安全退出。
    • ExecutorService.awaitTermination(long timeout, TimeUnit unit):等待每个任务结束,如果所有任务在指定时间之前完成,返回true,否则返回false。

    21.4.2 在阻塞时终结

    在上述示例中,Entrance.run()在其循环中包含了对sleep()的调用,如果没有变量canceled标志,我们很有可能对处理睡眠状态的任务进行终止,而此时线程处于阻塞状态。

    线程状态

    一个线程可以处于以下四种状态之一:

    1. 新建:当线程被创建时,此时它已经分配了必需的系统资源,并执行了初始化,已经有资格获取CPU时间了,之后调度器将把该线程转变为可运行状态或阻塞状态。

    2. 就绪:在此状态下,只要调度器把时间片分配给线程,线程就可以运行。

    3. 阻塞:线程能够运行,但有某个条件阻止了它的运行。当线程处于阻塞状态时,调度器将忽略线程,不会分配线程任何CPU时间,直到线程进入就绪状态。

    4. 死亡:处于死亡或终止状态的线程将不再是可调度的,并且再也不会得到CPU时间。

    进入阻塞状态

    一个任务进入阻塞状态,可能有如下原因:

    1. 通过调用sleep()使任务进入休眠状态。
    2. 通过调用wait()使线程挂起,直到线程得到了notify()或notifyAll()消息,进入就绪状态。
    3. 任务在等待某个输入/输出完成。
    4. 任务试图在某个对象上调用其同步控制方法,但对象锁已被占用。

    如果我们希望终止处于阻塞状态的任务,必须强制该任务跳出阻塞状态。

    21.4.3 中断

    Thread.interrupt():将线程设置为中断状态。如果在中断状态下执行一个阻塞操作,则会抛出InterruptedException异常。

    Executor也对此进行了支持,ExecutorService.shutdownNow():对该执行器启动的所有线程都设置为中断状态。如果希望中断单一任务,则可以通过ExecutorService.submit(Callable task)来执行任务。此时会返回一个Future对象,Future.cancel()则可以中断某个特定任务。

    下面的示例用Executor展示了基本的interrupt()用法:

    class SleepBlocked implements Runnable {
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(100);
            } catch (InterruptedException e) {
                System.out.println("InterruptedException");
            }
            System.out.println("Exiting SleepBlocked.run()");
        }
    }
    class IOBlocked implements Runnable {
        private InputStream in;
        public IOBlocked(InputStream in) { this.in = in; }
        public void run() {
            try {
                System.out.println("Waiting for read()");
                in.read();
            } catch (IOException e) {
                if(Thread.currentThread().isInterrupted()) {
                    System.out.println("Interrupted from blocked I/O");
                } else {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("Exiting IOBlocked.run()");
        }
    }
    class SynchronizedBlocked implements Runnable {
        public synchronized void f() {
            while(true)
                Thread.yield();
        }
        public SynchronizedBlocked() {
            new Thread() {
                public void run() {
                    f();
                }
            }.start();;
        }
        public void run() {
            System.out.println("Trying to call f()");
            f();
            System.out.println("Exiting SynchronizedBlocked.run()");
        }
    }
    
    public class Interrupting {
        private static ExecutorService exec = Executors.newCachedThreadPool();
        
        static void test(Runnable r) throws InterruptedException {
            Future<?> f = exec.submit(r);
            TimeUnit.MILLISECONDS.sleep(100);
            System.out.println("Interrupting " + r.getClass().getName());
            f.cancel(true);
            System.out.println("Interrupt sent to " + r.getClass().getName());
        }
        public static void main(String[] args) throws Exception {
            test(new SleepBlocked());
            test(new IOBlocked(System.in));
            test(new SynchronizedBlocked());
            TimeUnit.SECONDS.sleep(3);
            System.out.println("Aborting with System.exit(0)");
            System.exit(0);
        }
    }

    上面的每个任务都表示了一种不同类型的阻塞。并且,可以发现:我们可以中断由睡眠引起的阻塞,但无法中断由获取同步锁或执行I/O操作引起的阻塞。这也意味着I/O具有锁住我们多线程程序的潜在可能。

    对于这类问题,也有一个有效的解决方案,即关闭导致任务阻塞的底层资源:

    public class CloseResource {
        public static void main(String[] args) throws Exception {
            ExecutorService exec = Executors.newCachedThreadPool();
            ServerSocket server = new ServerSocket(8080);
            InputStream sockteInput = new Socket("localhost",8080).getInputStream();
            exec.execute(new IOBlocked(sockteInput));
            exec.execute(new IOBlocked(System.in));
            TimeUnit.MILLISECONDS.sleep(100);
            System.out.println("Shutting down all threads");
            exec.shutdownNow();
            TimeUnit.SECONDS.sleep(1);
            System.out.println("Closing " + sockteInput.getClass().getName());
            sockteInput.close();
            TimeUnit.SECONDS.sleep(1);
            System.out.println("Closing " + System.in.getClass().getName());
            System.in.close();
        }
    }

    该程序也显示,一旦底层资源被关闭,任务将解除阻塞。

    在第18章介绍的nio类库也提供了更人性化的I/O中断:

    class NIOBlocked implements Runnable {
        private final SocketChannel sc;
        public NIOBlocked(SocketChannel sc) { this.sc = sc; }
        public void run() {
            try {
                System.out.println("Waiting for read() in " + this);
                sc.read(ByteBuffer.allocate(1));
            } catch (ClosedByInterruptException e) {
                System.out.println("ClosedByInterruptException");
            } catch (AsynchronousCloseException e) {
                System.out.println("AsynchronousCloseException");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            System.out.println("Exiting NIOBlocked.run() " + this);
        }
    }
    public class NIOInterruption {
        public static void main(String[] args) throws Exception {
            ExecutorService exec = Executors.newCachedThreadPool();
            ServerSocket server = new ServerSocket(8080);
            InetSocketAddress address = new InetSocketAddress("localhost",8080);
            SocketChannel sc1 = SocketChannel.open(address);
            SocketChannel sc2 = SocketChannel.open(address);
            Future<?> f = exec.submit(new NIOBlocked(sc1));
            exec.execute(new NIOBlocked(sc2));
            exec.shutdown();
            TimeUnit.SECONDS.sleep(1);
            f.cancel(true);
            TimeUnit.SECONDS.sleep(1);
            sc2.close();
        }
    }

    在这里我们对任务2调用了shutdown()而不是shutdownNow(),并在最后对该任务所用的管道进行了关闭,发现被阻塞的nio通道会自动响应中断。

    被互斥所阻塞

    如果我们尝试在一个对象上调用其synchronized方法,而该对象锁已被占用,那么调用任务将被挂起,直至该锁可获得。下面的示例说明了同一个任务可以获得多次同一个对象锁:

    public class MultiLock {
        public synchronized void f1(int count) {
            if(count-- > 0) {
                System.out.println("f1() calling f2() with count " + count);
                f2(count);
            }
        }
        public synchronized void f2(int count) {
            if(count-- > 0) {
                System.out.println("f2() calling f1() with count " + count);
                f1(count);
            }
        }
        public static void main(String[] args) {
            final MultiLock multiLock = new MultiLock();
            new Thread() {
                public void run() {
                    multiLock.f1(10);
                }
            }.start();
        }
    }

    无论在任何时刻,只要任务以不可中断的方式被阻断,那么都有潜在的可能锁住程序。Java SE5并发类库中添加了一个特性,即ReentrantLock上阻塞的任务具备可以被中断的能力:

    class BlockedMutex {
        private Lock lock = new ReentrantLock();
        public BlockedMutex() {
            lock.lock();
        }
        public void f() {
            try {
                lock.lockInterruptibly();
                System.out.println("lock acquired in f()");
            } catch (InterruptedException e) {
                System.out.println("Interrupted from lock acquisition in f()");
            }
        }
    }
    class Blocked2 implements Runnable {
        BlockedMutex blocked = new BlockedMutex();
        public void run() {
            System.out.println("Waiting for f() in BlockedMutex");
            blocked.f();
            System.out.println("Broken out of blocked call");
        }
    }
    public class Interrupting2 {
        public static void main(String[] args) throws Exception {
            Thread t = new Thread(new Blocked2());
            t.start();
            TimeUnit.SECONDS.sleep(1);
            System.out.println("Issuing t.interrupt()");
            t.interrupt();
        }
    }

    ReentrantLock.lockInterruptibly():允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待而直接返回,这时不用获取锁,而会抛出一个InterruptedException。

    21.4.4 检查中断

    只有在任务要进入阻塞操作中,或已经在阻塞操作内部时,调用interrupt()方法才会发生中断。如果线程已经经过阻塞状态,此时如果想调用interrupt()以停止某个任务,则需要通过interrupted()来检查中断状态,并清除中断状态

    class NeedsCleanup {
        private final int id;
        public NeedsCleanup(int ident) {
            id = ident;
            System.out.println("NeedsCleanup " + id);
        }
        public void cleanup() {
            System.out.println("Cleaning up " + id);
        }
    }
    
    class Blocked3 implements Runnable {
        private volatile double d = 0.0;
        public void run() {
            try {
                while(!Thread.interrupted()) {
                    NeedsCleanup n1 = new NeedsCleanup(1);
                    try {
                        System.out.println("Sleeping");
                        TimeUnit.SECONDS.sleep(1);
                        NeedsCleanup n2 = new NeedsCleanup(2);
                        try {
                            System.out.println("Calculating");
                            for (int i = 0; i < 2500000; i++) 
                                d = d + (Math.PI + Math.E) / d;
                            System.out.println("Finished time-consuming operation");
                        } finally {
                            n2.cleanup();
                        }
                    } finally {
                        n1.cleanup();
                    }
                }
                System.out.println("Exiting via while() test");
            } catch (InterruptedException e) {
                System.out.println("Exiting via InterruptedException");
            }
        }
    }
    public class InterruptingIdiom {
        public static void main(String[] args) throws Exception {
            if(args.length != 1) {
                System.out.println("usage: java InterruptingIdiom delay-in-mS");
                System.exit(1);
            }
            Thread t = new Thread(new Blocked3());
            t.start();
            TimeUnit.MILLISECONDS.sleep(new Integer(args[0]));
            t.interrupt();
        }
    }

    由于在Java中缺乏自动的析构器调用,因此所有需要清理的对象创建操作后都必须紧跟try-finally子句,从而使得无论程序发生什么异常,清理都会发生。

    21.5 线程之间的协作

    通过前面的学习,我们知道了当使用线程来同时运行多个任务时,可以通过使用锁来同步两个任务的行为,从而使得在任意时刻,只有一个任务可以访问公共资源。

    下一步是学习如何使任务彼此之间可以协作,以使得多个任务可以一起工作去解决某个问题。在某些任务中,有些是可以并行执行,而有些任务则需要在特定任务完成之后才能执行

    当任务协作时,关键问题是这些任务之间的握手。我们同样使用了互斥的特性:互斥能够确保只有一个任务可以响应某个信号,从而根除任何可能的竞争条件。在互斥之上,我们为任务添加了一种途径,可以将其自身挂起,知道某些外部条件发生变化。

    21.5.1 wait()与notifyAll()

    wait()使得线程挂起,并等待某个条件发生变化,通常该条件由另一个任务来改变。只有在notify()或notifyAll()发生时,该任务才会被唤醒并去检查所产生的变化。

    通过前面的学习,我们知道:对sleep()和yield()的调用并不会释放对象锁。但对wait()的调用将释放锁,即其他任务可以获得该对象锁。

    wait()方法有两种形式:

    • wait():无限制等待,直到线程接收到notify()或notifyAll()
    • wait(long timeout):在指定时间内等待,当线程接收到notify()、notifyAll()或时间到期后,该线程将继续执行。

    需要注意的是:wait()、notify()以及notifyAll()都是基类Object的一部分,而不是Thread的一部分。并且,只能在同步控制方法或同步控制块内调用这些方法,即调用这些方法的前提是必须拥有对象锁。

    下面的示例演示了wait()与notify()的简单用法:

    class Car {
        private boolean waxOn = false;
        public synchronized void waxed() {
            waxOn = true;
            notifyAll();
        }
        public synchronized void buffed() {
            waxOn = false;
            notifyAll();
        }
        public synchronized void waitForWaxing() throws InterruptedException {
            while(waxOn == false)
                wait();
        }
        public synchronized void waitForBuffing() throws InterruptedException {
            while(waxOn == true)
                wait();
        }
    }
    
    class WaxOn implements Runnable {
        private Car car;
        public WaxOn(Car c) { car = c; }
        public void run() {
            try {
                while(!Thread.interrupted()) {
                    System.out.println("Wax on! ");
                    TimeUnit.MILLISECONDS.sleep(200);
                    car.waxed();
                    car.waitForBuffing();
                }
            } catch (InterruptedException e) {
                System.out.println("Exiting via interrupt");
            }
            System.out.println("Ending Wax On task");
        }
    }
    class WaxOff implements Runnable {
        private Car car;
        public WaxOff(Car c) { car = c; }
        public void run() {
            try {
                while(!Thread.interrupted()) {
                    car.waitForWaxing();
                    System.out.println("Wax off! ");
                    TimeUnit.MILLISECONDS.sleep(200);
                    car.buffed();
                }
            } catch (InterruptedException e) {
                System.out.println("Exiting via interrupt");
            }
            System.out.println("Ending Wax Off task");
        }
    }
    
    public class WaxOMatic {
        public static void main(String[] args) throws InterruptedException {
            Car c = new Car();
            ExecutorService exec = Executors.newCachedThreadPool();
            exec.execute(new WaxOff(c));
            exec.execute(new WaxOn(c));
            TimeUnit.SECONDS.sleep(5);
            exec.shutdownNow();
        }
    }

    在本例中有两个任务:涂蜡和抛光,并且抛光之前首先得进行涂蜡。Car中的变量waxOn表示涂蜡状态。通过判断waxOn的状态并调用wait()方法,我们可以实现涂蜡与抛光的顺序不可逆性。

    错失的信号

    当两个线程使用notify()/wait()或notifyAll()/wait()进行协作时,有可能会错过某个信号。例如:

            T1:
            synchronized (sharedMonitor) {
                <setup condition for T2>
                sharedMonitor.notify();
            }
            
            T2:
            while(someCondition) {
                // Point 1
                synchronized (sharedMonitor) {
                    sharedMonitor.wait();
                }
            }

    < setup condition for T2 >是改变T2运行条件的动作。假设T2对someCondition求值并发现其为true,但在Point1处,线程调度器切换到了T1,T1将执行其设置,然后调用notify()。当T2得以继续执行时,则会盲目进入wait()。由于错失了notify(),T2将无线等待,从而产生死锁。

    该问题的解决方案是防止在someCondition变量上产生竞争条件:

            T2:
            synchronized (sharedMonitor) {
                while(someCondition) {
                    sharedMonitor.wait();
                }
            }

    现在,如果T1首先执行,当控制返回T2时,它将发现条件发生变化,从而不会进入wait()。

    21.5.2 notify()与notifyAll()

    notify()会唤醒众多等待同一个锁的任务中的其中一个。而notifyAll()则会唤醒等待这个锁的所有任务:

    class Blocker {
        synchronized void waitingCall() {
            try {
                while(!Thread.interrupted()) {
                    wait();
                    System.out.print(Thread.currentThread() + " ");
                }
            } catch (InterruptedException e) {
                // OK to exit this way 
            }
        }
        synchronized void prod() { notify(); }
        synchronized void prodAll() { notifyAll(); }
    }
    
    class Task implements Runnable {
        static Blocker blocker = new Blocker();
        public void run() { blocker.waitingCall(); }
    }
    class Task2 implements Runnable {
        static Blocker blocker = new Blocker();
        public void run() { blocker.waitingCall(); }
    }
    
    
    public class NotifyVsNotifyAll {
        public static void main(String[] args) throws InterruptedException {
            ExecutorService exec = Executors.newCachedThreadPool();
            for (int i = 0; i < 5; i++) 
                exec.execute(new Task());
            exec.execute(new Task2());
            Timer timer = new Timer();
            timer.scheduleAtFixedRate(new TimerTask() {
                boolean prod = true;
                public void run() {
                    if(prod) {
                        System.out.print("\nnotify() ");
                        Task.blocker.prod();
                        prod = false;
                    } else {
                        System.out.print("\nnotifyAll() ");
                        Task.blocker.prodAll();
                        prod = true;
                    }
                }
            }, 400, 400);
            TimeUnit.SECONDS.sleep(5);
            timer.cancel();
            System.out.println("\nTimer canceled");
            TimeUnit.MILLISECONDS.sleep(500);
            System.out.print("Task2.blocker.prodAll() ");
            Task2.blocker.prodAll();
            TimeUnit.MILLISECONDS.sleep(500);
            System.out.println("\nShutting down");
            exec.shutdownNow();
        }
    }

    21.5.3 生产者与消费者

    在一个饭店中,有一个厨师和服务员。服务员必须等待厨师准备好膳食,而厨师在准备好时,会通知服务员,之后服务员上菜,然后返回继续等待:


    展开全文
  • 并发编程-进程

    2019-02-19 21:56:00
    单核+道,实现个进程的并发执行 进程与程序的区别 程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。 一 操作系统作用: 1:隐藏丑陋复杂硬件接口,提供良好抽象接口 2:管理、调度进程,...

    进程:正在进行的一个过程或者说一个任务。而负责执行任务则是cpu。

    单核+多道,实现多个进程的并发执行

    进程与程序的区别

    程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。

    一 操作系统的作用:
        1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
        2:管理、调度进程,并且将多个进程对硬件的竞争变得有序
    
    #二 多道技术:
        1.产生背景:针对单核,实现并发
        ps:
        现在的主机一般是多核,那么每个核都会利用多道技术
        有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,会被调度到4个
        cpu中的任意一个,具体由操作系统调度算法决定。
        
        2.空间上的复用:如内存中同时有多道程序
        3.时间上的复用:复用一个cpu的时间片
           强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样
                才能保证下次切换回来时,能基于上次切走的位置继续运行
    

      

    并发与并行

    一 并发:是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发,(并行也属于并发)

    二 并行:同时运行,只有具备多个cpu才能实现并行

    单核下,可以利用多道技术,多个核,每个核也都可以利用多道技术(多道技术是针对单核而言的

             有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4,

             一旦任务1遇到I/O就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术

             而一旦任务1的I/O结束了,操作系统会重新调用它(需知进程的调度、分配给哪个cpu运行,由操作系统说了算),可能被分配给四个cpu中的任意一个去执行

    开启子进程的两种方式

    #方式一:函数
    from multiprocessing import Process
    import time
    
    def task(name):
        print('%s is running' %name)
        time.sleep(3)
        print('%s is done' %name)
    
    if __name__ == '__main__':
        p = Process(target=task,args=('子进程1',))
        p.start()
    
        print('zhu')
    
    
    # 方式二:类
    from multiprocessing import Process
    import time
    
    class MyProcess(Process):
        def __init__(self,name):
            super().__init__()  # 继承父类
            self.name=name
        def run(self):
            print('%s is running' %self.name)
            time.sleep(3)
            print('%s is done' %self.name)
    
    if __name__ == '__main__':
        p = MyProcess('子进程1')
        p.start()
    
        print('zhu')

     

    转载于:https://www.cnblogs.com/hexiaorui123/p/10403618.html

    展开全文
  • 并发:个单元同时、并行被执行,并发执行单元共享资源访问就很容易导致竞态。 竞态: 假设有一个设备,执行单元A其写入3000个字符’a’而另一个执行单元B其写入4000个’b’,第三个执行单元C读取...
  • 并发进程

    千次阅读 热门讨论 2014-02-10 20:58:24
     首先与时间有关问题,因为临界区中共享变量运行个进程访问,造成了与时间有关 问题,为了解决这个问题,引入了PV操作用来实现临界区 管理。同时,PV操作也可以解决进程互斥和同步
  • 随着我们扩展到更高流量和需求水平,对多并发执行线程需求也越来越大。 因此,由依赖项注入器管理的对象角色非常重要。 单例这种需求一个特别重要例子。 在每分钟处理数百个请求大型Web应用程序...
  • 1、并发(concurrency)指的是多执行单元同时、并行被执行,而并发的执行单元共享资源(硬件资源和软件上全局变量、静态变量等)访问则很容易导致竞态(race condition)。   2、在设计自己驱动程序时...
  • 本报告介绍了ESG 集团对多个数据库管理软件产品进行的并发数据摄取和实时查询性能验证测试。测试结果表明,InterSystems IRIS 数据平台可在摄取上亿条记录同时执行数百万条查询,响应时间达到微秒级,其性能优于...
  • 进程有自己堆栈和局部变量,但线程之间没有单独地址空间,所以进程的程序要比多线程的程序健壮。 在进程切换时候需要耗费资源较大,效率差很,对于一些要求同时进行并且又要共享某些变量的并发操作只能
  • 另一个程序对这个程序的干扰不可复现,所以操作系统需要在并发环境下确定程序运行结果,因此需要对程序运行过程施加某种制约。 进程:描述和管理程序的运行过程。 进程定义:进程是程序在某个数据集合上一次运行...
  • 本文的主要内容是对第六,七,八篇文章进行总结。 总结: 《任务执行》的主要内容其实就是说执行一个线程任务最好的方式就是使用线程池。在此基础之上,介绍了4中线程池的性质和其生命周期的管理。 《任务的...
  • 进程是对正在运行程序的一个抽象。 进程概念起源于操作系统,是操作系统最核心概念。 程序仅仅只是一堆代码而已,而进程指程序的运行过程。 #操作系统作用: 1:隐藏丑陋复杂硬件接口,提供良好...
  • 操作系统介绍: 进程:即正在执行的一个过程;进程是对正在运行的程序的一个...道技术(针对单核,实现并发):道指程序道技术实现是为了解决程序竞争或者说共享同一个资源(比如CPU)有...
  • 要编写正确的并发程序,关键问题在于:访问共享的可变状态需要正确的管理。 一、可见性(变量的更新操作通知到其他线程) 1.个线程读写时内存的可见问题。因为java内存模型导致的。在之后的博文中会介绍内存...
  • 以一个整体的形式暴露给操作系统管理,里面包含各种资源的调用,内存的管理,网络接口的调用等,各种资源管理的集合,就可以称为进程,进程要操作cpu,必须先创建一个线程,所有在同一个进程里的线程共享同一...
  • 进程:程序正在执行的过程,就是一个正在执行的任务,而负责执行任务就是cpu 操作系统:操作系统就是一个协调、管理和控制计算机硬件资源和...道技术:道技术中的多道指的是多程序道技术实现为了解...
  • 程序所有事物,在任意时刻都只能执行一个步骤. 并发的多面性 并发解决问题大体上可以分为"速度"和"设计可管理性"两种 并发通常提高运行在单处理器上的程序的性能. 实现并发最直接方式在操作系统...
  • 所谓管程:指的是管理共享变量以及共享变量操作过程,让它们支持并发。翻译为 Java 就是管理成员变量和成员方法,让这个类线程安全一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作...
  • 第二个线程异步化执行相比于同步执行来说,异步执行能够很好优化程序的处理性能提升并发吞吐量同时,也带来了很麻烦,举个简单例子 线程对于共享变量访问带来安全性问题 一个变量 i. 假如一个线程去...
  • java高并发小结

    2019-01-10 19:13:41
    一、线程 1.线程使用可以提升程序的性能。 2.线程如果没有同步,操作的执行顺序不可预测。...要编码线程安全代码,其核心在于要状态访问操作进行管理,特别共享和可变状态访问。 2.竞态条件:在并...
  • 随着处理器核数增加,随着实现更高吞吐量不断增长需求,线程API变得非常流行,Java提供了它自己的多线程框架Executor Framework。 Executor 框架什么? Executor Framework包含一组用于有效管理工作线程...
  • Linux并发控制

    2018-10-11 19:17:06
    内核代码可抢占,因此,我们驱动程序的代码可能在任何时候丢失处理器独占,而拥有处理器进程可能正在调用驱动程序代码。设备中断异步事件,也会导致代码并发执行。 只要可能,就应该避免资源共享,...
  • 对象共享构建稳健的并发程序必须要正确使用线程和锁编写线程代码核心在于要对状态访问操作进行管理, 特别是对共享和可变状态访问对象状态是指存储在状态变量()中数据例如实例或静态域1. 可见性...
  • java并发编程

    2020-05-22 21:25:03
    并发的是cpu处理个线程里指令轮流,因为有一个调度器逐渐,windows时间片最短20ms所以感觉不到线程轮流执行,也就是微观串行,宏观并行 并行 并行指的是一个多核cpu,几个核心同时处理个线程,也...
  • ​进程就是一个正在执行的文件/程序,是对各种资源管理的集合, ​进程不具有执行的能力 ​每个应用是以一个整体的形式暴露给操作系统去管理,里面包含对各种资源的调用,内存的管理,网络接口的调用等等 ​...
  • 线程作为操作系统调度最小单元,个线程能够同时执行,这将明显提升程序的性能,在多核环境中表现得更加明显。但是过多创建线程和线程不当管理也容易造成问题。本章将着重介绍Java并发编程基础知识,从...
  • 线程作为操作系统调度最小单元,个线程能够同时执行,这将显著提升程序性能,在多核环境中表现得更加明显。但是,过多地创建线程和线程不当管理也容易造成问题。本章将着重介绍Java并发编程基础知识,从...
  • Java并发技术研究

    2018-04-02 17:27:09
    1.JAVA并发编程基础 Java从诞生开始就选择了内置对多线程...一个Java程序从main()方法开始执行,然后按照既定代码逻辑执行,看似没有其他线程参与,但实际上Java程序天生就是线程程序,因为执行main()方法...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 597
精华内容 238
关键字:

多程序并发执行是对的管理