多线程实验报告

2024-05-03

多线程实验报告(共6篇)

篇1:多线程实验报告

宁波工程学院电信学院计算机教研室

实验报告

课程名称: Java 2 姓 名: *** 实验项目: 多线程实验 学 号: **** 指导教师: **** 班 级: **** 实验位置: 电信楼机房 日 期:

一、实验目的

1、掌握多线程编程的特点和工作原理;

2、掌握编写线程程序的方法

3、了解线程的调度和执行过程

4、掌握线程同步机理

二、实验环境

windows记事本,java jdk 1.60版本,cmd命令运行窗口

三、实验内容 实验一:

应用Java中线程的概念写一个Java程序(包括一个测试线程程序类TestThread,一个Thread类的子类PrintThread)。在测试程序中用子类PrintThread创建2个线程,使得其中一个线程运行时打印10次“线程1正在运行”,另一个线程运行时打印5次“线程2正在运行

源程序:

public class A { public static void main(String args[]){

Test1 A1;

Test2 A2;

A1=new Test1();

A2=new Test2();

A1.start();

A2.start();} } class PrintThread extends Thread { } class Test1 extends PrintThread { public void run(){

for(int i=1;i<=10;i++)

{

System.out.println(“线程1正在运行!”);

} } } class Test2 extends PrintThread { public void run(){

for(int i=1;i<=5;i++)

{

System.out.println(“线程2正在运行!”);

} } } 运行结果:

实验二:

将上述程序用Runnable接口改写,并上机验证源程序 public class D { public static void main(String args[]){

Move move=new Move();

move.test1.start();

move.test2.start();} } class Move implements Runnable { Thread test1,test2;Move(){

test1=new Thread(this);

test1.setName(“线程1正在运行!”);

test2=new Thread(this);

test2.setName(“线程2正在运行!”);} public void run(){

if(Thread.currentThread()==test1)

{

for(int i=1;i<=10;i++)

{

System.out.println(test1.getName());

} } } else { for(int i=1;i<=5;i++){

System.out.println(test2.getName());} } 运行结果:

实验三:

import java.awt.*;import java.awt.event.*;public class E

{ public static void main(String args[])

{ new FrameMoney();

} } class FrameMoney extends Frame implements Runnable,ActionListener { int money=100;

TextArea text1,text2;

Thread 会计,出纳;

int weekDay;

Button start=new Button(“开始演示”);

FrameMoney()

{ 会计=new Thread(this);

出纳=new Thread(this);

text1=new TextArea(12,15);

text2=new TextArea(12,15);

setLayout(new FlowLayout());

add(start);

add(text1);

add(text2);

setVisible(true);

setSize(360,300);

validate();

addWindowListener(new WindowAdapter()

{ public void windowClosing(WindowEvent e)

{System.exit(0);

}

});

start.addActionListener(this);

}

public void actionPerformed(ActionEvent e)

{ if(!(出纳.isAlive()))

{ 会计=new Thread(this);

出纳=new Thread(this);

}

try

{ 会计.start();

出纳.start();

}

catch(Exception exp){}

}

public synchronized void 存取(int number)//存取方法

{ if(Thread.currentThread()==会计)

{ text1.append(“今天是星期”+weekDay+“n”);

for(int i=1;i<=3;i++)//会计使用存取方法存入90元,存入30元,稍歇一下

{ money=money+number;

//这时出纳仍不能使用存取方法

try { Thread.sleep(1000);//因为会计还没使用完存取方法

}

catch(InterruptedException e){}

text1.append(“帐上有”+money+“万n”);

}

}

else if(Thread.currentThread()==出纳)

{ text2.append(“今天是星期 ”+weekDay+“n”);

for(int i=1;i<=2;i++)//出纳使用存取方法取出30元,取出15元,稍歇一下

{ money=money-number/2;

//这时会计仍不能使用存取方法

try { Thread.sleep(1000);//因为出纳还没使用完存取方法

}

catch(InterruptedException e){}

text2.append(“帐上有”+money+“万n”);

}

}

}

public void run()

{ if(Thread.currentThread()==会计||Thread.currentThread()==出纳)

{ for(int i=1;i<=3;i++)//从周一到周三会计和出纳都要使用帐本

{ weekDay=i;

存取(30);

}

}

} }

运行结果:

}

四、实验心得与小结

通过本次实验,基本了解了线程的概念,作用,方法以及使用规则。1.首先:java 程序是建立在线程之上的。.2.创建线程必须继承 Thread class 它已经为线程的创建和运行做了必要的配置。run是线程就重要的方法。你必须覆写这个方法达到你想要的目的。3.run方法所包含的代码就是和其他线程同时运行的代码以达到同一时刻运行多段代码的目的。当终止了 run以后。这个线程也就结束了。调用线程的 start方法才会执行 run方法。

4.线程的生命周期:新建——Thread.State.NEW:当一个 Thread 类或者其子类的对象被声明并创建时,新的线程对象处于新建状态,此时它已经有了相应的内存空间和其他资源start方法尚未被调整用就绪可执行状态——Thread.State.RUNNABLE:处于新建状态的线程被启动后,将进入线程队列排队,这个时候具备了运行的条件,一旦轮到 CPU 的时候,就可以脱离创建它的主线程独立开始自己的生命周期运行:就绪的线程被调度进入运行状态,每一个 Thread 类及其子类的对象都有一个重要的run方法,当线程对象被调度执行的时候,它将自动调用本对象的 run方法,从第一句代码开始执行。

篇2:多线程实验报告

课程名称:专业班级:学生学号:学生姓名:实验名称:实验成绩:课程类别:

验 报 告

Java程序设计

计算机科学与技术2008级

2008081042 王湛泽

JAVA程序设计

必修□限选 公选□ 其它□

实验四

多线程

[实验目的] 1.练习线程的使用以深入理解线程状态与生命周期。2.了解线程调度机制、理解线程同步机制。

[实验内容] 1.编写一个多线程的Java应用程序,经历线程不同的状态与生命周期。

2.编写一个多线程的Java应用程序,在程序中进行线程同步的处理。[实验步骤与要求] 第1题 线程的状态

编写一个Java应用程序,在主线程中再创建2个线程,要求线程经历4种状态:新建、运行、中断和死亡 第2题 排队买票

编写一个Java应用程序,模拟5个人排队买票。售票员只有1张五元的钱,电影票五元一张。假设5个人的名字及排队顺序:赵、钱、孙、李、周。“赵”拿一张二十元的人民币买2张票,“钱”拿1张二十元的人民币1张票,“孙”拿1张十元的人民币买1张票,“李”拿1张十元的人民币买2张票,“周”拿1张五元的人民币买1张票,要求售票员按如下规则找赎:

二十元买2张票,找零:找1张十元;不许找2张五元 二十元买1张票,找零:找1张十元,1张五元;不许找3张五元

十元买1张票,找零:找1张五元 [作业提交] 第一题:

将代码贴在下面:

public class Example8_8{ public static void main(String args[]){ ClassRoom room=new ClassRoom();room.zhangHua.start();

room.teacher.start();} } class ClassRoom implements Runnable{ Thread zhangHua,teacher;ClassRoom(){ teacher=new Thread(this);zhangHua=new Thread(this);zhangHua.setName(“张华”);

teacher.setName(“刘老师”);} public void run(){ Thread thread=Thread.currentThread();if(thread==zhangHua){ try{ System.out.println(thread.getName()+“休息10秒后再说问候”);Thread.sleep(10000);} catch(InterruptedException e){ System.out.println(thread.getName()+“被吵醒了”);} System.out.println(thread.getName()+“说:早上好!”);} else if(thread==teacher){ for(int i=1;i<=2;i++){ System.out.println(thread.getName()+“说:t上课!”);try{ Thread.sleep(500);} 3 catch(InterruptedException e){} }

zhangHua.interrupt();//吵醒zhangXiao } } } 将结果运行截屏贴在下面:

第二题: 代码:

public class Example8_10{ public static void main(String args[]){ String s1=“张”,s2=“钱”,s3=“孙”,s4=“李”,s5=“周”;Cinema canema=new Cinema(s1,s2,s3,s4,s5);Thread zhang,qian,sun,li,zhou;zhang=new Thread(canema);qian=new Thread(canema);sun=new Thread(canema);li=new Thread(canema);zhou=new Thread(canema);zhang.setName(s1);qian.setName(s2);4 sun.setName(s3);li.setName(s4);zhou.setName(s5);zhang.start();qian.start();sun.start();li.start();zhou.start();} } class Cinema implements Runnable{ //实现Runnable接口的类(电影院)TicketSeller seller;//电影院的售票员

String name1,name2,name3,name4,name5;//买票人的名字(线程的名字)Cinema(String s1,String s2,String s3,String s4,String s5){ seller=new TicketSeller();name1=s1;name2=s2;name3=s3;name4=s4;name5=s5;} public void run(){ if(Thread.currentThread().getName().equals(name1)){ seller.sellTicket(20);} else if(Thread.currentThread().getName().equals(name2)){ seller.sellTicket(20);} else if(Thread.currentThread().getName().equals(name3)){ seller.sellTicket(10);} else if(Thread.currentThread().getName().equals(name4)){ seller.sellTicket(10);} else if(Thread.currentThread().getName().equals(name5)){ seller.sellTicket(5);} } } class TicketSeller{ //负责卖票的类

int fiveNumber=1,tenNumber=0,twentyNumber=0;5 public synchronized void sellTicket(int receiveMoney){ String s=Thread.currentThread().getName();if(receiveMoney==5){ fiveNumber=fiveNumber+1;System.out.println(s+“给售票员5元钱,售票员卖给”+s+“一张票,不必找赎”);} else if(receiveMoney==10&&s==“李”){

tenNumber=tenNumber+1;

System.out.println(s+“给售票员10元钱,售票员卖给”+s+“两张票,不必找赎”);

} else if(receiveMoney==10&&s==“孙”){

while(fiveNumber<1){ try{ System.out.println(s+“给售票员10元钱”);System.out.println(“售票员请”+s+“靠边等一会”);wait();//如果线程占有CPU期间执行了wait(),就进入中断状态 System.out.println(s+“结束等待,继续买票”);} catch(InterruptedException e){} } fiveNumber=fiveNumber-1;tenNumber=tenNumber+1;System.out.println(s+“给售票员10元钱,售票员卖给”+s+“一张票,找赎5元”);} else if(receiveMoney==20&&s==“钱”){

while(fiveNumber<1||tenNumber<1){ try{ System.out.println(s+“给售票员20元钱”);System.out.println(“售票员请”+s+“靠边等一会”);wait();//如果线程占有CPU期间执行了wait(),就进入中断状态 System.out.println(s+“结束等待,继续买票”);} catch(InterruptedException e){} } fiveNumber=fiveNumber-1;tenNumber=tenNumber-1;twentyNumber=twentyNumber+1;System.out.println(s+“给售票员20元钱,售票员卖给”+s+“一张票,找赎15 6 元”);} else if(receiveMoney==20&&s==“赵”){

while(tenNumber<1){ try{ System.out.println(s+“给售票员20元钱”);System.out.println(“售票员请”+s+“靠边等一会”);wait();//如果线程占有CPU期间执行了wait(),就进入中断状态 System.out.println(s+“结束等待,继续买票”);} catch(InterruptedException e){} }

tenNumber=tenNumber-1;twentyNumber=twentyNumber+1;System.out.println(s+“给售票员20元钱,售票员卖给”+s+“两张票,找赎15元”);}

截屏:

篇3:多线程实验报告

CPU的主频从500MHZ到1G到2G到3.4G似乎无论多么高的主频都会被应用软件无情的吞噬, 而我们还总觉得CPU的主频还不够高, 应用软件的执行速度还不够快, 所以处理器生产商不断努力研究出主频更高的CPU以适应桌面客户端软件对主频无止境的需求。但是三个物理难题让我们更难能见到高频率的CPU:热量 (很难散去CPU产生的过多的热量) , 功耗 (太高) , 漏电。虽然我们也能看到主频是4GHZ的Intel 的CPU, 但那是需要普通桌面用户所不能接受的散热设备支持的[1]。

当用户在开始使用多核处理器时, 大家都在说多核处理器只有在处理多任务时才有明显效果, 处理单任务时并没有多少改善, 甚至还不如单核处理器, 是真的吗?事实就是这样的!这主要是因为当时的客户端应用程序都是在单核处理器编程思想下编写的, 所以代码之间的依赖性很强, 程序也不会同时在两个处理器核上执行, 他们只会在内核之间来回切换, 这样就会耗费大量的CPU在处理器内核上下文的切换上, 这就是我们在打开task management时为什么明明看到两个处理器内核都在执行程序, 而程序运行速度还不如单核处理器快的原因:两个内核没有在同一时间执行同一个任务。这就是当前软件开发思想同多核处理器之间的矛盾。我们最初想到的当然就是开发并行的编译器。

上世纪60年代中期多处理器系统开发软件的科学家们也是这样想的, 因为用FORTRAN编写科学计算程序更方便, 于是就有了并行的FORTRAN 编译器。Gguama[2]和Horwitz[3]对指针依赖问题提出了自己的解决方法, Hendren[4]为递归问题的解决也做出了努力, 但由于C语言中指针的多变性和已分配内存的不确定性, 因此C的并行编译器没能成功面世[5]。由于这个原因客户端应用软件开发者又不得不寻找新的方法来让软件能与多核的CPU 相配合, 从而更有效地利用多核CPU。于是就使用多处理器下的Thread+Lock 编程模型来编写客户端应用软件, 这种编程模型下需要程序员有较高的和较深刻操作系统的和并行方面的知识, 同时又有很丰富的编程经验和能够随时保持清醒的头脑。由于该模型有较高的门槛, 不能用于大量的客户端软件的开发, 就有人[6]提出在已知编程语言中使用工具插件这样就有了Cilk[7]、OpenMP[8]、MPI[9]等工具, 也有人[6]觉得并行编程应该有独立的适合并行编程思想的编程语言, 就开发出了Scala[10]、Erlang[11]、FJHJ[12]等。

目前这些还不足以让以指数方式增长的core能很好的被软件开发者利用, 从而使客户端软件有更好的性能, 现在缺少的就是一个更高层的能更清楚更容易被人理解的表达并行程序设计者意图的概念还有合适的并行语言调试工具, 为了能够这样, 程序语言设计者、辅助工具制造者需要更认真的去思考并行本身。

2依赖性

图灵设计了计算机的抽象模型, 冯·诺依曼根据这个模型制造出了计算机, 而现在市场使用的计算机绝大部分是图灵机, 所以这种类型的计算机就离不开对内存的读写, 所以就不可避免的会对同一块内存进行读或者写的操作, 这样就会有数据依赖的问题产生。根据数据依赖产生的原因可以把数据依赖分为四种类型[5]:

(1) 写后读:因为一个内存地址空间刚被分配出去时, 还不确定其值是多少, 需要对其进行初始化, 即写操作, 然后才能使用, 所以这种对同一块内存先写后读的依赖又称为流依赖 (flow dependence) ;

(2) 读后写:顾名思义对同一块内存先读后写, 这种依赖又称为反依赖 (anit-dependence) ;

(3) 写后写:同样对同一块内存执行写操作后在进行写操作, 又称输入依赖 (input dependence) ;

(4) 读后读:对同一块内存进行两次读操作, 又称输出依赖 (output dependence) 。

而多核CPU下的程序正是要对同一块内存进行读写操作的, 所以数据依赖问题也就是多核编程的瓶颈问题, 能解决依赖性问题也就解决了并行问题, 因此上世纪后期和本世纪2004年以后, 很多学者都在这个问题上做了大量研究。Stanford 的SUIF[14]就是该时期的研究成果的代表之一。我国同样也有这方面的研究, 并取得了很大的进展, 复旦大学的AFT[15]自动化并行系统西北工业大学的PARACTIVE[16], 清华大学的交互式并行化系统TIPS[17]都是比较有代表性的。

3串行程序到并行程序的转换

串行程序到并行程序的转换过程其实也就是发现原串行程序的并行性, 并在不改变原串行程序执行结果的情况下, 将串行程序编译成可并行执行的二进制机器指令或者其他并行编译程序能是别的并行代码的过程。这个过程大致如下:

串行程序 (程序依赖性分析 (有向无环图 (拆图 (并行程序 (并行编译器编译连接。

程序间依赖关系的表示大都使用有向无环图, 如果程序间的依赖关系都能用有向无环图表示出来那么并行的问题也就基本解决了。

本文提供的就是假设能完全解决数据依赖问题后, 如何将串行程序转换成并行程序的一种算法。

4转换过程

4.1串行程序依赖性分析

本文的依赖性的分析是通过读取变量和分析变量的读写变化而得到的:

(1) 读取源文件将其按语句编号;

(2) 找出头文件语句输出到目标代码

(3) 重复2直到没有引用头文件的语句i;

(4) 从i识别出定义的变量建立变量表T;

(5) 如果i是变量定义语句则i++, 转到4, 否则转到6;

(6) 分离语句i找出语句i中各个变量是被读的还是被写的;

(7) 分配一个节点N, 标出变量所在语句, 和其读写属性;

(8) 将这个节点挂在表T对应变量的链表下;

(9) 重复7、8直到该语句所有变量的处理完;

(10) i++, 若i不是最后一条语句则转到6;否则11;

(11) 将T中每个链表若同一变量出现两次, 则只保留写属性, 若都是读属性则删除一个, 结束;

经过这一步的处理, 我们得到一个变量表, 该表记录着各个变量在各个语句中的被读写属性, 该表是为了生成各个语句依赖关系的有向无环图而使用的。

4.2形成有向无环图

设变量表共有n个:

i =0, 图G;

读取链表T[i], 为不同语句构造节点, 若图G中不包含该节点则在图中构造该节点, 若链表T[i]中当前到后序节点关系为以下三种, 则为该组节点建立依赖关系:①先读后写;②先写后读;③写后写;

i++;若i

语句依赖关系的有向无环图形成。

当然也可以使用已有的工具如:SUIF、 TGE (task graph extraction) [19], 来帮助得到各个变量的读写变化, 从而形成有向无环, 当然也可以到网站[20]里面直接去下载TGE。

4.3拆有向无环图形成并行语句集i=0;

找出图G中入度为0的节点, 将其移除并放在并行语句集合P[i++]中;

减去相应节点的入度;

若图中还有节点则转到2, 否则转到5;

结束, 集合P[k], k=0, 1, 2, …i-1中即为可以并行执行的语句, 集合若m>n, 集合P[m]后于集合P[n]执行;

4.4并行指令集到并行程序的转化

有了并行指令集也就知道哪些语句是可以并行执行的了, 该部分可以把源串行程序转换为各种编程模型语法下的源程序, 但此处作者的编程思想都是线程编程模型, 也就是fork-join模式的代码转换。

下面的程序为例说明该算法具体过程:

该程序经过依赖性分析得到下表1。

根据表1得到有向无环图G10见图1所示。

拆G1后得到G2, 见图2所示。

第一次拆图G1得到集合P[1]={1, 2, 3};

第二次拆图G2得到集合P[2]={4, 5};

最后得到P[3]={6}

在上例中语句1) a=2, 依次2) b=3 ;3) c=4;4) a=a+b+c;5) sum=c;6) sum=a+b+c+sum;

若用OpenMP来作为目标代码则结果为

5总结与展望

本文介绍了将串行的程序源代码转换为并行程序源代码的一种方法, 该方法首先对源串行程序进行语法分析, 找到其变量在各个语句里面的读写属性, 并制作成属性变化表, 对此表进行解析从而得到各个语句的依赖关系的有向无环图, 对该有向无环图解析得到并行集合, 最会形成与原串行代码对应的并行代码。当然这里并行的最小单位是C语句, 其并行粒度太小, 当前编程环境下有时编译成的并行代码执行效率并不如原程序的, 若将C语句由C函数来替代, 其并行粒度会增大, 效果也会较好。

其实每个C语句又是汇编成几条机器指令的这些机器指令又有并行性这里就没再介绍。目前并行编程思想还不成熟, 较好的并行编程模式还没有得到业界认可, 各个公司都在按照自己的模型去实现语言, 所以串行程序并行化也并没有固定的并行模式[2]。

篇4:浅谈多线程安全

【关键词】多线程 编程 安全

一、什么是线程安全

当你的程序所在的进程之中有多个线程在同时运行,而这些线程可能会同时运行一段代码或者会同时访问一个对象,如果每次运行这段代码或对对象访问之后,所得到的结果和单线程情况下运行结果一样,而且其他的变量的值也和预期保持一致,我们就认为是线程安全的。也就是说,当多个线程同时运行同一段代码时不会造成资源的冲突,不会产生错误的结果就是线程安全的。如过有一段线程安全的代码,它在多个线程中使用时不需要做同步处理;而线程不安全的代码在多线程环境中使用必须要做同步处理,否则将会出现不可预期的后果。

二、线程不安全所造成的影响

在不进行数据保护操作时,当一段代码或者对象可能被多个线程访问,而且访问顺序不能确定,那么一旦这段代码或者对象处于有条件的线程安全,线程兼容,线程对立这一分类,就可能出现多个线程先后更改数据,从而造成得到的数据不一致,或者数据出现污染,更严重的时候会出现脏数据,甚至程序崩溃。线程不安全是可怕的,一方面它对于程序编写人员来说是一场灾难,因为线程不安全导致的错误会被程序复杂的逻辑掩盖,它所造成的错误与逻辑不严谨所造成的错误基本上相似,这就为除错造成了困难,很多时候需要重新审视整个代码,甚至很长时间无法发现问题所在。另一方面它对于程序的使用者造成的损失基本无法估计,在当下的数据时代,任何数据的不一致,数据的污染都会给一个公司和个人造成无法挽回的影响。

三、线程不安全的分类

线程的安全程度并没有一个统一的分类,在乔希·布洛赫所给出线程分类描述,线程安全性分为五类,分别是:不可变、线程安全、有条件线程安全、线程兼容和线程对立。当然,若在明确的明白下线程安全特性的情况下,无论是否使用这种分类系统都没有关系。乔希·布洛赫所提出的分类方法也有局限性,各个分类之间的界限并不是绝对的明朗,但是作为对线程安全的分类,确是简单明了的。下面就各个类别进行详细的说明:

1,不可变的

不可变的对象一定是线程安全的,因为其不可变的特性,它永远也不需要进行额外的同步操作。只要一个不可变的对象在构建时候是正确的,那么永远也不会看到它处于不一致的状态。再代码中,基本类型,基本数值类,字符常量都是不可变的。

2,线程安全的

所有线程安全的代码或者对象都具有上面所提到的线程安全的属性,也就是说,这段代码或对象在多线程环境下,被多个线程访问,不管这些线程的访问顺序是何种顺序,所有访问的线程都不需要进行额外的同步工作。这种线程安全的要求是严格的,满足这种要求的和满足不可变類型的所有代码或者对象都是绝对的线程安全。

3,有条件的线程安全

有条件的线程安全,指的是对于这段代码或者对象仅仅在一些操作访问顺序不同需要额外的同步操作。最显著的有条件的线程安全是在遍历一些返回迭代器对象的时候,如果在遍历这些对象的同时存在另一个或多个线程进行了添加或移除内部元素的操作,必然会出现迭代器的失效。为了保证在某一线程在执行遍历操作的时候该对象不会被其他线程访问,应通过相应的手段,阻止其他线程做出导致该对象的迭代器失效的操作。进行迭代的线程应该确保它是独占性的访问该对象,从而保证了遍历的完整性。

4,线程兼容的

线程兼容的代码或者对象不是线程安全的,但是可以通过正常使用同步而在多线程环境下安全的使用。

5,线程对立的

线程对立是指一段代码或者对象在运行中,一旦存在多进程对它进行操作,总是不能保证线程安全的,这种不能保证不管是否进行了同步的操作。线程对立是一种罕见且特殊的情况,例如当一段代码或对象中的静态数据被修改之后,被修改的数据可能会影响到其他代码或者其他对象的行为时,这种时候就会出现线程对立。

四、防范线程不安全影响的措施

1.对于可能会运行在多线程环境下的代码和对象的编写人员,应尽量的保证该代码在多线程环境每次运行结果和单线程情况下运行结果一致,而且其他的变量和值也能和预期结果保持一致。这种情况从根本上保证了线程安全。

2.对于可能会运行在多线程环境下的代码和对象的编写人员,在编写代码和对象的时候,应注意辨别是否能在多线程下正确运行,根据上文提出的线程安全的分类,对于有条件的线程安全的,线程兼容的,线程对立的应该给以相应的标记,或在文档之中详细的写出说明。保证该代码或者对象的潜在使用者能够通过查看注释或文档,清楚明白的知道该代码或对象在多线程环境下的运行状态。

3.对于某段代码或者某个对象的使用者,在使用这段代码或对象之前,应该清楚的知道该代码和对象是否是线程安全的,并且应该清楚的知道接下来使用这段代码或对象的运行环境是否为多线程环境,根据不同的情况决定是否需要进行线程间的同步,或者用互斥锁等方法使得线程能够有序的访问该代码或对象。

4.对于某段代码或者某个对象的使用者,如果确定为多线程环境,且确定接下来使用的代码或对象有可能会被多个线程访问,并且无法确定其是否是线程安全的时候,应尽量通过多种方式确定其线程安全与否,这些方式包括联系作者或者通过自己的测试,以及查看源代码。

5.对于软件设计者来说,当程序不能按照设计运行,应当在检测逻辑错误的同时,检测是否因为线程不安全而导致的错误是必要的。依次检查所使用别人代码或别人提供的对象是否存在线程安全问题是最为合适的。

6.对于已经发现因为线程安全问题导致错误的软件使用者,应及时停止使用该软件,及时联系软件厂商,要求修改。

参考文献:

1 Lubomir F.Bic,Alan C.Shaw.操作系统原理[M].北京:清华大学出版社,2005

2 安德鲁斯.多线程,并行与分布式程序设计基础(影印版)[M].北京:高等教育出版社,2002

篇5:多线程实验报告

本文主要论述IOS创建锁的方法(总结):

一、使用关键字

1)@synchronized(互斥锁)

优点:使用@synchronized关键字可以很方便地创建锁对象,而且不用显式的创建锁对象。

缺点:会隐式添加一个异常处理来保护代码,该异常处理会在异常抛出的时候自动释放互斥锁。而这种隐式的异常处理会带来系统的额外开销,为优化资源,你可以使用锁对象。

二、“Object-C”语言

1)NSLock(互斥锁)

2)NSRecursiveLock(递归锁)

条件锁,递归或循环方法时使用此方法实现锁,可避免死锁等问题。

3)NSConditionLock(条件锁)

使用此方法可以指定,只有满足条件的时候才可以解锁。

4)NSDistributedLock(分布式锁)

在IOS中不需要用到,也没有这个方法,因此本文不作介绍,这里写出来只是想让大家知道有这个锁存在。

如果想要学习NSDistributedLock的话,你可以创建MAC OS的项目自己演练,方法请自行Google,谢谢。

三、C语言

1)pthread_mutex_t(互斥锁)

2)GCD-信号量(“互斥锁”)

3)pthread_cond_t(条件锁)

线程安全 —— 锁

一、使用关键字:

1)@synchronized

// 实例类person

Person *person = [[Person alloc] init];

// 线程A

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

@synchronized(person) {

[person personA];

[NSThread sleepForTimeInterval:3]; // 线程休眠3秒

}

});

// 线程B

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

@synchronized(person) {

[person personB];

}

});

关键字@synchronized的使用,锁定的对象为锁的唯一标识,只有标识相同时,才满足互斥。如果线程B锁对象person改为self或其它标识,那么线程B将不会被阻塞。你是否看到@synchronized(self) ,也是对的。它可以锁任何对象,描述为@synchronized(anObj)。

二、Object-C语言

1)使用NSLock实现锁

// 实例类person

Person *person = [[Person alloc] init];

// 创建锁

NSLock *myLock = [[NSLock alloc] init];

// 线程A

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

[myLock lock];

[person personA];

[NSThread sleepForTimeInterval:5];

[myLock unlock];

});

// 线程B

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

[myLock lock];

[person personB];

[myLock unlock];

});

程序运行结果:线程B会等待线程A解锁后,才会去执行线程B。如果线程B把lock和unlock方法去掉之后,则线程B不会被阻塞,这个和synchronized的一样,需要使用同样的锁对象才会互斥。

NSLock类还提供tryLock方法,意思是尝试锁定,当锁定失败时,不会阻塞进程,而是会返回NO。你也可以使用lockBeforeDate:方法,意思是在指定时间之前尝试锁定,如果在指定时间前都不能锁定,也是会返回NO。

注意:锁定(lock)和解锁(unLock)必须配对使用

2)使用NSRecursiveLock类实现锁

// 实例类person

Person *person = [[Person alloc] init];

// 创建锁对象

NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];

// 创建递归方法

static void (^testCode)(int);

testCode = ^(int value) {

[theLock tryLock];

if (value > 0)

{

[person personA];

[NSThread sleepForTimeInterval:1];

testCode(value - 1);

}

[theLock unlock];

};

//线程A

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

testCode(5);

});

//线程B

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

[theLock lock];

[person personB];

[theLock unlock];

});

如果我们把NSRecursiveLock类换成NSLock类,那么程序就会死锁。因为在此例子中,递归方法会造成锁被多次锁定(Lock),所以自己也被阻塞了。而使用NSRecursiveLock类,则可以避免这个问题。

3)使用NSConditionLock(条件锁)类实现锁:

使用此方法可以创建一个指定开锁的条件,只有满足条件,才能开锁。

// 实例类person

Person *person = [[Person alloc] init];

// 创建条件锁

NSConditionLock *conditionLock = [[NSConditionLock alloc] init];

// 线程A

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

[conditionLock lock];

[person personA];

[NSThread sleepForTimeInterval:5];

[conditionLock unlockWithCondition:10];

});

// 线程B

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

[conditionLock lockWhenCondition:10];

[person personB];

[conditionLock unlock];

});

线程A使用的是lock方法,因此会直接进行锁定,并且指定了只有满足10的情况下,才能成功解锁,

unlockWithCondition:方法,创建条件锁,参数传入“整型”。lockWhenCondition:方法,则为解锁,也是传入一个“整型”的参数。

三、C语言

1)使用pthread_mutex_t实现锁

注意:必须在头文件导入:#import

 

// 实例类person

Person *person = [[Person alloc] init];

// 创建锁对象

__block pthread_mutex_t mutex;

pthread_mutex_init(&mutex, NULL);

// 线程A

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

pthread_mutex_lock(&mutex);

[person personA];

[NSThread sleepForTimeInterval:5];

pthread_mutex_unlock(&mutex);

});

// 线程B

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

pthread_mutex_lock(&mutex);

[person personB];

pthread_mutex_unlock(&mutex);

});

实现效果和上例的相一致

2)使用GCD实现“锁”(信号量)

GCD提供一种信号的机制,使用它我们可以创建“锁”(信号量和锁是有区别的,具体请自行百度)。

// 实例类person

Person *person = [[Person alloc] init];

// 创建并设置信量

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

// 线程A

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

[person personA];

[NSThread sleepForTimeInterval:5];

dispatch_semaphore_signal(semaphore);

});

// 线程B

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

[person personB];

dispatch_semaphore_signal(semaphore);

});

效果也是和上例介绍的相一致。

我在这里解释一下代码。dispatch_semaphore_wait方法是把信号量加1,dispatch_semaphore_signal是把信号量减1。

我们把信号量当作是一个计数器,当计数器是一个非负整数时,所有通过它的线程都应该把这个整数减1。如果计数器大于0,那么则允许访问,并把计数器减1。如果为0,则访问被禁止,所有通过它的线程都处于等待的状态。

3)使用POSIX(条件锁)创建锁

// 实例类person

Person *person = [[Person alloc] init];

// 创建互斥锁

__block pthread_mutex_t mutex;

pthread_mutex_init(&mutex, NULL);

// 创建条件锁

__block pthread_cond_t cond;

pthread_cond_init(&cond, NULL);

// 线程A

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

pthread_mutex_lock(&mutex);

pthread_cond_wait(&cond, &mutex);

[person personA];

pthread_mutex_unlock(&mutex);

});

// 线程B

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

pthread_mutex_lock(&mutex);

[person personB];

[NSThread sleepForTimeInterval:5];

pthread_cond_signal(&cond);

pthread_mutex_unlock(&mutex);

});

效果:程序会首先调用线程B,在5秒后再调用线程A。因为在线程A中创建了等待条件锁,线程B有激活锁,只有当线程B执行完后会激活线程A。

pthread_cond_wait方法为等待条件锁。

pthread_cond_signal方法为激动一个相同条件的条件锁。

简单总结:

篇6:Java线程及多线程技术及应用

多个线程同时访问或操作同一资源时,很容易出现数据前后不一致的问题。请看下面的例子:

男孩拿着折子去北京银行海淀分行取钱

女孩拿着男孩的银行卡去西单百货疯狂购物

男孩走到柜台钱询问帐户余额

银行的业务员小姐亲切地告诉他:您还有10000元!。

女孩看上了一件时髦的衣裳,准备买下

男孩在思考要取多少钱呢?

女孩到收银台准备刷卡消费

收银台刷卡机读取银行卡余额为10000元

女孩买衣服刷卡消费5000元

消费清单打印出来,消费:5000元 余额:5000元

女孩离开商场

男孩思考了1毫秒

男孩决定取5000元

银行的业务员小姐为男孩办理相关业务手续

交易完成

银行的业务员小姐告诉男孩:您的余额为5000元。

男孩离开银行

男孩帐户中一共有10000元,男孩拿着存折从银行取走5000元,女孩拿着男孩的银行卡购物刷卡消费5000元,最后男孩的帐户里却还剩5000元。显然这是不正确的,但是为什么会发生这样的情况呢?我们可以这样分析:男孩可以看作是一条线程,女孩也可以看作是一条线程,在同一时刻,两个线程都操作了同一个资源,那就是男孩的帐户。男孩从查看帐户余额到取走现金应该被看作是个原子性操作,是不可再分的,然而当男孩查看完余额正思考取多少钱的时候,女孩购物消费了5000元,也就是说女孩这条线程打断了男孩这条线程所要执行的任务。所以男孩刚查看完的余额10000元就不正确了,最终导致帐户中少减了5000元。

为了避免这样的事情发生,我们要保证线程同步互斥,所谓同步互斥就是:并发执行的多个线程在某一时间内只允许一个线程在执行以访问共享数据

2、Java中线程互斥的实现机制

由多线程带来的性能改善是以可靠性为代价的,所以编程出线程安全的类代码是十分必要的。当多个线程可以访问共享资源(调用单个对象的属性和方法,对数据进行读、写、修改、删除等操作)时,应保证同时只有一个线程访问共享数据,Java对此提出了有效的解决方案—同步锁。任何线程要进入同步互斥方法(访问共享资源的方法或代码段)时,就必须得到这个共享资源对象的锁,线程进入同步互斥方法后其它线程则不能再进入同步互斥方法,直到拥有共享资源对象锁的线程执行完同步互斥方法释放了锁,下一个线程才能进入同步互斥方法被执行。

Java的这一线程互斥的实现机制可以用一个最通俗的比方来说明:比如公共卫生间就是一个共享资源,每个人都可以使用,但又不能同时使用,所以卫生间里有一把锁。一个人进去了,会把门锁上,其他人就不能进去。当Ta出来的时候,要打开锁,下一个人才能继续使用。

3、利用Synchronized关键字用于修饰同步互斥方法

(1)同步互斥方法

public synchronized void method(){

//允许访问控制的代码

}

(2)同步互斥代码块

synchronized(syncObject){

//允许访问控制的代码

}

(3)锁定整个类

public synchronized class SyncObject{

}

由于synchronized 块可以针对任意的代码块,且可任意指定上锁的对象,因此灵活性较高。但要注意:

l synchronized可以用来限定一个方法或一小段语句或整个类(该类中的所有方法都是synchronized方法)

l 将访问共享数据的代码设计为synchronized方法

l 由于可以通过 private 关键字来保证数据对象只能被方法访问,所以只需针对方法提出一套同步锁定机制。通过synchronized 方法来控制对类中的成员变量(共享数据)的访问。

l 编写线程安全的代码会使系统的总体效率会降低,要适量使用

l 只有某一个线程的synchronized方法执行完后其它线程的synchronized方法才能被执行。

l 当前时间,只有一个线程访问被锁定的代码段,但不能保证其他线程去访问其他没有被锁定的代码段。因此所有对共享资源进行操作的代码段都应该加锁。

l 对数据库操作时,修改数据的线程要加锁,而读数据的线程可以不加锁

有了这种解决方案,我们用线程安全的代码来重新实现一下男孩和女孩取钱的故事。以下是核心代码:

package com.px1987.j2se.thread.synchronous.v2;

/** 帐户类 */

public class Account {

/** 余额 */

private int balance;

public Account(int balance) {

this.balance = balance;

}

}

package com.px1987.j2se.thread.synchronous.v2;

/** 男孩类,实现Runnable接口*/

public class Boy implements Runnable {

/** 银行帐户*/

Account account;

public Boy(Account account) {

this.account = account;

}

/** 男孩拿着折子去北京银行海淀分行取钱*/

public void run() {

System.out.println(男孩拿着折子去北京银行海淀分行取钱);

synchronized (account) {

System.out.println(男孩走到柜台钱询问帐户余额);

int balance = account.getBalance();

System.out.println(银行的业务员小姐亲切地告诉他:您还有 +

balance + 元!。);

try {

System.out.println(男孩在思考要取多少钱呢?);

Thread.sleep(1);

System.out.println(男孩思考了1毫秒);

}

catch (InterruptedException e) {

e.printStackTrace();

}

int money = 5000;

System.out.println(男孩决定取 + money + 元);

System.out.println(银行的业务员小姐为男孩办理相关业务手续);

account.setBalance(balance - money);

System.out.println(交易完成);

System.out.println(银行的业务员小姐告诉男孩:您的余额为 +

account.getBalance()+ 元。);

}

System.out.println(男孩离开银行);

}

}

package com.px1987.j2se.thread.synchronous.v2;

/** 女孩类,实现runnable接口*/

public class Girl implements Runnable {

/** 女孩持有男孩的银行卡*/

Account account;

public Girl(Account account) {

this.account = account;

}

/*** 女孩拿着小军的银行卡去西单百货疯狂购物*/

public void run() {

String tabs = ;

System.out.println(tabs + 女孩拿着小军的银行卡去西单百货疯狂购物);

System.out.println(tabs + 女孩看上了一件时髦的衣裳,准备买下);

synchronized (account) {

System.out.println(tabs + 女孩到收银台准备刷卡消费);

int balance = account.getBalance();

System.out.println(tabs + 收银台刷卡机读取银行卡余额为 + balance + 元);

int payout = 5000;

System.out.println(tabs + 女孩买衣服刷卡消费 + payout + 元);

account.setBalance(balance - payout);

System.out.println(tabs + 消费清单打印出来,消费: + payout + 元 + 余额:

+ account.getBalance() + 元);

}

System.out.println(tabs + 女孩离开商场);

}

}

package com.px1987.j2se.thread.synchronous.v2;

public class Bank {

public static void main(String[] args) {

Account account=new Account(10000);

Thread boyThread=new Thread(new Boy(account));

Thread girlThread=new Thread(new Girl(account));

boyThread.start();

girlThread.start();

}

}

修改后的代码运行结果如下图:

男孩拿着折子去北京银行海淀分行取钱

女孩拿着小军的银行卡去西单百货疯狂购物

女孩看上了一件时髦的衣裳,准备买下

女孩到收银台准备刷卡消费

收银台刷卡机读取银行卡余额为10000元

女孩买衣服刷卡消费5000元

消费清单打印出来,消费:5000元 余额:5000元

女孩离开商场

男孩走到柜台钱询问帐户余额

银行的业务员小姐亲切地告诉他:您还有5000元!,

男孩在思考要取多少钱呢?

男孩思考了1毫秒

男孩决定取5000元

银行的业务员小姐为男孩办理相关业务手续

交易完成

银行的业务员小姐告诉男孩:您的余额为0元。

男孩离开银行

从结果中可以看出来,男孩从查看余额到取钱,女孩没有操作帐户,所以最后的余额是正确的。

4、线程死锁

使用互斥锁容易产生死锁问题。比如:一个线程需要锁定两个对象才能完成,线程1拥有对象A的锁,线程1如果再拥有对象B的锁就能完成操作,线程2拥有对象B的锁,线程2如果再拥有对象A的锁就能完成操作。

很不幸的是线程1执行不下去了,因为线程1等待的资源对象B被线程2锁住了,线程2也执行不下去了,因为线程2等待的资源对象A被线程1锁住了,这样就造成了死锁。

阅读一段文字:由多线程带来的性能改善是以可靠性为代价的,主要是因为有可能产生线程死锁。死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不能正常运行。简单的说就是:线程死锁时,第一个线程等待第二个线程释放资源,而同时第二个线程又在等待第一个线程释放资源。这里举一个通俗的例子:如在人行道上两个人迎面相遇,为了给对方让道,两人同时向一侧迈出一步,双方无法通过,又同时向另一侧迈出一步,这样还是无法通过。假设这种情况一直持续下去,这样就会发生死锁现象。

导致死锁的根源在于不适当地运用“synchronized”关键词来管理线程对特定对象的访问。“synchronized”关键词的作用是,确保在某个时刻只有一个线程被允许执行特定的代码块,因此,被允许执行的线程首先必须拥有对变量或对象的排他性访问权。当线程访问对象时,线程会给对象加锁,而这个锁导致其它也想访问同一对象的线程被阻塞,直至第一个线程释放它加在对象上的锁。

(1)死锁问题的一个代码示例

package com.px1987.j2se.thread.DeadLock;

class Thread1 implements Runnable {

private Object a;

private Object b;

public Thread1(Object a, Object b) {

super();

this.a = a;

this.b = b;

}

public void run() {

synchronized (a) {

System.out.println(Thread1获得对象a的锁);

try {

Thread.sleep(1);

}

catch (InterruptedException e) {

e.printStackTrace();

}

synchronized (b) {

System.out.println(Thread1获得对象b的锁);

try {

Thread.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

}

package com.px1987.j2se.thread.DeadLock;

class Thread2 implements Runnable {

private Object a;

private Object b;

public Thread2(Object a, Object b) {

super();

this.a = a;

this.b = b;

}

public void run() {

synchronized (b) {

System.out.println(Thread2获得对象b的锁);

try {

Thread.sleep(1);

}

catch (InterruptedException e) {

e.printStackTrace();

}

synchronized (a) {

System.out.println(Thread2获得对象a的锁);

try {

Thread.sleep(1);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}

}

package com.px1987.j2se.thread.DeadLock;

public class TestDeadLock {

public static void main(String[] args) {

Object a=new Object();

Object b=new Object();

Thread thread1=new Thread(new Thread1(a,b));

Thread thread2=new Thread(new Thread2(a,b));

thread1.start();

thread2.start();

}

}

下面是运行结果:

(2)死锁问题的另一个代码示例

package com.px1987.j2se.thread.DeadLock;

public class ThreadDeadLock {

public static void main(String[] args) {

ThreadOne threadOne=new ThreadOne();

ThreadTwo threadTwo=new ThreadTwo();

String s1=s;

String s2=sss;

threadOne.op1=s1;

threadTwo.op1=s1;

threadOne.op2=s2;

threadTwo.op2=s2;

threadOne.start();

threadTwo.start();

}

}

class ThreadOne extends Thread{

String op1;

String op2;

public void run(){// 同步中又有同步,就可能死锁

synchronized(op1){

System.out.println(Thread.currentThread().getName()+锁定op1);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized(op2){

System.out.println(Thread.currentThread().getName()+锁定op2);

}

}

}

}

class ThreadTwo extends Thread{

String op1;

String op2;

public void run(){

synchronized(op2){

System.out.println(Thread.currentThread().getName()+锁定op2);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized(op1){

System.out.println(Thread.currentThread().getName()+锁定op1);

}

}

}

上一篇:治理交通建设领域商业贿赂自查自纠实施方案下一篇:出自寓言故事的词语