并发危险:解决多线程代码中的 11 个常见的问题

并发危险:解决多线程代码中的 11 个常见的问题
并发危险:解决多线程代码中的 11 个常见的问题

并发危险:解决多线程代码中的11 个常见的问题

并发危险

解决多线程代码中的11 个常见的问题

Joe Duffy

目录

数据争用忘记同步粒度错误读写撕裂无锁定重新排序重新进入死锁锁保护戳记两步舞曲优先级反转实现安全性的模式不变性纯度隔离并发现象无处不在。服

务器端程序长久以来都必须负责处理基本并发编程模型,而随着多核处理器的日益普及,客户端程序也将需要执行一些任务。随着并发操作的不断增加,有关确保安

全的问题也浮现出来。也就是说,在面对大量逻辑并发操作和不断变化的物理硬件并行性程度时,程序必须继续保持同样级别的稳定性和可靠性。

与对应的顺序代码相比,正确设计的并发代码还必须遵循一些额外的规则。对内存的读写以及对共享资源的访问必须使用同步机制进行管制,以防发生冲突。另外,通常有必要对线程进行协调以协同完成某项工作。

这些附加要求所产生的直接结果是,可以从根本上确保线程始终保持一致并且保证其顺利向前推进。同步和协调对时间的依赖性很强,这就导致了它们具有不确定性,难于进行预测和测试。

些属性之所以让人觉得有些困难,只是因为人们的思路还未转变过来。没有可供学习的专门

API,也没有可进行复制和粘贴的代码段。实际上的确有一组基础概念需要您学习和适应。很可能随着时间的推移某些语言和库会隐藏一些概念,但如果您现在就

开始执行并发操作,则不会遇到这种情况。本文将介绍需要注意的一些较为常见的挑战,并针对您在软件中如何运用它们给出一些建议。

首先我将讨论在并发程序中经常会出错的一类问题。我把它们称为“安全隐患”,因为它们很容易发现并且后果通常比较严重。这些危险会导致您的程序因崩溃或内存问题而中断。当

从多个线程并发访问数据时会发生数据争用(或竞争条件)。特别是,在一个或多个线程写入一段数据的同时,如果有一个或多个线程也在读取这段数据,则会发生

这种情况。之所以会出现这种问题,是因为Windows 程序(如C++ 和Microsoft .NET Framework

之类的程序)基本上都基于共享内存概念,进程中的所有线程均可访问驻留在同一虚拟地址空间中的数据。静态变量和堆分配可用于共享。

请考虑下面这个典型的例子:static class Counter {

internal static int s_curr = 0;

internal static int GetNext() {

return s_curr++;

}

}Counter 的目标可能是想为GetNext 的每个调用分发一个新的唯一数字。但是,如果程序中的两个线程同时调用GetNext,则这两个线程可能被赋予相同的数字。原因是

s_curr++ 编译包括三个独立的步骤:

将当前值从共享的s_curr 变量读入处理器寄存器。

递增该寄存器。

将寄存器值重新写入共享s_curr 变量。

照这种顺序执行的两个线程可能会在本地从s_curr 读取了相同的值(比如42)并将其递增到某个值(比如

43),然后发布相同的结果值。这样一来,GetNext 将为这两个线程返回相同的数字,导致算法中断。虽然简单语句

s_curr++

看似不可分割,但实际却并非如此。忘记同步

这是最简单的一种数据争用情况:同步被完全遗忘。这种争用很少有良性的情况,也就是说虽然它们是正确的,但大部分都是因为这种正确性的根基存在问题。

这种问题通常不是很明显。例如,某个对象可能是某个大型复杂对象图表的一部分,而该图表恰好可使用静态变量访问,或在创建新线程或将工作排入线程池时通过将某个对象作为闭包的一部分进行传递可变为共享图表。

当对象(图表)从私有变为共享时,一定要多加注意。这称为发布,在后面的隔离上下文中会对此加以讨论。反之称为私有化,即对象(图表)再次从共享变为私有。

对这种问题的解决方案是添加正确的同步。在计数器示例中,我可以使用简单的联锁:static class Counter {

internal static volatile int s_curr = 0;

internal static int GetNext() {

return Interlocked.Increment(ref s_curr);

}

}它之所以起作用,是因为更新被限定在单一内存位置,还因为(这一点非常方便)存在硬件指令(LOCK INC),它相

当于我尝试进行原子化操作的软件语句。

或者,我可以使用成熟的锁定:static class Counter { internal static int s_curr = 0;

private static object s_currLock = new object();

internal static int GetNext() {

lock (s_currLock) {

return s_curr++;

}

}

}lock

语句可确保试图访问GetNext 的所有线程彼此之间互斥,并且它使用CLR System.Threading.Monitor 类。C++

程序使用CRITICAL_SECTION

来实现相同目的。虽然对这个特定的示例不必使用锁定,但当涉及多个操作时,几乎不可能将其并入单个互锁操作中。粒度错误

即使使用正确的同步对共享状态进行访问,所产生的行为仍然可能是错误的。粒度必须足够大,才能将必须视为原子的操作封装在此区域中。这将导致在正确性与缩小区域之间产生冲突,因为缩小区域会减少其他线程等待同步进入的时间。

例如,让我们看一看图 1 所示的银行帐户抽象。一切都很

正常,对象的两个方法(Deposit 和Withdraw)看起来不会发生并发错误。一些银行业应用程序可能会使用它们,而且不担心余额会因为并发访问而遭到损坏。

图1 银行帐户class BankAccount {

private decimal m_balance = 0.0M;

private object m_balanceLock = new object();

internal void Deposit(decimal delta) {

lock (m_balanceLock) { m_balance += delta; }

}

internal void Withdraw(decimal delta) {

lock (m_balanceLock) {

if (m_balance < delta)

throw new Exception("Insufficient funds");

m_balance -= delta;

}

}

}但是,如果您想添加一个Transfer 方法该怎么办?一种天真的(也是不正确的)想法会认为由于Deposit 和Withdraw 是安全隔离的,因此很容易就可以合并它们:class BankAccount {

internal static void Transfer(

BankAccount a, BankAccount b, decimal delta) {

Withdraw(a, delta);

Deposit(b, delta);

}

// As before

}这是不正确的。实际上,在执行Withdraw 与Deposit 调用之间的一段时间内资金会完全丢失。

正确的做法是必须提前对 a 和 b 进行锁定,然后再执行方法调用:class BankAccount {

internal static void Transfer(

BankAccount a, BankAccount b, decimal delta) {

lock (a.m_balanceLock) {

lock (b.m_balanceLock) {

Withdraw(a, delta);

Deposit(b, delta);

}

}

}

// As before

}事实证明,此方法可解决粒度问题,但却容易发生死锁。稍后,您会了解到如何修复它。读写撕裂

前所述,良性争用允许您在没有同步的情况下访问变量。对于那些对齐的、自然分割大小的字—例如,用指针分割大小的内容在32 位处理器中是32

位的(4 字节),而在64 位处理器中则是64 位的(8 字节)—

读写操作是原子的。如果某个线程只读取其他线程将要写入的单个变量,而没有涉及任何复杂的不变体,则在某些情况下您完全可以根据这一保证来略过同步。

但要注意。如果试图在未对齐的内存位置或未采用自然分割大小的位置这样做,可能会遇到读写撕裂现象。之所以发生撕裂现象,是因为此类位置的读或写实际上涉及多个物理内存操作。它们之间可能会发生并行更新,并进而导致其结果可能是之前的值和之后的值通过某种形式的组合。

例如,假设ThreadA 处于循环中,现在需要仅将0x0L 和0xaaaabbbbccccddddL 写入64 位变量s_x 中。ThreadB 在循环中读取它(参见图2)。

图2 将要发生的撕裂现象internal static volatile long s_x; void ThreadA() {

int i = 0;

while (true) {

s_x = (i & 1) == 0 ? 0x0L : 0xaaaabbbbccccddddL;

i++;

}

}

void ThreadB() {

while (true) {

long x = s_x;

Debug.Assert(x == 0x0L || x == 0xaaaabbbbccccddddL);

}

}您

可能会惊讶地发现ThreadB 的声明可能会被触发。原因是ThreadA 的写入操作包含两部分(高32 位和低32

位),具体顺序取决于编译器。ThreadB 的读取也是如此。因此ThreadB 可以见证值0xaaaabbbb00000000L 或

0x00000000aaaabbbbL。无锁定重新排序

时编写无锁定代码来实现更好的可伸缩性和可靠性是一种非常诱人的想法。这样做需要深入了解目标平台的内存模型(有关详细信息,请参阅Vance

Morrison 的文章"Memory Models:Understand the Impact of Low-Lock

Techniques in Multithreaded Apps",网址为

https://www.360docs.net/doc/4b17151271.html,/magazine/cc163715)。如果不了解或不注

意这些规则可能会导致内存重新排序错误。之所以发生这些错误,是因为编译器和处理器在处理或优化期间可自由重新排序内存操作。

例如,假设s_x 和s_y 均被初始化为值0,如下所示:internal static volatile int s_x = 0;

internal static volatile int s_xa = 0;

internal static volatile int s_y = 0;

internal static volatile int s_ya = 0;

void ThreadA() {

s_x = 1;

s_ya = s_y;

}

void ThreadB() {

s_y = 1;

s_xa = s_x;

}是

否有可能在ThreadA 和ThreadB 均运行完成后,s_ya 和s_xa 都包含值0?看上去这个问题很可笑。或者s_x = 1 或者s_y = 1 会首先发生,在这种情况下,其他线程会在开始处理其自身的更新时见证这一更新。至少理论上如此。遗憾的是,处理器随时都可能重新排序此代码,以使在写入之前加载操作更有效。您可以借助一个显式内存屏障来避免

此问题:void ThreadA() {

s_x = 1;

Thread.MemoryBarrier();

s_ya = s_y;

}.NET

Framework 为此提供了一个特定API,C++ 提供了

_MemoryBarrier

和类似的宏。但这个示例并不是想说明您应该在各处都插入内存屏障。它要说明的是在完全弄清内存模型之前,应避免使用无锁定代码,而且即使在完全弄清之后也

应谨慎行事。在

Windows(包括Win32 和.NET

Framework)中,大多数锁定都支持递归获得。这只是意味着,即使当前线程已持有锁但当它试图再次获得时,其要求仍会得到满足。这使得通过较小的原

子操作构成较大的原子操作变得更加容易。实际上,之前给出的BankAccount 示例依靠的就是递归获得:Transfer 对Withdraw 和Deposit 都进行了调用,其中每个都重复获得了Transfer 已获得的锁定。

是,如果最终发生了递归获得操作而您实际上并不希望如此,则这可能就是问题的根源。这可能是因为重新进入而导

致的,而发生重新进入的原因可能是由于对动态

代码(如虚拟方法和委托)的显式调用或由于隐式重新输入的代码(如STA 消息提取和异步过程调用)。因此,最好不要从锁定区域对动态方法进行调用。

例如,设想某个方法暂时破坏了不变体,然后又调用委托:class C {

private int m_x = 0;

private object m_xLock = new object();

private Action m_action = ...;

internal void M() {

lock (m_xLock) {

m_x++;

try { m_action(); }

finally {

Debug.Assert(m_x == 1);

m_x--;

}

}

}

}C

的方法M 可确保m_x 不发生改变。但会有很短的一段时间,m_x 会先递增1,然后再重新递减。对m_action

的调用看起来没有任何问题。遗憾的是,如果它是从 C 类用户接受的委托,则表示任何代码都可以执行它所请求的操作。这包括回调到同一实例的M

方法。如果发生了这种情况,finally 中的声明可能会被触发;同一堆栈中可能存在多个针对M

的活动的调用(即使您未直接执行此操作),这必然会导致m_x 包含的值大于1。当多个线程遇到死锁时,系统会直接停止响应。多篇《MSDN 杂志》文章都介绍了死锁的发生原因以及使死锁变得能够接受的一些方法,其中包括我自己的文章"No More Hangs:Advanced Techniques to Avoid and Detect Deadlocks in .NET Apps"(网址为

https://www.360docs.net/doc/4b17151271.html,/magazine/cc163618)以及Stephen Toub 的2007 年10 月.NET 相关问题专栏(网址为

https://www.360docs.net/doc/4b17151271.html,/magazine/cc163352),

因此这里只做简单的讨论。总而言之,只要出现了循环等待链—例如,ThreadA 正在等待ThreadB 持有的资源,而ThreadB

反过来也在等待ThreadA 持有的资源(也许是间接等待第三个ThreadC 或其他资源)—则所有向前的推进工作都可能会停下来。

问题的常见根源是互斥锁。实际上,之前所示的BankAccount 示例遇到的就是这个问题。如果ThreadA 试图将$500 从帐户

#1234 转移到帐户#5678,与此同时ThreadB 试图将$500 从#5678 转移到#1234,则代码可能发生死锁。

使用一致的获得顺序可避免死锁,如图 3 所示。此逻辑可概括为“同步锁获得”之类的名称,通过此操作可依照各个锁之间的某种顺序动态排序多个可锁定的对象,从而使得在以一致的顺序获得两个锁的同时必须维持两个

锁的位置。另一个方案称为“锁矫正”,可用于拒绝被认定以不一致的顺序完成的锁获得。

图3 一致的获得顺序class BankAccount {

private int m_id; // Unique bank account ID.

internal static void Transfer(

BankAccount a, BankAccount b, decimal delta) {

if (a.m_id < b.m_id) {

Monitor.Enter(a.m_balanceLock); // A first

Monitor.Enter(b.m_balanceLock); // ...and then B

} else {

Monitor.Enter(b.m_balanceLock); // B first

Monitor.Enter(a.m_balanceLock); // ...and then A

}

try {

Withdraw(a, delta);

Deposit(b, delta);

} finally {

Monitor.Exit(a.m_balanceLock);

Monitor.Exit(b.m_balanceLock);

}

}

// As before ...

}

锁并不是导致死锁的唯一根源。唤醒丢失是另一种现象,此时某个事件被遗漏,导致线程永远休眠。在Win32

自动重置和手动重置事件、CONDITION_V ARIABLE、CLR Monitor.Wait、Pulse 以及PulseAll

调用等同步事件中经常会发生这种情况。唤醒丢失通常是一种迹象,表示同步不正确,无法重置等待条件或在

wake-all(WakeAllConditionVariable 或Monitor.PulseAll)更为适用的情况下使用了

wake-single 基元(WakeConditionVariable 或

Monitor.Pulse)。

此问题的另一个常见根源是自动重置事件和手动重置事件

信号丢失。由于此类事件只能处于一个状态(有信号或无信号),因此用于设置此事件的冗余调用实际上将被忽略不计。如果代码认定要设置的两个调用始终需要转换为两个唤醒

的线程,则结果可能就是唤醒丢失。

锁保护

当某个锁的到达率与其锁获得率相比始终居高不下时,可能会产生锁保护。在极端的情况下,等待某个锁的线程超过了其承受力,就会导致灾难性后果。对于服务器端的程序而言,如果客户端所需的某些受锁保护的数据结构需求量大增,则经常会发生这种情况。

例如,请设想以下情况:平均来说,每100 毫秒会到达8 个请求。我们将八个线程用于服务请求(因为我们使用的是

8-CPU 计算机)。这八个线程中的每一个都必须获得一个锁并保持20 毫秒,然后才能展开实质的工作。

憾的是,对这个锁的访问需要进行序列化处理,因此,全部八个线程需要160 毫秒才能进入并离开锁。第一个退出后,需要经过140

毫秒第九个线程才能访问该锁。此方案本质上无法进行调整,因此备份的请求会不断增长。随着时间的推移,如果到达率不降低,客户端请求就会开始超时,进而发

生灾难性后果。

所周知,在锁中是通过公平性对锁进行保护的。原因在于在锁本来已经可用的时间段内,锁被人为封闭,使得到达的线程必须等待,直到所选锁的拥有者线程能够唤

醒、切换上下文以及获得和释放该锁为止。为解决这种问题,Windows 已逐渐将所有内部锁都改为不公平锁,而且CLR 监视器也是不公平的。

对于这种有关保护的基本问题,唯一的有效解决方案是减少锁持有时间并分解系统以尽可能减少热锁(如果有的话)。

虽然说起来容易做起来难,但这对于可伸缩性来说还是非常重要的。

“蜂拥”是指大量线程被唤醒,使得它们全部同时从Windows 线程计划程序争夺关注点。例如,如果在单个手动设置事件中有100 个阻塞的线程,而您设置该事件…嗯,算了吧,您很可能会把事情弄得一团糟,特别是当其中的大部分线程都必须再次等待时。

现阻塞队列的一种途径是使用手动设置事件,当队列为空时变为无信号而在队列非空时变为有信号。遗憾的是,如果从零个元素过渡到一个元素时存在大量正在等待

的线程,则可能会发生蜂拥。这是因为只有一个线程会得到此单一元素,此过程会使队列变空,从而必须重置该事件。如果有100

个线程在等待,那么其中的99 个将被唤醒、切换上下文(导致所有缓存丢失),所有这些换来的只是不得不再次等待。两步舞曲

有时您需要在持有锁的情况下通知一个事件。如果唤醒的线程需要获得被持有的锁,则这可能会很不凑巧,因为在它被唤醒后只是发现了它必须再次等待。这样做非常浪费资源,

而且会增加上下文切换的总数。此情况称为两步舞曲,如果涉及到许多锁和事件,可能会远远超出两步的范畴。

Win32 和CLR 的条件变量支持在本质上都会遇到两步舞

曲问题。它通常是不可避免的,或者很难解决。

步舞曲问题在单处理器计算机上情况更糟。在涉及到事件时,内核会将优先级提升应用到唤醒的线程。这几乎可以保证抢先占用线程,使其能够在有机会释放锁之前

设置事件。这是在极端情况下的两步舞曲,其中设置ThreadA 已切换出上下文,使得唤醒的ThreadB

可以尝试获得锁;当然它无法做到,因此它将进行上下文切换以使ThreadA 可再次运行;最终,ThreadA 将释放锁,这将再次提升

ThreadB 的优先级,使其优先于ThreadA,以便它能够运行。如您所见,这涉及了多次无用的上下文切换。

优先级反转

修改线程优先级常常是自找苦吃。当不同优先级的许多线程共享对同样的锁和资源的访问权时,可能会发生优先级反转,即较低优先级的线程实际无限期地阻止较高优先级线程

的进度。这个示例所要说明的道理就是尽可能避免更改线程优先级。

面是一个优先级反转的极端示例。假设低优先级的ThreadA 获得某个锁L。随后高优先级的ThreadB 介入。它尝试获得L,但由于

ThreadA 占用使得它无法获得。下面就是“反转

”部分:好像ThreadA 被人为临时赋予了一个高于ThreadB

的优先级,这一切只是因为它持有ThreadB 所需的锁。

ThreadA 释放了锁后,此情况最终会自行解决。遗憾的是,如果涉及到中等优先级的ThreadC,设想一下会发生什么情况。虽然

ThreadC 不需要锁L,但它的存在可能会从根本上阻止ThreadA 运行,这将间接地阻止高优先级ThreadB 的运行。

终,Windows Balance Set Manager 线程会注意到这一情况。即使ThreadC 保持永远可运行状态,ThreadA

Java多线程技术及案例

Java多线程技术及案例 进程和线程: 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。 线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。 多进程是指操作系统能同时运行多个任务(程序)。 多线程是指在同一程序中有多个顺序流在执行。 Java中多线程的多种实现方式 Java中有多种多线程实现方法,主要是继承https://www.360docs.net/doc/4b17151271.html,ng.Thread类的方法和 https://www.360docs.net/doc/4b17151271.html,ng.Runnable接口的方法。 继承Thread类 Thread是https://www.360docs.net/doc/4b17151271.html,ng包中的一个类,从这个类中实例化的对象代表线程,启动一个新线程需要建立一个Thread实例。 使用Thread类启动新的线程的步骤如下: 1.实例化Thread对象 2.调用start()方法启动线程 构造方法:

public Thread(String threadName); public Thread(); 例程: publicclass Thread1extends Thread{//定义一个类继承Thread privateint count=1000; publicvoid run(){//重写run方法 while(true){ System.out.print(count+" "); if(--count==0){ return; } } } publicstaticvoid main(String[] args){ Thread1 th1=new Thread1();//实例化继承了Thread的类 Thread1 th2=new Thread1(); th1.start();//调用start()方法, th2.start(); for(int i=0;i<1000;i++){ System.out.print("A "); } }

解决多线程中11个常见问题

并发危险 解决多线程代码中的11 个常见的问题 Joe Duffy 本文将介绍以下内容:?基本并发概念 ?并发问题和抑制措施 ?实现安全性的模式?横切概念本文使用了以下技术: 多线程、.NET Framework 目录 数据争用 忘记同步 粒度错误 读写撕裂 无锁定重新排序 重新进入 死锁 锁保护 戳记 两步舞曲 优先级反转 实现安全性的模式 不变性 纯度 隔离 并发现象无处不在。服务器端程序长久以来都必须负责处理基本并发编程模型,而随着多核处理器的日益普及,客户端程序也将需要执行一些任务。随着并发操作的不断增加,有关确保安全的问题也浮现出来。也就是说,在面对大量逻辑并发操作和不断变化的物理硬件并行性程度时,程序必须继续保持同样级别的稳定性和可靠性。 与对应的顺序代码相比,正确设计的并发代码还必须遵循一些额外的规则。对内存的读写以及对共享资源的访问必须使用同步机制进行管制,以防发生冲突。另外,通常有必要对线程进行协调以协同完成某项工作。 这些附加要求所产生的直接结果是,可以从根本上确保线程始终保持一致并且保证其顺利向前推进。同步和协调对时间的依赖性很强,这就导致了它们具有不确定性,难于进行预测和测试。 这些属性之所以让人觉得有些困难,只是因为人们的思路还未转变过来。没有可供学习的专门API,也没有可进行复制和粘贴的代码段。实际上的确有一组基础概念需要您学习和适应。很可能随着时间的推移某些语言和库会隐藏一些概念,但如果您现在就开始执行并发操作,则不会遇到这种情况。本

文将介绍需要注意的一些较为常见的挑战,并针对您在软件中如何运用它们给出一些建议。 首先我将讨论在并发程序中经常会出错的一类问题。我把它们称为“安全隐患”,因为它们很容易发现并且后果通常比较严重。这些危险会导致您的程序因崩溃或内存问题而中断。 当从多个线程并发访问数据时会发生数据争用(或竞争条件)。特别是,在一个或多个线程写入一段数据的同时,如果有一个或多个线程也在读取这段数据,则会发生这种情况。之所以会出现这种问题,是因为Windows 程序(如C++ 和Microsoft .NET Framework 之类的程序)基本上都基于共享内存概念,进程中的所有线程均可访问驻留在同一虚拟地址空间中的数据。静态变量和堆分配可用于共享。请考虑下面这个典型的例子: static class Counter { internal static int s_curr = 0; internal static int GetNext() { return s_curr++; } } Counter 的目标可能是想为GetNext 的每个调用分发一个新的唯一数字。但是,如果程序中的两个线程同时调用GetNext,则这两个线程可能被赋予相同的数字。原因是s_curr++ 编译包括三个独立的步骤: 1.将当前值从共享的s_curr 变量读入处理器寄存器。 2.递增该寄存器。 3.将寄存器值重新写入共享s_curr 变量。 按照这种顺序执行的两个线程可能会在本地从s_curr 读取了相同的值(比如42)并将其递增到某个值(比如43),然后发布相同的结果值。这样一来,GetNext 将为这两个线程返回相同的数字,导致算法中断。虽然简单语句s_curr++ 看似不可分割,但实际却并非如此。 忘记同步 这是最简单的一种数据争用情况:同步被完全遗忘。这种争用很少有良性的情况,也就是说虽然它们是正确的,但大部分都是因为这种正确性的根基存在问题。 这种问题通常不是很明显。例如,某个对象可能是某个大型复杂对象图表的一部分,而该图表恰好可使用静态变量访问,或在创建新线程或将工作排入线程池时通过将某个对象作为闭包的一部分进行传递可变为共享图表。 当对象(图表)从私有变为共享时,一定要多加注意。这称为发布,在后面的隔离上下文中会对此加以讨论。反之称为私有化,即对象(图表)再次从共享变为私有。 对这种问题的解决方案是添加正确的同步。在计数器示例中,我可以使用简单的联锁: static class Counter { internal static volatile int s_curr = 0; internal static int GetNext() { return Interlocked.Increment(ref s_curr);

JAVA多线程试题 答案

多线程 一.选择题 1.下列说法中错误的一项是(A) A.线程就是程序 B.线程是一个程序的单个执行流 B.多线程是指一个程序的多个执行流D.多线程用于实现并发 2.下列哪个一个操作不能使线程从等待阻塞状态进入对象阻塞状态(D) A.等待阴塞状态下的线程被notify()唤 B.等待阻塞状态下的纯种被interrput()中断 C.等待时间到 D.等待阻塞状态下的线程调用wait()方法 3.下列哪个方法可以使线程从运行状态进入其他阻塞状态(A) A.sleep B.wait C.yield D.start 4.下列说法中错误的一项是(D) A.一个线程是一个Thread类的实例 B.线程从传递给纯种的Runnable实例run()方法开始执行 C.线程操作的数据来自Runnable实例 D.新建的线程调用start()方法就能立即进入运行状态 5.下列关于Thread类提供的线程控制方法的说法中,错误的一项是(D) A.在线程A中执行线程B的join()方法,则线程A等待直到B执行完成 B.线程A通过调用interrupt()方法来中断其阻塞状态 C.若线程A调用方法isAlive()返回值为true,则说明A正在执行中 D.currentThread()方法返回当前线程的引用 6.下列说法中,错误的一项是() A.对象锁在synchronized()语句执行完之后由持有它的线程返还 B.对象锁在synchronized()语句中出现异常时由持有它的线程返还 C.当持有锁的线程调用了该对象的wait()方法时,线程将释放其持有的锁 D.当持有锁的线程调用了该对象的构造方法时,线程将释放其持有的锁 7.下面的哪一个关键字通常用来对对象的加锁,从而使得对对象的访问是排他的A A.sirialize B transient C synchronized D static 二.填空题 1.在操作系统中,被称做轻型的进程是线程 2.多线程程序设计的含义是可以将一个程序任务分成几个并行的任务 3.在Java程序中,run()方法的实现有两种方式:实现Runnable接口和继承Thread类 4.多个线程并发执行时,各个线程中语句的执行顺序是确定的,但是线程之间的相对执行顺序是不确定的 6.Java中的对象锁是一种独占的排他锁 7.程序中可能出现一种情况:多个线种互相等待对方持有的锁,而在得到对方的锁之前都不会释放自己的锁,这就是死锁 8.线程的优先级是在Thread类的常数MIN_PRIORITY和MAX_PRIORITY 之间的一个值 9.处于新建状态的线程可以使用的控制方法是start()和stop()。 10.一个进程可以包含多个线程

15个Java多线程面试题及答案

15个Java多线程面试题及答案 1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。 2)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它? lock接口在多线程和并发编程中最大的优势是它们为读和写分别提 供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。Java线程面试的问题越来越会根据面试者的回答来提问。芯学苑老师强烈建议在你在面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。 3)在java中wait和sleep方法的不同?

通常会在电话面试中经常被问到的Java线程面试问题。最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。 4)用Java实现阻塞队列。 这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。 5)用Java写代码来解决生产者——消费者问题。 与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在Java中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。 6)用Java编程一个会导致死锁的程序,你将怎么解决?

java多线程并发面试题【java多线程和并发基础面试题】

java多线程并发面试题【java多线程和并 发基础面试题】 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。下面就由小编为大家介绍一下java多线程和并发基础面试题的文章,欢迎阅读。 java多线程和并发基础面试题篇1 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单一进程。线程可以被称为轻量级进程。线程需要较少的来创建和驻留在进程中,并且可以共享进程中的。 2. 多线程编程的好处是什么? 在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待而进入空闲状态。多个线程共享堆内存(heap memory),因此创建多个线程去执行一些任务会比创建多个进程更好。举个例子,Servlets比CGI更好,是因为Servlets支持多线程而CGI不支持。 3. 用户线程和守护线程有什么区别? 当我们在Java程序中创建一个线程,它就被称为用户线程。一个守护线程是在后台执行并且不会阻止JVM终止的

线程。当没有用户线程在运行的时候,JVM关闭程序并且退出。一个守护线程创建的子线程依然是守护线程。 4. 我们如何创建一个线程? 有两种创建线程的方法:一是实现Runnable接口,然后将它传递给Thread的构造函数,创建一个Thread对象;二是直接继承Thread类。 java多线程和并发基础面试题篇2 1. 有哪些不同的线程生命周期? 当我们在Java程序中新建一个线程时,它的状态是New。当我们调用线程的start()方法时,状态被改变为Runnable。线程调度器会为Runnable线程池中的线程分配CPU时间并且讲它们的状态改变为Running。其他的线程状态还有Waiting,Blocked 和Dead。 2. 可以直接调用Thread类的run()方法么? 当然可以,但是如果我们调用了Thread的run()方法,它的行为就会和普通的方法一样,为了在新的线程中执行我们的代码,必须使用Thread.start()方法。 3. 如何让正在运行的线程暂停一段时间? 我们可以使用Thread类的Sleep()方法让线程暂停一段时间。需要注意的是,这并不会让线程终止,一旦从休眠中唤醒线程,线程的状态将会被改变为Runnable,并且根据线程调度,它将得到执行。

windows 并发的多线程的应用

(1)苹果香蕉问题 #include using namespace std; #include #include int k; HANDLE Apple_;HANDLE Banana_; CRITICAL_SECTION mmutex; DWORD WINAPI Son(LPVOID n) {//HANDLE Apple_; CRITICAL_SECTION mmutex; int i=1; OpenSemaphore(MUTEX_ALL_ACCESS,false,"Apple_"); while(1) { ::WaitForSingleObject(Apple_,INFINITE);//等苹果 cout<<"Son eats"<

多线程与并发面试题

多线程与并发面试题

JAVA多线程和并发基础面试问答 原文链接译文连接作者:Pankaj 译者:郑旭东校对:方腾飞 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,可是你依然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题。(校对注:非常赞同这个观点) Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它能够被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一个包含了不同的类和程序的单一进程。线程能够被称为轻量级进程。线程需要较少的资源来创立和驻留在进程中,而且能够共享进程中的资源。 2. 多线程编程的好处是什么?

在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态。多个线程共享堆内存(heap memory),因此创立多个线程去执行一些任务会比创立多个进程更好。举个例子,Servlets比CGI更好,是因为Servlets支持多线程而CGI不支持。 3. 用户线程和守护线程有什么区别? 当我们在Java程序中创立一个线程,它就被称为用户线程。一个守护线程是在后台执行而且不会阻止JVM终止的线程。当没有用户线程在运行的时候,JVM关闭程序而且退出。一个守护线程创立的子线程依然是守护线程。 4. 我们如何创立一个线程? 有两种创立线程的方法:一是实现Runnable接口,然后将它传递给Thread的构造函数,创立一个Thread对象;二是直接继承Thread类。若想了解更多能够阅读这篇关于如何在Java中创立线程的文章。 5. 有哪些不同的线程生命周期?

多线程练习题卷

多线程 一、单项选择题(从下列各题四个备选答案中选出一个正确答案,并将其代号写在答题纸相应位置处。答案错选或未选者,该题不得分。)50 1.下述哪个选项为真?( ) A.Error类是一个RoutimeException异常 B.任何抛出一个RoutimeException异常的语句必须包含在try块之内 C.任何抛出一个Error对象的语句必须包含在try块之内 D. 任何抛出一个Exception异常的语句必须包含在try块之内 2.下列关于Java线程的说法哪些是正确的?( ) A.每一个Java线程可以看成由代码、一个真实的CPU以及数据3部分组成 B.创建线程的两种方法,从Thread类中继承的创建方式可以防止出现多父类问题 C.Thread类属于java.util程序包 D.以上说法无一正确 3.哪个关键字可以对对象加互斥锁?( ) A.transient B.synchronized C.serialize D.static 4.下列哪个方法可用于创建一个可运行的类?() A.public class X implements Runable { public void run() {……} } B. public class X implements Thread { public void run() {……} } C. public class X implements Thread { public int ru n() {……} } D.public class X implements Runable { protected void run() {……} } 5.下面哪个选项不会直接引起线程停止执行?( ) A.从一个同步语句块中退出来 B.调用一个对象的wait方法 C.调用一个输入流对象的read方法 D.调用一个线程对象的setPriority方法 6.使当前线程进入阻塞状态,直到被唤醒的方法是( ) A.resume()方法 B.wait()方法 C.suspend()方法 D.notify()方法 7.运行下列程序,会产生的结果是( ) public class X extends Thread implements Runnable { public void run(){ System.out.println(“this is run()”); } public static void main(String[] args) { Thread t=new Thread(new X()); t.start(); }

Java并发编程实践

1对象的共享 关键字synchronized不仅能实现原子性还能确保当一个线程修改了对象状态后,另一个线程就可以看到对象状态的变化(内存可见性) 1.1可见性 重排序:在缺乏足够同步的多线程程序中,代码的执行顺序不会按照程序员写好的顺序进行。这是因为Java内存模型允许编译器、CPU对操作的执行顺序进行调整。 1.1.1失效数据 在多线程程序中,get方法、set方法都需要进行同步。这是因为get方法在获取变量时可能会获得一个失效的值,这个失效的值就是之前某个线程设置的。虽然已经失效但这个值曾经是正确的。 1.1.2非原子的64位操作 Java内存模型要求,非volatile类型的64位数值变量(double和long),JVM允许将64位的读操作和写操作分解为两个32位的操作。 那么在多线程的环境中,如果要读取非volatile类型的double、隆就有可能会读取到某个值的高32位和另一个值的低32位组成的一个数值。 但目前各种平台的商用虚拟机几乎都把64位数据的读写操作作为原子操作来对待。1.1.3加锁与可见性 加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。 同步代码块的锁就是方法调用所在的对象,静态synchronized方法以Class对象作为锁。 synchronized可以用于实例变量、对象引用、static方法、类名称字面常量。 1.在某个对象实例内,synchronized aMethod(){} 可以防止多个线程同时访问这个对象其他

的synchronized方法(这个对象还有其他的synchronized方法,如果其中一个线程访问了其中一个synchronized方法,那么其他线程将不能访问此对象另外的synchronized方法)。但不同的对象实例间的synchronized方法是相互独立的,也就是说其他线程照样可以访问相同类的另一个对象实例的synchronized方法。 2.synchronized static aMethod(){}对这个类所有的静态synchronized方法都会起作用,但 不会对非静态的synchronized起作用。这是因为static方法属于类方法,他属于这个Class (注意:这里的Class不是指Class的某个具体对象),那么static方法所获取到的锁就是调用这个方法的对象所属的类,而非static方法获取到的锁就是当前调用这个方法的对象了。 3.除了在方法上用synchronized关键字外,也可以在方法内部的某个区块中用synchronized 表示只对这个区块中的资源进行同步访问,例如synchronized(this){/**区块**/}的作用域就是当前对象。 1.1.3.1非static方法 运行结果是:chunk对象与chunk1对象锁互不干扰。 chunk1 9chunk

Java多线程详解

JAVA多线程编程详解 一、理解多线程 多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享 一个存储空间,这使得线程间的通信远较进程简单。 具体到java内存模型,由于Java 被设计为跨平台的语言,在内存管理上,显然也要有一个统一的模型。系统存在一个主内存(Main Memory),Java 中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。 “”“” 多个线程的执行是并发的,也就是在逻辑上同时,而不管是否是物理上的同时。如 ,那么真正的同时是不可能的。多线程和传统的单线程在程序设计果系统只有一个CPU“” 上最大的区别在于,由于各个线程的控制流彼此独立,使得各个线程之间的代码是乱序执 行的,将会带来线程调度,同步等问题。 二、在Java中实现多线程 我们不妨设想,为了创建一个新的线程,我们需要做些什么?很显然,我们必须指明 这个线程所要执行的代码,而这就是在Java中实现多线程我们所需要做的一切! 作为一个完全面向对象的语言,Java提供了类https://www.360docs.net/doc/4b17151271.html,ng.Thread 来方便多线程编程,这个类提供了大量的方法来方便我们控制自己的各个线程。 那么如何提供给Java 我们要线程执行的代码呢?让我们来看一看Thread 类。Thread 类最重要的方法是run(),它为Thread 类的方法start()所调用,提供我们的线程所要执行的代码。为了指定我们自己的代码,只需要覆盖它! 方法一:继承Thread 类,重写方法run(),我们在创建的Thread 类的子类中重写run(),加入线程所要执行的代码即可。下面是一个例子:

并发危险:解决多线程代码中的 11 个常见的问题

并发危险:解决多线程代码中的11 个常见的问题 并发危险 解决多线程代码中的11 个常见的问题 Joe Duffy 目录 数据争用忘记同步粒度错误读写撕裂无锁定重新排序重新进入死锁锁保护戳记两步舞曲优先级反转实现安全性的模式不变性纯度隔离并发现象无处不在。服 务器端程序长久以来都必须负责处理基本并发编程模型,而随着多核处理器的日益普及,客户端程序也将需要执行一些任务。随着并发操作的不断增加,有关确保安 全的问题也浮现出来。也就是说,在面对大量逻辑并发操作和不断变化的物理硬件并行性程度时,程序必须继续保持同样级别的稳定性和可靠性。 与对应的顺序代码相比,正确设计的并发代码还必须遵循一些额外的规则。对内存的读写以及对共享资源的访问必须使用同步机制进行管制,以防发生冲突。另外,通常有必要对线程进行协调以协同完成某项工作。

这些附加要求所产生的直接结果是,可以从根本上确保线程始终保持一致并且保证其顺利向前推进。同步和协调对时间的依赖性很强,这就导致了它们具有不确定性,难于进行预测和测试。 这 些属性之所以让人觉得有些困难,只是因为人们的思路还未转变过来。没有可供学习的专门 API,也没有可进行复制和粘贴的代码段。实际上的确有一组基础概念需要您学习和适应。很可能随着时间的推移某些语言和库会隐藏一些概念,但如果您现在就 开始执行并发操作,则不会遇到这种情况。本文将介绍需要注意的一些较为常见的挑战,并针对您在软件中如何运用它们给出一些建议。 首先我将讨论在并发程序中经常会出错的一类问题。我把它们称为“安全隐患”,因为它们很容易发现并且后果通常比较严重。这些危险会导致您的程序因崩溃或内存问题而中断。当 从多个线程并发访问数据时会发生数据争用(或竞争条件)。特别是,在一个或多个线程写入一段数据的同时,如果有一个或多个线程也在读取这段数据,则会发生 这种情况。之所以会出现这种问题,是因为Windows 程序(如C++ 和Microsoft .NET Framework

JAVA多线程编程详解-详细操作例子

一、理解多线程 多线程是这样一种机制,它允许在程序中并发执行多个指令流,每个指令流都称为一个线程,彼此间互相独立。线程又称为轻量级进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单。 具体到java内存模型,由于Java被设计为跨平台的语言,在内存管理上,显然也要有一个统一的模型。系统存在一个主内存(Main Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。 多个线程的执行是并发的,也就是在逻辑上“同时”,而不管是否是物理上的“同时”。如果系统只有一个CPU,那么真正的“同时”是不可能的。多线程和传统的单线程在程序设计上最大的区别在于,由于各个线程的控制流彼此独

立,使得各个线程之间的代码是乱序执行的,将会带来线程调度,同步等问题。 二、在Java中实现多线程 我们不妨设想,为了创建一个新的线程,我们需要做些什么?很显然,我们必须指明这个线程所要执行的代码,而这就是在Java中实现多线程我们所需要做的一切! 作为一个完全面向对象的语言,Java提供了类https://www.360docs.net/doc/4b17151271.html,ng.Thread 来方便多线程编程,这个类提供了大量的方法来方便我们控制自己的各个线程。 那么如何提供给Java 我们要线程执行的代码呢?让我们来看一看Thread 类。Thread 类最重要的方法是run (),它为Thread 类的方法start()所调用,提供我们的线程所要执行的代码。为了指定我们自己的代码,只需要覆盖它! 方法一:继承Thread 类,重写方法run(),我们在创建Thread 类的子类中重写run(),加入线程所要执行的代码即可。下面是一个例子:

java实现多线程的网络并发服务器

课程设计说明书 课程名称: 操作系统原理-课程设计 课程代码: 题目: 多线程的网络并发服务器设计 年级/专业/班: 学生姓名: 学号: 开始时间:2011 年12月11日 完成时间:2011 年12月24 日 课程设计成绩: 指导教师签名:年月日

目录 1 引言 (1) 1.1问题的提出 (1) 1.2国内外研究的现状 (1) 1.3任务与分析 (1) 2 程序的主要功能 (2) 2.1客户端接收和发送消息功能 (2) 2.2服务器分配和回收序号功能 (2) 2.3服务器显示信息功能 (2) 3 程序运行平台 (3) 4 总体设计 (4) 5 程序类的说明 (5) 6 模块分析 (10) 6.1客服端模块 (10) 6.2服务器模块 (10) 6.3处理客户端线程的模块 (12) 6.4服务器线程模块 (12) 7 系统测试 (13) 8 结论 (18) 参考文献 (19) I

1 引言 1.1 问题的提出 在现代化的生活中,网络无处不在,那么是怎样来实现客户与服务器的通信的呢,服务器是怎样来实现处理不同客户端发来的请求,这就涉及到多线程的网络并发服务器的设计。 1.2国内外研究的现状 传统的并发服务器往往是基于多进程机制的,每个客户一个进程,需要操作系统的干预,进程数目受操作系统的限制。然而许多新型操作系统都是多线程,将并行服务器设计为多线程,可以增加其效率。这样,不必为每个服务请求启动一个单独的进程或任务,多线程并行服务器可以启动一个执行速度更快的独立线程。如果主机是多处理器的,则多线程并行服务器能够在多处理机上执行多个线程。 1.3任务与分析 一个进程可以创建多个线程,线程与线程间的通信方式比较容易。本设计主要是编写一个多线程的网络通信程序,不同的线程处理不同类型的消息,比如有专门处理TCP 的线程、专门处理UDP消息的线程等。 1

【2020最新Java面试题资料】15道面试常问的Java多线程面试题

1)现在有T1T2T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。 2)在中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它? lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。线程面试的问题越来越会根据面试者的回答来提问。我强烈建议在你去参加多线程的面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。 3)在java中wait和sleep方法的不同? 通常会在电话面试中经常被问到的线程面试问题。最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。 4)用实现阻塞队列。 这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。 5)用Java写代码来解决生产者——消费者问题。 与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。 6)用编程一个会导致死锁的程序,你将怎么解决? 这是我最喜欢的线程面试问题,因为即使死锁问题在写多线程并发程序时非常普遍,但是很多侯选者并不能写deadlock free code(无死锁代码?),他们很挣扎。只要告诉他们,你有N个资源和N个线程,并且你需要所有的资源来完成一个操作。为了简单这里的n可以替换为2,越大的数据会使问题看起来更复杂。通过避免中的死锁来得到关于死锁的更多信息。

15个顶级Java多线程面试题及回答

Java 线程面试问题 在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。如果你想获得任何股票投资银行的前台资讯职位,那么你应该准备很多关于多线程的问题。在投资银行业务中多线程和并发是一个非常受欢迎的话题,特别是电子交易发展方面相关的。他们会问面试者很多令人混淆的Java线程问题。面试官只是想确信面试者有足够的Java线程与并发方面的知识,因为候选人中有很多只浮于表面。用于直接面向市场交易的高容量和低延时的电子交易系统在本质上是并发的。下面这些是我在不同时间不同地点喜欢问的Java线程问题。我没有提供答案,但只要可能我会给你线索,有些时候这些线索足够回答问题。现在引用Java5并发包关于并发工具和并发集合的问题正在增多。那些问题中ThreadLocal、Blocking Queue、Counting Semaphore和ConcurrentHashMap比较流行。 15个Java多线程面试题及回答 1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行? 这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。 2)在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它? lock接口在多线程和并发编程中最大的优势是它们为读和写分别提供了锁,它能满足你写像ConcurrentHashMap这样的高性能数据结构和有条件的阻塞。Java线程面试的问题越来越会根据面试者的回答来提问。我强烈建议在你去参加多线程的面试之前认真读一下Locks,因为当前其大量用于构建电子交易终统的客户端缓存和交易连接空间。 3)在java中wait和sleep方法的不同? 通常会在电话面试中经常被问到的Java线程面试问题。最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,sleep通常被用于暂停执行。 4)用Java实现阻塞队列。 这是一个相对艰难的多线程面试问题,它能达到很多的目的。第一,它可以检测侯选者是否能实际的用Java线程写程序;第二,可以检测侯选者对并发场景的理解,并且你可以根据这个问很多问题。如果他用wait()和notify()方法来实现阻塞队列,你可以要求他用最新的Java 5中的并发类来再写一次。 5)用Java写代码来解决生产者——消费者问题。 与上面的问题很类似,但这个问题更经典,有些时候面试都会问下面的问题。在Java中怎么解决生产者——消费者问题,当然有很多解决方法,我已经分享了一种用阻塞队列实现的方法。有些时候他们甚至会问怎么实现哲学家进餐问题。

JAVA中实现多线程总结

我们知道,在操作系统级别上软件的运行一般都是以进程为单位,而在每个进程的运行过程中允许同时并发执行多个不同线程,这就使得一个程序能同时执行不同的操作。使用多线程的目的是为了最大限度地利用计算机CPU资源。JAVA程序字节码最终是在JVM虚拟机下运行的,同一虚拟机进程中的不同操作都是通过多线程来运行的。在JAVA虚拟机中,线程常用有单线程和多线程,单线程指程序执行过程只是一个有效操作的序列,不同操作都有着明确的先后顺序;而多线程允许同时进行着不同的操作,这些不同的操作同时并发进行着,并由CPU时钟频率根据不同的调度方式对他们进行执行调度。 在JAVA语言中提供了丰富的多线程操纵接口,提供了各类不同的线程实现方法供我们选择,功能非常强大。在手机软件设计中,由于同样需要执行网络连接(基于HTTP的高级Internet协议通讯)、UI调度等待、UI显示幻化、游戏控制等操作需要通过后台的数据运算或UI不断更新等操作。因此在J2ME中,KVM虚拟机也提供了功能强大的多线程API,使我们同样能在J2ME中实现线程的并发运算。 在J2ME中,主要有以下三种方法实现多线程。 一、继承Thread类(https://www.360docs.net/doc/4b17151271.html,ng.Thread) 通过编写线程类继承Thread类并重写Thread类中的run()方法实现线程,当线程对象被运行时候将会自动执行run方法中的实体内容,从而开辟一个单独的线程并运行起来。 如:public class ThreadSimple extends Thread{ public ThreadSimple() { //constructor } public void run() { //run code entity } } 线程实例使用,直接创建对象并调用start()方法即可运行线程。 new ThreadSimple().start(); 当执行start方法时候,将会自动运行run方法,但是执行start方法时候只做了一件事,就是将线程转化为可执行状态,然后等待操作系统进行调度并运行,因此无法保证线程能立即启动。在JAVA中,Thread类实现了Runnable接口,因此run方法是通过实现接口Runnable中的抽象方法。

java并发编程--一道经典多线程题的2种解法

问题的描述 启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10, 然后是线程3打印11,12,13,14,15. 接着再由线程1打印 16,17,18,19,20....以此类推, 直到打印到75. 程序的输出结果应该为: 线程1: 1 线程1: 2 线程1: 3 线程1: 4 线程1: 5 线程2: 6 线程2: 7 线程2: 8 线程2: 9 线程2: 10 ... 线程3: 71 线程3: 72 线程3: 73 线程3: 74 线程3: 75

解法一: 采用原始的synchronized, wait(), notify(), notifyAll()等方式控制线程. Java代码 1.public class NumberPrintDemo { 2. // n为即将打印的数字 3. private static int n = 1; 4. // state=1表示将由线程1打印数字, state=2表示将由线程2打印 数字, state=3表示将由线程3打印数字 5. private static int state = 1; 6. 7. public static void main(String[] args) { 8. final NumberPrintDemo pn = new NumberPrintDemo(); 9. new Thread(new Runnable() { 10. public void run() { 11. // 3个线程打印75个数字, 单个线程每次打印5个连 续数字, 因此每个线程只需执行5次打印任务. 3*5*5=75 12. for (int i = 0; i < 5; i++) { 13. // 3个线程都使用pn对象做锁, 以保证每个交替 期间只有一个线程在打印 14. synchronized (pn) { 15. // 如果state!=1, 说明此时尚未轮到线程1 打印, 线程1将调用pn的wait()方法, 直到下次被唤醒 16. while (state != 1) 17. try { 18. pn.wait(); 19. } catch (InterruptedException e) { 20. e.printStackTrace(); 21. } 22. // 当state=1时, 轮到线程1打印5次数 字 23. for (int j = 0; j < 5; j++) { 24. // 打印一次后n自增 25. System.out.println(Thread.currentTh read().getName() 26. + ": " + n++); 27. } 28. System.out.println();

多核技术与并发多线程技术的区别介绍

多核技术与并发多线程技术的区别介绍 2009-04-16 20:29:32 作者:admin来源:浏览次数:195 网友评论 0 条 很多人在多核技术与并发多线程技术上会把概念弄混淆,我这里给大家简单介绍一下这两个技术的不同。多核技术可以看成是一种cpu的集成技术,在一个CPU处理模块上,可以集成2个或者是多个CPU,但是,他们还是单独的物理cpu。并发多线程技术则需要OS 的支持,是在OS级别上,可以实现一个物理cpu的多线程并发处理,提高oltp环境模式下的cpu利用率。 先说多核技术,如在IBM的power5中,就有如下几种不同的cpu集成模式:如D ual-Core Module(双核),表示一个cpu模块中其实有2个物理的cpu(也叫core),他们共享L2与L3 cache。这种cpu模块一般用在520/550上,所以,象采用这种模式的550最多可以有4颗CPU(2个cpu模块)。而Quad Core Module [QCM],这个不知道怎么翻译了,这种技术下,在一个cpu模块中,有4个物理cpu,相当与2个Dual-Core Module集成在一个cpu模块中,从55Q以后的机型,如55Q、55A都支持这样的cpu模块,所以,如果采用这样的模块,55A就可以达到8颗物理cpu。最后说Multi-Chip Module,这个就叫多核技术了,如一个cpu模块中,可以有8个物理cpu,相当于4个Dual-Core Module,一般用在590与595上。如图,则是一个采用了Multi-Chip Module技术的cpu模块: 从以上的图片与描述可以看到,这种多核技术不过是物理cpu的高集成度技术,让更小的地方,可以放更多的cpu,如550空间大小不变,同样2个cpu模块,如果采用双

相关文档
最新文档