`

线程123总结

 
阅读更多
线程同步的真实意思,其实是“排队”:几个线程之间要排队,一个一个对共享资源进行操作,而不是同时进行操作。

因此,关于线程同步,需要牢牢记住的第一点是:线程同步就是线程排队。同步就是排队。线程同步的目的就是避免线程“同步”执行。这可真是个无聊的绕口令。
关于线程同步,需要牢牢记住的第二点是 “共享”这两个字。只有共享资源的读写访问才需要同步。如果不是共享资源,那么就根本没有同步的必要。
关于线程同步,需要牢牢记住的第三点是,只有“变量”才需要同步访问。如果共享的资源是固定不变的,那么就相当于“常量”,线程同时读取常量也不需要同步。至少一个线程修改共享资源,这样的情况下,线程之间就需要同步。
关于线程同步,需要牢牢记住的第四点是:多个线程访问共享资源的代码有可能是同一份代码,也有可能是不同的代码;无论是否执行同一份代码,只要这些线程的代码访问同一份可变的共享资源,这些线程之间就需要同步。

为什么不在每一个对象内部都增加一个新的区域,专门用来加锁呢?这种设计理论上当然也是可行的。问题在于,线程同步的情况并不是很普遍。如果因为这小概率事件,在所有对象内部都开辟一块锁空间,将会带来极大的空间浪费。得不偿失。
于是,现代的编程语言的设计思路都是把同步锁加在代码段上。确切的说,是把同步锁加在“访问共享资源的代码段”上。这一点一定要记住,同步锁是加在代码段上的。

我们应该在代码段上加什么样的锁。这个问题是重点中的重点。这是我们尤其要注意的问题:访问同一份共享资源的不同代码段,应该加上同一个同步锁;如果加的是不同的同步锁,那么根本就起不到同步的作用,没有任何意义。
这就是说,同步锁本身也一定是多个线程之间的共享对象。

在Java里面,同步锁的概念就是这样的。任何一个Object Reference都可以作为同步锁。我们可以把Object Reference理解为对象在内存分配系统中的内存地址。


同一时刻,只有一个线程能够获得lock1的所有权,只有一个线程可以执行代码段A或者代码段B。其他竞争失败的线程只能暂停运行,进入到该同步锁的就绪(Ready)队列。
每一个同步锁下面都挂了几个线程队列,包括就绪(Ready)队列,待召(Waiting)队列等。比如,lock1对应的就绪队列就可以叫做lock1 - ready queue。每个队列里面都可能有多个暂停运行的线程。
注意,竞争同步锁失败的线程进入的是该同步锁的就绪(Ready)队列,而不是后面要讲述的待召队列(Waiting Queue,也可以翻译为等待队列)。就绪队列里面的线程总是时刻准备着竞争同步锁,时刻准备着运行。而待召队列里面的线程则只能一直等待,直到等到某个信号的通知之后,才能够转移到就绪队列中,准备运行。
成功获取同步锁的线程,执行完同步代码段之后,会释放同步锁。该同步锁的就绪队列中的其他线程就继续下一轮同步锁的竞争。成功者就可以继续运行,失败者还是要乖乖地待在就绪队列中。
因此,线程同步是非常耗费资源的一种操作。我们要尽量控制线程同步的代码段范围。同步的代码段范围越小越好。我们用一个名词“同步粒度”来表示同步代码段的范围。

同步锁模型只是最简单的同步模型。同一时刻,只有一个线程能够运行同步代码。
有的时候,我们希望处理更加复杂的同步模型,比如生产者/消费者模型、读写同步模型等。这种情况下,同步锁模型就不够用了。我们需要一个新的模型。这就是我们要讲述的信号量模型。
信号量模型的工作方式如下:线程在运行的过程中,可以主动停下来,等待某个信号量的通知;这时候,该线程就进入到该信号量的待召(Waiting)队列当中;等到通知之后,再继续运行。


原子仅仅是保证变量操作的原子性,但整个程序还需要考虑线程安全的。
AtomicLong aLong = new AtomicLong(10000);
aLong.addAndGet(x));
信号量仅仅是对池资源进行监控,但不保证线程的安全,因此,在使用时候,应该自己控制线程的安全访问池资源。
new Semaphore(size)

getSp().acquire(x);
getSp().release(x);


条件变量都实现了java.util.concurrent.locks.Condition接口,条件变量的实例化是通过一个Lock对象上调用newCondition()方法来获取的,这样,条件就和一个锁对象绑定起来了。因此,Java中的条件变量只能和锁配合使用,来控制并发程序访问竞争资源的安全。

myLock.lock();
Lock lock = new ReentrantLock();
myLock.unlock();

Java提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,在一定程度上提高了程序的执行效率。
ReadWriteLock lock = new ReentrantReadWriteLock(false);
myLock.readLock().lock();
myLock.readLock().unlock();

myLock.writeLock().lock();
myLock.writeLock().unlock();


Java定义了阻塞队列的接口java.util.concurrent.BlockingQueue,阻塞队列的概念是,一个指定长度的队列,如果队列满了,添加新元素的操作会被阻塞等待,直到有空位为止。同样,当队列为空时候,请求队列元素的操作同样会阻塞等待,直到有可用元素为止
Java为阻塞栈定义了接口:java.util.concurrent.BlockingDeque


线程的调度-让步
线程的让步使用Thread.yield()方法,yield() 为静态方法,功能是暂停当前正在执行的线程对象,并执行其他线程。

线程的调度-合并
应用场景是当一个线程必须等待另一个线程执行完毕才能执行时可以使用join方法。
Thread t1 = new MyThread1();
//t1线程合并到主线程中,主线程停止执行过程,转而执行t1线程,直到t1执行完毕后继续。
t1.join();

线程的调度-休眠

线程休眠的目的是使线程让出CPU的最简单的做法之一,线程休眠时候,会将CPU资源交给其他线程,以便能轮换执行,当休眠一定时间后,线程会苏醒,进入准备状态等待执行。

线程的交互

等待/通知,要记住的关键点是:
必须从同步环境内调用wait()、notify()、notifyAll()方法。线程不能调用对象上等待或通知的方法,除非它拥有那个对象的锁。
wait()、notify()、notifyAll()都是Object的实例方法。与每个对象具有锁一样,每个对象可以有一个线程列表,他们等待来自该信号(通知)。线程通过执行对象上的wait()方法获得这个等待列表。从那时候起,它不再执行任何其他指令,直到调用对象的notify()方法为止。如果多个线程在同一个对象上等待,则将只选择一个线程(不保证以何种顺序)继续执行。如果没有线程等待,则不采取任何特殊操作。

锁和同步,有一下几个要点:
1)、只能同步方法,而不能同步变量和类;
2)、每个对象只有一个锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步?
3)、不必同步类中所有的方法,类可以同时拥有同步和非同步方法。
4)、如果两个线程要执行一个类中的synchronized方法,并且两个线程使用相同的实例来调用方法,那么一次只能有一个线程能够执行方法,另一个需要等待,直到锁被释放。也就是说:如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
5)、如果线程拥有同步和非同步方法,则非同步方法可以被多个线程自由访问而不受锁的限制。
6)、线程睡眠时,它所持的任何锁都不会释放。
7)、线程可以获得多个锁。比如,在一个对象的同步方法里面调用另外一个对象的同步方法,则获取了两个对象的同步锁。
8)、同步损害并发性,应该尽可能缩小同步范围。同步不但可以同步整个方法,还可以同步方法中一部分代码块。
9)、在使用同步代码块时候,应该指定在哪个对象上同步,也就是说要获取哪个对象的锁
分享到:
评论

相关推荐

    gdb调试多线程程序总结

    使用 `break thread_test.c:123` 命令可以在所有线程中相应的行上设置断点。 使用 `thread apply ID1 ID2 command` 命令可以让一个或者多个线程执行 GDB 命令 command。 使用 `thread apply all command` 命令可以...

    Windows多线程编程

    unsigned int _tcs = _beginthread(_thread_func, 0, (void*)123); ``` ##### 3. 线程同步机制 在多线程环境中,为了保证数据的一致性和完整性,通常需要使用线程同步机制来控制对共享资源的访问。Windows提供了...

    QT线程QThread的推荐用法

    总结来说,QT线程QThread的推荐用法包括使用`QtConcurrent::run()`来执行一次性任务,以及通过`moveToThread()`方法将QObject实例移到新线程中长期运行。这些方法简化了多线程编程,提高了程序效率,并确保了与主线...

    libmpg123库,c++库,android端,armeabi-v7a系统

    总结,libmpg123库在Android armeabi-v7a系统上用于MP3音频解码,通过JNI实现Java与C++的交互。开发者需要熟悉C++编程、JNI机制以及Android的NDK环境,才能成功地集成和利用该库。通过优化解码流程,我们可以实现...

    基于事件的_NIO_多线程服务器

    #### 总结 基于事件的NIO多线程服务器是一种非常高效且灵活的网络服务器架构。通过合理的线程模型设计和事件驱动机制的应用,不仅提高了系统的并发处理能力,也极大地简化了业务逻辑的编写。对于开发高性能的网络...

    线程与进程

    #### 六、总结 线程和进程作为操作系统中最基本的执行单元,它们之间存在着重要的区别和联系。了解这些区别有助于开发者在设计多任务应用程序时做出合理的选择,从而达到优化程序性能的目的。无论是选择多进程还是...

    python多线程ping服务器在线情况

    #### 总结 本文介绍了一个基于Python的多线程方案,用于批量检测服务器的在线状态。通过使用`threading`模块和`Queue`队列,有效地实现了并发处理和任务分发,极大地提高了网络监控的效率。这种方案不仅适用于...

    QT线程二(线程的同步).doc

    总结来说,QT线程同步通过QMutex和QMutexLocker提供了有效的方法来管理多线程对共享资源的访问,确保程序的正确性和稳定性。在编写多线程代码时,正确使用这些同步机制至关重要,可以避免数据不一致性和程序崩溃等...

    基于IMS的远程多线程PGM研究与实现修改

    总结,这个项目旨在通过在IMS环境下引入多线程技术,对PGM协议进行改造,以提高其在远程通信中的效率和可靠性。这一研究对于提升多媒体服务的性能,尤其是大规模、实时性要求高的应用场景,具有重要的理论和实践价值...

    vb6的dll线程注入示例,CreateRemoteThread ,GetProcAddress

    总结起来,VB6的DLL线程注入示例主要依赖`CreateRemoteThread`和`GetProcAddress`这两个核心函数,它们使得我们能够在目标进程中运行自定义的DLL代码。通过理解这些原理和实践,开发者可以更好地掌握系统级别的编程...

    java实现两个线程交替打印的实例代码

    总结 在本篇文章中,我们讨论了如何使用 Java 实现两个线程交替打印的实例代码。我们使用了两种方法来实现线程交替打印:使用 ReentrantLock 和 Condition 实现线程交替打印,以及使用 LinkedTransferQueue 实现...

    基于ARM的多线程应用程序的设计73351.doc

    基于ARM架构的多线程应用程序设计主要涉及到操作系统中的并发执行和线程同步机制。生产者-消费者问题是多线程编程中的一个经典案例,它展示了如何在多个线程间共享资源并确保数据的一致性和正确性。在这个问题中,有...

    按键精灵多线程多开脚本开发教程.docx

    总结来说,多线程多开脚本开发是按键精灵高级应用的一个方面,它涉及到线程的创建、同步、数据共享以及文件读写等多个知识点。掌握这些技能,能够帮助开发者编写出更加灵活和高效的自动化脚本,满足各种需求。

    Java并发编程最全面试题 123道.pdf

    Java 并发编程的基础知识、多线程应用场景、并发编程的缺点、并发编程三个必要因素、Java 程序中如何保证多线程的运行安全、并行和并发的区别、多线程的好处和劣势、线程和进程的区别、上下文切换等进行总结。...

    android安卓app开发教程之--总结了50条安卓开发经验.zip

    (123代表不同线程,轮流插入一个记录),读和写均不会锁住db,读写交替并没有规律,执行次数和程度看cpu分配给哪个线程的时间片长 43. 选择正确的集合类型使你能够在集合性能与内存占用之间达到合理的平衡。除此...

    基于ARM的多线程应用程序的设计说明.doc

    总结,基于ARM的多线程应用程序设计涉及了硬件接口编程、网络通信、多线程编程等多个方面,展示了在嵌入式系统中如何高效利用资源、实现并发处理和远程控制的综合能力。通过这样的设计实践,可以深入理解和掌握多...

    gdb调试常用命令总结

    - 示例:`(gdb) break thread_test.c:123 thread all` 4. **向所有线程应用命令** - `threadapply all command`可以让所有线程执行指定的gdb命令。 - 示例:`(gdb) threadapply all bt` - 这条命令会在所有...

    StringBuffer基本数据类型的封装)总结共11页

    这个类被设计为线程安全的,适用于多线程环境,以避免线程冲突带来的问题。本总结共11页,将深入探讨`StringBuffer`类及其与基本数据类型相关的操作。 首先,`StringBuffer`是`StringBuilder`的线程安全版本。两者...

    浅谈linux模拟多线程崩溃和多进程崩溃

    当一个线程出现致命错误,如访问无效内存(如示例代码中的`memcpy(ptr, "123", 3);`),可能导致整个进程崩溃。在提供的代码中,`fun2`线程尝试释放`ptr`后再对其进行内存拷贝,这是典型的空指针异常,会触发段错误...

    PHP线程的内存回收问题_.docx

    #### 总结 PHP中的内存管理通过引用计数和引用状态实现了高效且灵活的内存回收机制。在处理复杂的引用关系时,开发者需要注意避免无意间引入难以追踪的错误。利用工具如Xdebug可以帮助理解和调试内存管理相关的问题...

Global site tag (gtag.js) - Google Analytics