精华内容
下载资源
问答
  • Python多线程线程同步

    千次阅读 2019-03-25 23:05:17
    线程同步的真实意思和字面意思恰好相反。 线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。 Python threading模块提供了Lock/RLock、Condition、queue...

    当多个线程同时读写同一份共享资源的时候,可能会引起冲突。 这时候,我们需要引入线程“同步”机制,即各位线程之间要有个先来后到,不能一窝蜂挤上去抢作一团。 线程同步的真实意思和字面意思恰好相反。 线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。
    Python threading模块提供了Lock/RLock、Condition、queue、Event等对象来实现线程同步。
    1. Lock/RLock对象
    Lock是比较低级的同步原语,当被锁定以后不属于特定的线程。一个所有两种状态:locked和unlocked。如果锁处于unlocked状态,acquire()方法将其修改为locked并立即返回;如果锁已处于locked状态,则阻塞当前线程并等待其他线程释放锁,然后将其修改为locked并立即返回。release()方法用来将锁的状态由locked修改为unlocked并立即返回,如果锁已经处于unlocked状态,调用该方法将抛出异常。
    可重入锁RLock对象也是一种常用的线程同步原语,可以被同一个线程acquire()多次。当处于locked状态时,某线程拥有该锁;当处于unlocked状态时,该锁不属于任何线程。
    RLock对象的acquire() / release()调用对可以嵌套,仅当最后一个或者最外层release()执行结束后,锁才被设置为unlocked。

    Lock对象成员如下:

    方法描述
    acquire()获得锁。该方法等待锁被解锁,将其设置为locked并返回True。
    release()释放锁。当锁被锁定时,将其重置为解锁并返回。如果锁未锁定,则会引发RuntimeError。
    locked()如果锁被锁定,返回True。

    例1:使用RLock/Lock实现线程同步:

    import threading
    import time
    
    
    # 自定义线程类
    class MyThread(threading.Thread):
        def __init__(self):
            threading.Thread.__init__(self)
    
        # 重写线程代码
        def run(self):
            global x
            # 获得锁
            lock.acquire()
            for i in range(3):
                x = x + i
            time.sleep(2)
            print(x)
            # 释放锁
            lock.release()
    
    
    # 创建锁
    lock = threading.RLock()
    # lock = threading.Lock()
    t1 = []
    for i in range(10):
        # 创建线程
        t = MyThread()
        t1.append(t)
    x = 0
    for i in t1:
        # 启动线程
        i.start()
    

    2. Condition对象
    使用Condition对象可以在某些事件触发后才处理数据,可以用于不同线程之间的通信或通知,以实现更高级别的同步。Condition对象除了具有acquire() / release()方法之外,还有wait()、notify()和notify_all()等方法。

    方法描述
    acquire()获取底层锁。此方法等待底层锁被解锁,将其设置为locked并返回True。
    notify(n=1)在此条件下最多唤醒n个等待的任务(默认为1)。如果没有任务在等待,则该方法是no-op。必须在调用此方法之前获取锁,并在调用后不久释放锁。如果使用未锁定的锁调用,则会引发RuntimeError错误。
    locked()如果获得了底层锁,则返回True。
    notify_all()唤醒所有在此条件下等待的任务。此方法的作用类似于notify(),但会唤醒所有等待的任务。必须在调用此方法之前获取锁,并在调用后不久释放锁。如果使用未锁定的锁调用,则会引发RuntimeError错误。
    release()释放底层锁。当在未锁定的锁上调用时,将引发RuntimeError。
    wait()等待通知。如果调用此方法时调用任务没有获得锁,则会引发RuntimeError。这个方法释放底层锁,然后阻塞它,直到它被notify()或notify_all()调用唤醒。一旦被唤醒,条件将重新获得锁,该方法将返回True。
    wait_for(predicate)等待predicate变为true。predicate必须是可调用的,其结果将被解释为布尔值。最后一个值是返回值。

    例2:使用Condition对象实现线程同步:

    import threading
    
    
    # 生产者类
    class Producer(threading.Thread):
        def __init__(self, thread_name):
            threading.Thread.__init__(self, name=thread_name)
    
        # 重写线程代码
        def run(self):
            global x
            # 获得锁
            con.acquire()
            if x == 20:
                # 等待通知
                con.wait()
            else:
                print("\nProducer: ", end=' ')
            for i in range(20):
                print(x, end=' ')
                x = x + 1
            print(x)
            con.notify()
            # 释放锁
            con.release()
    
    
    # 消费者类
    class Consumer(threading.Thread):
        def __init__(self, thread_name):
            threading.Thread.__init__(self, name=thread_name)
    
        # 重写线程代码
        def run(self):
            global x
            # 获得锁
            con.acquire()
            if x == 0:
                # 等待通知
                con.wait()
            else:
                print("\nConsumer: ", end=' ')
            for i in range(20):
                print(x, end=' ')
                x = x-1
            print(x)
            con.notify()
            # 释放锁
            con.release()
    
    
    # 创建锁
    con = threading.Condition()
    x = 0
    p = Producer('Producer')
    c = Consumer('Consumer')
    p.start()
    c.start()
    p.join()
    c.join()
    print('After Producer and Consumer all done:', x)
    

    3. queue对象
    queue模块实现多生产者、多消费者队列。当信息必须在多个线程之间安全地交换时,它在线程编程中特别有用。此模块中的Queue类实现所有必需的锁定语义。queue模块提供了Queue、LifoQueue或PriorityQueue对象。
    队列对象(Queue、LifoQueue或PriorityQueue)提供以下描述的公共方法:

    方法描述
    qsize()返回队列的大致大小。注意,qsize() > 0不保证后续get()不会阻塞,qsize() < maxsize也不保证put()不会阻塞。
    empty()如果队列为空,返回True,否则返回False。如果empty()返回True,则不能保证对put()的后续调用不会阻塞。类似地,如果empty()返回False,则不能保证对get()的后续调用不会阻塞。
    full()如果队列已满,返回True,否则返回False。如果full()返回True,则不能保证对get()的后续调用不会阻塞。类似地,如果full()返回False,则不能保证对put()的后续调用不会阻塞。
    put(item, block=True, timeout=None)将项放入队列。如果可选的参数block=True, timeout=None(缺省值),则在空闲插槽可用之前,如果有必要,将阻塞。如果timeout是一个正数,那么它将阻塞最多的超时秒,如果在这段时间内没有可用的空闲插槽,则引发完整的异常。否则(block为false),如果一个空闲插槽立即可用,则将一个项放到队列中,否则引发完全异常(在这种情况下忽略超时)。
    put_nowait(item)相当于put(item, False)。
    get(block=True, timeout=None)从队列中删除并返回一个项。如果可选的block=true, timeout=None(缺省值),则在项目可用之前,如果有必要,将阻塞。如果timeout是一个正数,那么它将阻塞最多的超时秒,如果在这段时间内没有可用的项,则引发空异常。否则(block为false),返回一个立即可用的项,否则引发空异常(在这种情况下忽略超时)。
    get_nowait()等价于get(False)。提供了两种方法来支持跟踪已加入队列的任务是否已被守护进程使用者线程完全处理。
    task_done()指示已完成先前排队的任务。由队列使用者线程使用。对于用于获取任务的每个get(),后续对task_done()的调用告诉队列任务上的处理已经完成。如果join()当前处于阻塞状态,那么在处理完所有项之后,它将继续运行(这意味着对于已经放入队列()的每个项,都收到了task_done()调用)。如果调用次数超过放置在队列中的项的次数,则引发ValueError。
    join()阻塞,直到获取和处理队列中的所有项。每当向队列添加项时,未完成任务的数量就会增加。每当使用者线程调用task_done()来指示检索了该项并完成了对该项的所有工作时,计数就会下降。当未完成任务的计数降为零时,join()解块。

    例3:使用Queue对象实现线程同步:

    def worker():
        while True:
            item = q.get()
            if item is None:
                break
            do_work(item)
            q.task_done()
    
    q = queue.Queue()
    threads = []
    for i in range(num_worker_threads):
        t = threading.Thread(target=worker)
        t.start()
        threads.append(t)
    
    for item in source():
        q.put(item)
    
    # block until all tasks are done
    q.join()
    
    # stop workers
    for i in range(num_worker_threads):
        q.put(None)
    for t in threads:
        t.join()
    
    

    4. Event对象
    Event对象是一种简单的线程同步通信技术,一个线程设置Event对象,另一个线程等待Event对象。

    方法描述
    wait()等待事件被设置。如果事件被设置,立即返回True。否则阻塞,直到另一个任务调用set()。
    set()设置事件。所有等待事件设置的任务将立即被唤醒。
    clear()清除(取消)事件。等待on wait()的任务现在将阻塞,直到再次调用set()方法。
    is_set()如果设置了事件,则返回True。

    例4:使用Event对象实现线程同步:

    import threading
    
    
    # 自定义线程类
    class MyThread(threading.Thread):
        def __init__(self, thread_name):
            threading.Thread.__init__(self, name=thread_name)
    
        # 重写线程代码
        def run(self):
            global my_event
            if my_event.isSet():
                my_event.clear()
                # 等待通知
                my_event.wait()
                print(self.getName())
            else:
                print(self.getName())
                my_event.set()
    
    
    # 创建锁
    my_event = threading.Event()
    my_event.set()
    t1 = []
    
    for i in range(10):
        t = MyThread(str(i))
        t1.append(t)
    
    for t in t1:
        t.start()
    
    展开全文
  • python的多线程线程同步方式

    千次阅读 2019-07-17 09:37:23
    1.线程执行 join与setDaemon 1.子线程在主线程运行结束后,会继续执行完,如果给子线程设置为守护线程(setDaemon=True),主线程运行结束子线程即结束; 2 .如果join()线程,那么主线程会等待子线程执行完再执行...

    1.线程执行

    join与setDaemon

    • 1.子线程在主线程运行结束后,会继续执行完,如果给子线程设置为守护线程(setDaemon=True),主线程运行结束子线程即结束;

    • 2 .如果join()线程,那么主线程会等待子线程执行完再执行。

    import threading
    import time
    
    
    def get_thread_a():
        print("get thread A started")
        time.sleep(3)
        print("get thread A end")
    
    
    def get_thread_b():
        print("get thread B started")
        time.sleep(5)
        print("get thread B end")
    
    
    if  __name__ == "__main__":
        thread_a = threading.Thread(target=get_thread_a)
        thread_b = threading.Thread(target=get_thread_b)
        start_time = time.time()
        thread_b.setDaemon(True)
        thread_a.start()
        thread_b.start()
        thread_a.join()   
    
        end_time = time.time()
        print("execution time: {}".format(end_time - start_time))
    

    thread_a是join,首先子线程thread_a执行,thread_b是守护线程,当主线程执行完后,thread_b不会再执行

    执行结果如下:

    get thread A started
    get thread B started
    get thread A end
    execution time: 3.003199815750122
    

    2.线程同步

    多线程间共享全局变量,多个线程对该变量执行不同的操作时,该变量最终的结果可能是不确定的(每次线程执行后的结果不同),如:对count变量执行加减操作 ,count的值是不确定的,要想count的值是一个确定的需对线程执行的代码段加锁。

    3.线程同步的方式

    3.1 锁机制

    在这里插入图片描述
    python对线程加锁主要有Lock和Rlock模块

    Lock:

    from threading import Lock
     
    lock = Lock()
    lock.acquire()
    lock.release()
    

    Lock有acquire()和release()方法,这两个方法必须是成对出现的,acquire()后面必须release()后才能再acquire(),否则会造成死锁

    Rlock:

    鉴于Lock可能会造成死锁的情况,RLock(可重入锁)对Lock进行了改进,RLock可以在同一个线程里面连续调用多次acquire(),但必须再执行相同次数的release()

    from threading import RLock
    
    lock = RLock()
    lock.acquire()
    lock.acquire()
    lock.release()
    lock.release()
    

    当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“同步阻塞”(参见多线程的基本概念)。

    直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

    3.2 Semaphore(信号量)

    信号量也提供acquire方法和release方法,每当调用acquire方法的时候,如果内部计数器大于0,则将其减1,如果内部计数器等于0,则会阻塞该线程,直到有线程调用了release方法将内部计数器更新到大于1位置。

    Semaphore(信号量)是计算机科学史上最古老的同步指令之一。Semaphore管理一个内置的计数器,每当调用acquire()时-1,调用release() 时+1。计数器不能小于0;当计数器为0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。

    基于这个特点,Semaphore经常用来同步一些有“访客上限”的对象,比如连接池。

    BoundedSemaphore 与Semaphore的唯一区别在于前者将在调用release()时检查计数器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。

    构造方法:
    Semaphore(value=1): value是计数器的初始值。

    import time
    import threading
    
    
    def get_thread_a(semaphore,i):
        time.sleep(1)
        print("get thread : {}".format(i))
        semaphore.release()
    
    
    def get_thread_b(semaphore):
        for i in range(10):
            semaphore.acquire()
            thread_a = threading.Thread(target=get_thread_a, args=(semaphore,i))
            thread_a.start()
    
    
    if __name__ == "__main__":
        semaphore = threading.Semaphore(2)
        thread_b = threading.Thread(target=get_thread_b, args=(semaphore,))
        thread_b.start()
    

    3.3 条件判断

    所谓条件变量,即这种机制是在满足了特定的条件后,线程才可以访问相关的数据。
      
    它使用Condition类来完成,由于它也可以像锁机制那样用,所以它也有acquire方法和release方法,而且它还有wait,notify,notifyAll方法。
    在这里插入图片描述

    """
    一个简单的生产消费者模型,通过条件变量的控制产品数量的增减,调用一次生产者产品就是+1,调用一次消费者产品就会-1.
    """
    
    """
    使用 Condition 类来完成,由于它也可以像锁机制那样用,所以它也有 acquire 方法和 release 方法,而且它还有
    wait, notify, notifyAll 方法。
    """
    
    import threading
    import queue,time,random
    
    class Goods:#产品类
        def __init__(self):
            self.count = 0
        def add(self,num = 1):
            self.count += num
        def sub(self):
            if self.count>=0:
                self.count -= 1
        def empty(self):
            return self.count <= 0
    
    class Producer(threading.Thread):#生产者类
        def __init__(self,condition,goods,sleeptime = 1):#sleeptime=1
            threading.Thread.__init__(self)
            self.cond = condition
            self.goods = goods
            self.sleeptime = sleeptime
        def run(self):
            cond = self.cond
            goods = self.goods
            while True:
                cond.acquire()#锁住资源
                goods.add()
                print("产品数量:",goods.count,"生产者线程")
                cond.notifyAll()#唤醒所有等待的线程--》其实就是唤醒消费者进程
                cond.release()#解锁资源
                time.sleep(self.sleeptime)
    
    class Consumer(threading.Thread):#消费者类
        def __init__(self,condition,goods,sleeptime = 2):#sleeptime=2
            threading.Thread.__init__(self)
            self.cond = condition
            self.goods = goods
            self.sleeptime = sleeptime
        def run(self):
            cond = self.cond
            goods = self.goods
            while True:
                time.sleep(self.sleeptime)
                cond.acquire()#锁住资源
                while goods.empty():#如无产品则让线程等待
                    cond.wait()
                goods.sub()
                print("产品数量:",goods.count,"消费者线程")
                cond.release()#解锁资源
    
    g = Goods()
    c = threading.Condition()
    
    pro = Producer(c,g)
    pro.start()
    
    con = Consumer(c,g)
    con.start()
    

    Condition内部有一把锁,默认是RLock,在调用wait()和notify()之前必须先调用acquire()获取这个锁,才能继续执行;当wait()和notify()执行完后,需调用release()释放这个锁,在执行with condition时,会先执行acquire(),with结束时,执行了release();所以condition有两层锁,最底层锁在调用wait()时会释放,同时会加一把锁到等待队列,等待notify()唤醒释放锁

    wait() :允许等待某个条件变量的通知,notify()可唤醒

    notify(): 唤醒等待队列wait()

    # encoding: UTF-8
    import threading
    import time
     
    # 商品
    product = None
    # 条件变量
    con = threading.Condition()
     
    # 生产者方法
    def produce():
        global product
        
        if con.acquire():
            while True:
                if product is None:
                    print 'produce...'
                    product = 'anything'
                    
                    # 通知消费者,商品已经生产
                    con.notify()
                
                # 等待通知
                con.wait()
                time.sleep(2)
     
    # 消费者方法
    def consume():
        global product
        
        if con.acquire():
            while True:
                if product is not None:
                    print 'consume...'
                    product = None
                    
                    # 通知生产者,商品已经没了
                    con.notify()
                
                # 等待通知
                con.wait()
                time.sleep(2)
     
    t1 = threading.Thread(target=produce)
    t2 = threading.Thread(target=consume)
    t2.start()
    t1.start()
    

    3.4 同步队列

    put方法和task_done方法,queue有一个未完成任务数量num,put依次num+1,task依次num-1.任务都完成时任务结束。
      在这里插入图片描述

    import threading
    import queue
    import time
    import random
    
    '''
    1.创建一个 Queue.Queue() 的实例,然后使用数据对它进行填充。
    2.将经过填充数据的实例传递给线程类,后者是通过继承 threading.Thread 的方式创建的。
    3.每次从队列中取出一个项目,并使用该线程中的数据和 run 方法以执行相应的工作。
    4.在完成这项工作之后,使用 queue.task_done() 函数向任务已经完成的队列发送一个信号。
    5.对队列执行 join 操作,实际上意味着等到队列为空,再退出主程序。
    '''
    
    class jdThread(threading.Thread):
        def __init__(self,index,queue):
            threading.Thread.__init__(self)
            self.index = index
            self.queue = queue
    
        def run(self):
            while True:
                time.sleep(1)
                item = self.queue.get()
                if item is None:
                    break
                print("序号:",self.index,"任务",item,"完成")
                self.queue.task_done()#task_done方法使得未完成的任务数量-1
    
    q = queue.Queue(0)
    '''
    初始化函数接受一个数字来作为该队列的容量,如果传递的是
    一个小于等于0的数,那么默认会认为该队列的容量是无限的.
    '''
    for i in range(2):
        jdThread(i,q).start()#两个线程同时完成任务
    
    for i in range(10):
        q.put(i)#put方法使得未完成的任务数量+1
    

    3.5 Event对象

    Event对象是一种简单的线程同步通信技术,一个线程设置Event对象,另一个线程等待Event对象。
    在这里插入图片描述

    import threading
    
    
    # 自定义线程类
    class MyThread(threading.Thread):
        def __init__(self, thread_name):
            threading.Thread.__init__(self, name=thread_name)
    
        # 重写线程代码
        def run(self):
            global my_event
            if my_event.isSet():
                my_event.clear()
                # 等待通知
                my_event.wait()
                print(self.getName())
            else:
                print(self.getName())
                my_event.set()
    
    
    # 创建锁
    my_event = threading.Event()
    my_event.set()
    t1 = []
    
    for i in range(10):
        t = MyThread(str(i))
        t1.append(t)
    
    for t in t1:
        t.start()
    
    
    展开全文
  • 当使用多线程访问同一个资源的时候,非常容易出现线程安全的问题(例如,当多个线程同时对一个数据进行修改的时候,会导致某些线程对数据的修改丢失)。 因此,需要采用同步机制来解决这种问题。而Java主要提供了三...

    当使用多线程访问同一个资源的时候,非常容易出现线程安全的问题(例如,当多个线程同时对一个数据进行修改的时候,会导致某些线程对数据的修改丢失)。

    因此,需要采用同步机制来解决这种问题。而Java主要提供了三种实现同步机制的方法。今天我们就来认识一下~~

    一、synchronized关键字

    在Java语言中,每个对象都有一个对象锁与之相关联,该锁表明对象在任何时候只允许被一个线程锁拥有,当一个线程调用对象的一段synchronized代码时,需要先获取这个锁,然后去执行相应的代码,执行结束之后,释放锁。

    而synchronized关键字主要有两种用法:synchronized方法synchronized块。此外,这个关键字还可以作用于静态方法、类或者某个实例,但这都对程序的效率有很大的影响

    1.synchronized方法。在方法的声明前主要有synchronized关键字,示例如下:

    public synchronized void mutiThreadAccess();

    只要把多个线程对类需要被同步的资源的操作放到mutiThreadAccess()方法中,就能保证这个方法在同一时刻只能被同一个线程访问,从而保证了多线程访问的安全性。然而,当一个方法的方法体规模非常大时,把该方法声明为synchronized会大大影响程序的执行效率。为了提高程序的效率,Java提供了synchronized块。

    2.synchronized块

    synchronized块既可以把任意的代码段声明为synchronized,也可以指定上锁的对象,有非常高的灵活性。其用法如下:

    synchronized(syncObject){
    
    //访问synchObject的代码
    
    }

    二、wait()方法与notify()方法

    当使用synchronized来修饰某个共享资源时,如果线程A1在执行synchronized代码,另一个线程A2也要同时执行同一个对象的同一synchronized代码时,线程A2将要等到线程A1执行完成之后,才能继续执行。在这种情况下可以使用wait()方法和notify()方法。

    在synchronized代码被执行期间,线程可以调用对象的wait()方法,释放对象锁,进入等待状态,并且可以调用notify()方法或者notifyAll()方法通知正在等待的其他线程。notify()方法仅唤醒一个线程(即等待队列中的第一个线程),并允许它去获得锁,notifyAll()方法唤醒所有等待这个对象的线程并允许他们去获得锁,但并不是让所有唤醒线程都去获取到锁,而是让他们去竞争。

    三、Lock

    JDK5新增加了Lock接口以及它的一个实现类ReentrantLock(重入锁),Lock也可以用来实现多线程的同步。具体而言,它提供了如下一些方法来实现多线程的同步:

    1.lock()。

    这个方法以阻塞的方式获取锁,也就是说,如果获取到锁了,立即返回;如果别的线程持有锁,当前线程就等待,知道获取到锁之后返回。

    2.tryLock()。

    这个方法与lock()方法不同,它以非阻塞的方式来获取锁。此外,它只是常识性地去获取一下锁,如果获取到了锁,立即返回true,否则立即返回false。

    3.tryLock(long timeout,TimeUnit unit)

    如果获取到锁,立即返回true,否则会等待参数给定的时间单元,在等待的过程中,如果获取到了锁,就返回true,如果等待超时则返回false。

    4.lockInterruptibly()

    如果获取了锁,立即返回;如果没有获取到锁,当前线程处于休眠状态,直到获取到锁,或者当前线程被别的线程中断(会受到InterruptedException异常)。它与lock()方法最大的区别在于如果lock()方法获取不到锁就会一直处于阻塞状态,而且还会忽略interrupt()方法。

    示例如下:

    package javatest;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class LockTest {
    
    	public static void main(String[] args)throws InterruptedException {
    		// TODO Auto-generated method stub
    		final Lock lock=new ReentrantLock();
    		lock.lock();
    		Thread t1=new Thread(new Runnable() {
    			public void run() {
    				try {
    					lock.lockInterruptibly();
    				}catch(InterruptedException e)
    				{
    					System.out.println("interrupted");
    				}
    			}
    		});
    		t1.start();
    		t1.interrupt();
    		Thread.sleep(1);
    	}
    }
    

    运行结果如下: 

    如果把lock.lockInterruptibly()替换为lock.lock(),编译器将会提示lock.lock()catch代码块无效,这是因为lock.lock()不会抛出异常,由此可见lock()方法会忽略interrupt()引发的异常。

     

    好啦,以上就是实现Java多线程同步的三种方法的相关总结,如果大家有什么更具体的发现或者发现文中有描述错误的地方,欢迎留言评论,我们一起学习呀~~

     

    Biu~~~~~~~~~~~~~~~~~~~~宫å´éªé¾ç«è¡¨æå|é¾ç«gifå¾è¡¨æåä¸è½½å¾ç~~~~~~~~~~~~~~~~~~~~~~pia!

    展开全文
  • 什么是线程同步? 当使用个线程来访问同一个数据时,非常容易出现线程安全问题(比如个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。 锁对象  在Java SE5.0引入ReentrantLock类。...

    什么是线程同步?
    当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。

    锁和条件的关键之处:

    • 锁用来保护代码片段,任意时刻只能有一个线程执行被保护的代码。
    • 锁可以管理试图进入保护代码片段的线程
    • 锁可以拥有一个或者多个相关的条件对象
    • 每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。

    锁对象 

    在Java SE5.0引入ReentrantLock类。Lock是Java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作。

    用ReentrantLock保护代码块的基本结构如下:

    myLock.lock(); //a ReentrantLock object
    try
    {
        critical section
    }
    finally
    {
        myLock.unlock();//确保代码抛出异常锁必须被释放
    }
    

    这一结构确保任何时刻只有一个线程进入临界区。一旦一个线程封锁了锁对象,其他任何线程都无法通过lock语句。当其他线程调用lock时,他们被阻塞,直到第一个线程释放锁对象。把解锁操作放在finally子句之内是至关重要的。如果在临界区的代码抛出异常,锁必须释放。否则,其他线程将永远阻塞。

    如果使用锁,就不能使用带资源的try语句。首先解锁方法名不是close。不过即使将他重命名,带资源的try语句也无法正常工作。他的首部希望声明一个新变量。但如果使用一个锁,你可能想使用多个线程共享的那个变量,而不是新变量。

    public class Bank
    {
       private Lock bankLock <span style="font-size:18px;"><span class="FrameTitleFont" size="+1"></span></span>= new ReentrantLock();//<span style="font-size:18px;"><span class="FrameTitleFont" size="+1"></span></span>ReentrantLock 实现了Lock接口
      ........
       
       public void transfer(int from, int to, double amount) 
       {
          bankLock.lock();
          try
          {
             System.out.print(Thread.currentThread());
             accounts[from] -= amount;
             System.out.printf(" %10.2f from %d to %d", amount, from, to);
             accounts[to] += amount;
             System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
          }
          finally
          {
             bankLock.unlock();
          }
       }
        
        public double getTotalBalance()
       {
          bankLock.lock();
          try
          {
             double sum = 0;
    
             for (double a : accounts)
                sum += a;
    
             return sum;
          }
          finally
          {
             bankLock.unlock();
          }
       }
    
    }
    

       假定一个线程调用transfer,在执行结束前被剥夺运行权。假定第二个线程也调用了transfer,由于第二个线程并不能获得锁,将在调用lock方法时被阻塞。他必须等待第一个线程完成transfer方法之后才能再度被激活。当第一个线程释放锁时,第二个线程才能开始运行。这样余额就不会出错了。

        每个Bank对象有自己的ReentrantLock对象,如果两个线程试图访问同一个Bank对象,那么锁以串行方式提供服务。但是,如果两个线程访问不同的Bank对象,每个线程得到不同的锁对象,两个线程都不会发生阻塞。两个线程在操作不同的Bank实例的时候,线程之间不会相互影响。

        锁是可重入的,因为线程可以重复的获得已经持有的锁。锁保持一个持有计数来跟踪对Lock方法的嵌套调用。线程每一次调用都用unlock来释放锁。由于这一特性,被一个锁保护的代码可以调用另一个使用相同锁的方法。例如,transfer方法调用getTotalBalance方法,这也会封锁bankLock对象,此时bankLock对象的持有计数为2,当getTotalBalance方法退出时,持有计数变为1,当transfer方法退出时,持有计数变为0。线程释放锁。通常,可能想要保护需若干个操作来更新或者检查共享对象的代码块。要确保这些操作完成后,另一个线程才能使用相同的对象。

        要留心临界区的代码,不要因为异常的抛出二跳出了临界区。如果在临界区代码结束之前抛出了异常,finally字句释放锁,但会使对象可能出于一种受损状态。

    条件对象

    通常,线程进入临界区,却发现在某一条件满足后才能执行。要使用一个条件对象来管理那些已经获得了一个锁,但是不能做有用工作的线程。(条件对象经常被称为条件变量)

    分析上文中的银行模拟程序,

    if(bank.getBalance(from)>=amount)
    transfer(from, to, amount);

    如果当前程序通过if条件判断,且在调用transfer之前被中断,在线程再次运行前,账户余额可能已经低于提款金额。必须确保没有其他线程在本检查余额与转账活动之间修改余额。通过使用锁来保护检查与转账动作来做到这一点:

    public void transfer(int from, int to, double amount) 
       {
          bankLock.lock();
          try
          {
             while (accounts[from] < amount)
             { 
                 //wait()
              }
             //transfer funds
           ........
          finally
          {
             bankLock.unlock();
          }
       }
    

    现在,当账户中没有足够的余额时,等待直到另一个线程向账户中注入资金。但是,这一线程刚刚获得了bankLock的排他性访问,因此别的线程没有进行存款操作的机会,这就是为什么需要用条件对象的原因。

         一个锁对象可以有一个或者多个相关的条件对象。可以用newCondition方法获得一个条件对象。习惯的给每一个条件对象命名为可以反应它所表达条件的名字。如sufficientFunds = bankLock.newCondition();

    如果transfer方法发现余额不足,它调用sufficientFunds.await();当前线程被他阻塞了,并放弃锁。我们希望这样可以等待另一个线程进行增加账户余额的操作。

        等待获得锁的线程和调用await方法的线程在本质上存在不同。一旦一个线程调用await方法,他进入该条件的等待集。当该锁可用时,该线程不能马上解除阻塞。相反,它处于阻塞状态,直到另一个线程调用同一条件上的signalAll方法时为止。

        当另一个线程转账时,它应该调用sufficientFunds.signalAll();这一调用重新激活因为这一条件等待的所有线程。当这些线程从等待集当中移除时,他们再次成为可运行的,调度器将再次激活它们。同时,他们试图重新进入该对象。一旦锁成为可用,他们将从await调用返回,获得该锁并从被阻塞的地方继续执行。

        此时,线程应该再次测试该条件。由于无法确保该条件被满足,signalAll方法仅仅通知正在等待的线程:此时有可能已经满足条件,值得再次去检测条件。

        最关重要的是最终需要某个其他线程调用signalAll方法。当一个线程调用await时,他没有办法自己激活自身,它寄希望于其他线程。如果没有其他线程重新来激活等待的线程,他就永远不再运行。导致死锁。如果所有其他线程被阻塞,最后一个线程再解除其他阻塞线程之前就调用await,那么它也被阻塞。没有线程解除其他阻塞线程,那么该程序就挂起。

        应该何时调用signalAll呢,在本例中,当一个账户的余额发生改变时,等待的线程就有机会检查余额。调用signalAll不会立即激活一个等待线程。它仅仅解除等待线程的阻塞,以便这些线程可以在当前线程同步推出后,通过竞争实现对对象的访问。当一个线程拥有某个条件的锁时,它仅仅可以在该条件上调用await,signalAll和signal方法。

    以下为完整的例子:

    synch/Bank.java
    
    package synch;
    
    import java.util.concurrent.locks.*;
    
    public class Bank
    {
       private final double[] accounts;
       private Lock bankLock;
       private Condition sufficientFunds;
    
       public Bank(int n, double initialBalance)
       {
          accounts = new double[n];
          for (int i = 0; i < accounts.length; i++)
             accounts[i] = initialBalance;
          bankLock = new ReentrantLock();
          sufficientFunds = bankLock.newCondition();
       }
    
       public void transfer(int from, int to, double amount) throws InterruptedException
       {
          bankLock.lock();
          try
          {
             while (accounts[from] < amount)
                sufficientFunds.await();
             System.out.print(Thread.currentThread());
             accounts[from] -= amount;
             System.out.printf(" %10.2f from %d to %d", amount, from, to);
             accounts[to] += amount;
             System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
             sufficientFunds.signalAll();
          }
          finally
          {
             bankLock.unlock();
          }
       }
       public double getTotalBalance()
       {
          bankLock.lock();
          try
          {
             double sum = 0;
    
             for (double a : accounts)
                sum += a;
    
             return sum;
          }
          finally
          {
             bankLock.unlock();
          }
       }
    
       public int size()
       {
          return accounts.length;
       }
    }
    

    synch/TransferRunnable.java
    
    package synch;
    
    public class TransferRunnable implements Runnable
    {
       private Bank bank;
       private int fromAccount;
       private double maxAmount;
       private int DELAY = 10;
    
       public TransferRunnable(Bank b, int from, double max)
       {
          bank = b;
          fromAccount = from;
          maxAmount = max;
       }
    
       public void run()
       {
          try
          {
             while (true)
             {
                int toAccount = (int) (bank.size() * Math.random());
                double amount = maxAmount * Math.random();
                bank.transfer(fromAccount, toAccount, amount);
                Thread.sleep((int) (DELAY * Math.random()));
             }
          }
          catch (InterruptedException e)
          {
          }
       }
    }
    
    synch/SynchBankTest
    
    package synch;
    
    public class SynchBankTest
    {
       public static final int NACCOUNTS = 100;
       public static final double INITIAL_BALANCE = 1000;
    
       public static void main(String[] args)
       {
          Bank b = new Bank(NACCOUNTS, INITIAL_BALANCE);
          int i;
          for (i = 0; i < NACCOUNTS; i++)
          {
             TransferRunnable r = new TransferRunnable(b, i, INITIAL_BALANCE);
             Thread t = new Thread(r);
             t.start();
          }
       }
    }
    




    展开全文
  • C++多线程同步(采用事件对象Event)

    千次阅读 2016-11-25 12:07:11
    C++多线程同步(采用事件对象Event)
  • 在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。synchronized既可以加在一段代码上,也可以加在方法上。 关键是,不要认为给方法或者...
  • java多线程同步的例子

    千次阅读 2018-05-30 23:03:28
    多线程访问共享的资源对象,为了避免错误,java提供三种解决机制同步代码块 synchronized code block同步方法 synchronized method同步锁 ReentrantLockpackage com.linchengshen._01.thread; //针对多线程并发访问...
  • 线程同步:处理多线程问题时,多个线程访问同一个对象,并且某个对象还想修改这个线程。这时候就需要线程同步线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面...
  •   1、确保线程互斥访问同步代码;   2、保证共享变量的修改能够及时可见;   3、有效解决指令重排序问题。   synrhronized关键字简洁、清晰、语义明确,因此即使有了Lock接口,使用的还是非常广泛。其应用层...
  • Java多线程同步优化的6种方案

    万次阅读 2020-06-24 17:44:34
    Java中可以使用锁来解决多线程的同步问题,保障了数据的一致性,但也会代理很多问题,本章总结了多线程同步的几种优化方案:包括读写锁、写时复制机制、锁细化等方案。
  • C++11 多线程同步

    千次阅读 2016-11-09 21:28:05
    出现数据竞争,一般会用临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)这四种方法来完成线程同步。 1、临界区 对于临界资源,多线程必须互斥地对它进行访问。每个...
  • C++多线程并发(二)---线程同步之互斥锁

    万次阅读 多人点赞 2019-03-20 00:08:29
    一、何为线程同步 在前一篇文章《C++多线程并发编程(一)—线程管理》中解释多线程并发时说到两个比较重要的概念: 多线程并发:在同一时间段内交替处理多个操作,线程切换时间片是很短的(一般为毫秒级),一个...
  • JAVA多线程——实现同步

    千次阅读 2018-07-26 17:20:33
    java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用, 从而保证了...
  • 【Java多线程-6】synchronized同步

    千次阅读 2020-03-31 15:35:49
    前文描述了Java多线程编程,多线程的方式提高了系统资源利用和程序效率,但多个线程同时处理共享的数据时,就将面临线程安全的问题。 例如,下面模拟这样一个场景:一个售票处有3个售票员,出售20张票。 public ...
  • windows系统多线程同步机制原理总结

    千次阅读 2018-12-24 21:24:33
    windows系统多线程同步机制原理总结 同步问题是开发过程中遇到的重要问题之一。同步是要保证在并发执行的环境中各个控制流可以有序地执行,包括对于资源的共享或互斥访问,以及代码功能的逻辑顺序。 为了保证多线程...
  • 多线程面试题(值得收藏)

    万次阅读 多人点赞 2019-08-16 09:41:18
    史上最强多线程面试47题(含答案),建议收藏 金九银十快到了,即将进入找工作的高峰期,最新整理的最全多线程并发面试47题和答案总结,希望对想进BAT的同学有帮助,由于篇幅较长,建议收藏后细看~ 1、并发编程三要素?...
  • 一、多线程同步关键字-synchronized1.概念 synchronized保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性。当多个并发线程访问同一个对象object中的同步...
  • 临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源...
  • C#多线程——线程同步

    万次阅读 2018-08-25 13:11:53
    个线程同时使用共享对象会造成很问题,同步这些线程使得对共享对象的操作能够以正确的顺序执行是非常重要的。 二、实现线程同步的方法: • 使用Mutex类 • 使用SemaphoreSlim类 • 使用AutoResetEvent类 • ...
  • //线程共同访问一个对象中的实例变量,则会出现"非线程安全"问题 class MyRunnable1 implements Runnable{ private int num = 10; public void run() { try { if(num &gt; 0) { System...
  • C++多线程并发(三)---线程同步之条件变量

    千次阅读 多人点赞 2019-05-03 12:43:12
    在前一篇文章《C++多线程并发编程(二)—线程同步之互斥锁》中解释了线程同步的原理和实现,使用互斥锁解决数据竞争访问问题,算是线程同步的加锁原语,用于排他性的访问共享数据。我们在使用mutex时,一般都会期望...
  • 多线程间的通信和同步

    千次阅读 多人点赞 2019-06-15 11:21:07
    最近看了很多关于网络编程和多线程的书,为了以后查看相关内容方便,整理了几本书的精华形成这篇博文,希望能帮助观看这篇博文的读者。 目录 一、什么是多线程? 二、为什么要创建线程 三、线程之间如何通信 四...
  • Qt多线程同步

    千次阅读 2018-09-15 12:06:04
    一、Qt中使用多线程时候,多线程同步就是一个不可避免的问题。多线程同步就是使多个线程在同时执行同一段代码的时候,有顺序的执行,不会出现同时有两个或者多个线程执行同一段代码的情况,特别是在对变量或者...
  • 前几篇:Java多线程编程-(1)-线程安全和锁Synchronized概念Java多线程编程-(2)-可重入锁以及Synchronized的其他基本特性Java多线程编程-(3)-线程本地ThreadLocal的介绍与使用Java多线程编程-(4)-线程间通信...
  • 多线程之间如何实现同步

    千次阅读 2019-07-01 11:56:53
    线程安全问题一般是发生再多线程环境,当多个线程同时共享一个全局变量或静态变量做写的操作时候,可能会发生数据冲突问题,也就是线程安全问题,在读的操作不会发生数据冲突问题 下面看个简单的买票例子 案例:需求...
  • java中实现多线程同步方式

    千次阅读 2018-05-22 14:46:21
    多线程实现方式 1)、实现Runnable接口,并实现run()方法 以下是主要步骤: 1、自定义类并实现Runnable接口,并实现run()方法。 2、创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象。 3...
  • c++实现多线程同步

    千次阅读 2018-05-23 16:01:27
    之所以需要线程同步,是因为个线程同时对一个数据对象进行修改操作时,可能会对数据造成破坏,下面是个线程同时修改同一数据造成破坏的例子: 1 #include &lt;thread&gt; 2 #include &lt;iostream&...
  • 多线程:解释线程同步的必要性

    千次阅读 2020-08-25 17:12:44
    为什么需要线程同步? 当个线程同时运行时,线程的调度由操作系统决定,程序本身无法决定。因此,任何一个线程都有可能在任何指令处被操作系统暂停,然后在某个时间段后继续执行。 这个时候,有个单线程模型下不...
  • java多线程同步5种方法

    万次阅读 多人点赞 2018-05-28 22:11:07
    二、为什么要线程同步因为当我们有个线程要同时访问一个变量或对象时,如果这些线程中既有读又有写操作时,就会导致变量值或对象的状态出现混乱,从而导致程序异常。举个例子,如果一个银行账户同时被两个线程操作...
  • C#多线程同步的几种方法

    千次阅读 2017-03-06 14:37:07
    1.为什么使用同步 多线程操作的时候我们知道要避免线程之间共享数据...lock是一种比较好用的简单的线程同步方式,它是通过为给定对象获取互斥锁来实现同步的。它是通过线程之间的互斥来达到同步效果的。用法如下: ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 525,338
精华内容 210,135
关键字:

多线程同步对象