Java的多线程(精选十篇)
Java的多线程 篇1
线程是允许进行并行计算的一个抽象概念:在另一个线程完成计算任务的同时, 一个线程可以对图像进行更新, 第二个线程可以同时处理同一个进程发出的两个网络请求。从概念上讲, 线程提供了一种在一个软件中并行执行代码的方式-每个线程都“同时”在一个共享的内存空间中执行指令, (当然是在一个处理器上, 这是通过处于运行状态的线程的交替执行完成的。) , 因此, 每个线程都可以访问一个程序内的数据结构。由于这种原因, 多线程编程的难度就可想而知了, 因为一个程序内有许多不同的线程需要安全地共享数据。
1 线程的创建和运行
Java在Java.long.Thread和Java.long.Runnable类中提供了大部分的线程功能。创建一个线程非常简单, 就是扩展Thread类, 并调用start) 。通过创建一个执行Runnable) 的类, 并将该类作为参数传递给Thread () , 也可以定义一个线程。
下面这个简单的Java程序, 其中有2个线程同时在从1数到s, 并将结果打印出来。
可以使用System.Threading.Thread和Sys-tem.
Threading.Thread Start二个类将上述的Java程序转换为C#语言:
这个例子中有一些小技巧。Java允许扩展java.lang.Thread类和执行Java.lang.Runnable接口, C#则没有为我们提供这些便利。一个C#中的Thread对象是不可知的, 必须通过Thread Start进行创建, 这意味着不能使用内部的类模式, 而必须创建一个对象, 而且必须传递给线程一个对象的方法供线程执行用。
2 线程的使用
Java中存在许多编程人员希望能够对线程使用的标准操作:例如, 测试线程是否存在、加人一个线程直到它死亡、杀死一个线程等。
线程管理的函数
Java中java.lang.Thread中的方法和C#中System.Threading.Thread对象的对比。
set Daemon (boolean on) 方法
Is Background设置属性值
使一个存在的进程成为一个新线程 (如果剩下的所有进程都成了新线程, 程序将停止运行) 。
is Daemon) 方法
Is Background获取属性
如果该线程是一个后台线程, 则返回真值。
is Alive) 方法
Is Alive获取属性
如果该线程处于活动状态, 则返回真值。
Interrupt () 方法
尽管在Java中这一方法可以用来设置线程的中断状态, 而且可以用来检查线程是否被中断。在C#中没有相应的方法, 对一个没有处于阻塞状态的线程执行Interrupt方法将使下一次阻塞调用自动失效。
is Interrupted) 方法
n/a
如果该线程处于阻塞状态, 则返回真值。
sleep (long millis) 和sleep (long millis, intnanos)
Sleep (int millisecond Timeout) and Sleep (System.Time Span) 方法
使正在执行的线程暂停一段给定的时间, 或直到它被中断。这一方法将在Java中将产生一个java.lang.Interrupted Exception状态, 在C#中将产生System.Threading.Thread Interrupted Exception状态。
Join () , join (long millis) 和join (long millis, int-nanos) 方法与Java中仅依靠超时设定不同的是, 在C#语言中则依据线程停止运行是由于线程死亡 (返回真) 或是超时 (返回假) 而返回一个布尔型变量。
Suspend () 方法
二者的功能相同。这一方法容易引起死循环, 如果一个占有系统关健资源的线程被挂起来, 则在这一线程恢复运行之前, 其他的线程不能访问该资源。
resume) 方法
恢复一个被挂起的线程。
Stop () 方法
Abort) 方法
参见下面的“线程停止”部分。
3 线程的中止
由于能够在没有任何征兆的情况下使运行的程序进人一种混乱的状态, Java中的Thread.stop受到了普遍的反对。根据所调用的stop) 方法, 一个未经检查的Java.lang.Thread Death错误将会破坏正在运行着的程序的栈, 随着它的不断运行, 能够解除任何被锁定的对象。由于这些锁被不分青红皂白地被打开, 由它们所保护的数据就非常可能陷人混乱状态中。根据当前的Java文档, 推荐的中止一个线程的方法是让运行的线程检查一个由其他的线程能够改变的变量, 该变量代表一个“死亡时间”条件。下面的程序就演示了这种方法。
上述的讨论对C#中的Abort方法也适合。根据调用的Abort方法, 令人捉摸不定的System.Threading.Thread Abort Exception可能会破坏线程的栈, 它可能释放线程保持的一些变量, 使处于保护状态中的数据结构出现不可预测的错误。我建议使用与上面所示的相似的方法来通知一个应该死亡的线程。
结束语
在这篇文章中将重点讨论Java和C#在多线程编程方面的具体实现方法, 阐述了两者实现的不同之处, 并实现了Java中多线程编程的常用模式转换为C#.。
参考文献
[1]陈昊鹏译.Bruce Eckel.Java编程思想[M].第4版.北京:机械工业出版社, 2007.
[2]Kathy Sierra, Bert Bates.深人浅出Java[M].影印版·南京:东南大学出版社, 2005.
Java程序员面试中的多线程问题 篇2
摘要:很多核心Java面试题来源于多线程(Multi-Threading)和集合框架(Collections Framework),理解核心线程概念时,娴熟的实际经验是必需的。这篇文章收集了 Java 线程方面一些典型的问题,这些问题经常被高级工程师所问到。
很多核心Java面试题来源于多线程(Multi-Threading)和集合框架(Collections Framework),理解核心线程概念时,娴熟的实际经验是必需的。这篇文章收集了 Java 线程方面一些典型的问题,这些问题经常被高级工程师所问到。
0.Java 中多线程同步是什么?
在多线程程序下,同步能控制对共享资源的访问。如果没有同步,当一个 Java 线程在修改一个共享变量时,另外一个线程正在使用或者更新同一个变量,这样容易导致程序出现错误的结果。
1.解释实现多线程的几种方法?
一 Java 线程可以实现 Runnable 接口或者继承 Thread 类来实现,当你打算多重继承时,优先选择实现 Runnable。
2.Thread.start()与 Thread.run()有什么区别?
Thread.start()方法(native)启动线程,使之进入就绪状态,当 cpu 分配时间该线程时,由 JVM 调度执行 run()方法。
3.为什么需要 run()和 start()方法,我们可以只用 run()方法来完成任务吗?
我们需要 run()&start()这两个方法是因为 JVM 创建一个单独的线程不同于普通方法的调用,所以这项工作由线程的 start 方法来完成,start 由本地方法实现,需要显示地被调用,使用这俩个方法的另外一个好处是任何一个对象都可以作为线程运行,只要实现了 Runnable 接口,这就避免因继承了 Thread 类而造成的 Java 的多继承问题。
4.什么是 ThreadLocal 类,怎么使用它?
ThreadLocal 是一个线程级别的局部变量,并非“本地线程”。ThreadLocal 为每个使用该变量的线程提供了一个独立的变量副本,每个线程修改副本时不影响其它线程对象的副本(译者注)。
下面是线程局部变量(ThreadLocal variables)的关键点:
一个线程局部变量(ThreadLocal variables)为每个线程方便地提供了一个单独的变量。
ThreadLocal 实例通常作为静态的私有的(private static)字段出现在一个类中,这个类用来关联一个线程。
当多个线程访问 ThreadLocal 实例时,每个线程维护 ThreadLocal 提供的独立的变量副本。
常用的使用可在 DAO 模式中见到,当 DAO 类作为一个单例类时,数据库链接(connection)被每一个线程独立的维护,互不影响。(基于线程的单例)
ThreadLocal 难于理解,下面这些引用连接有助于你更好的理解它。
《Good article on ThreadLocal on IBM DeveloperWorks 》、《理解 ThreadLocal》、《Managing data : Good example》、《Refer Java API Docs》
5.什么时候抛出 InvalidMonitorStateException 异常,为什么?
调用 wait()/notify()/notifyAll()中的任何一个方法时,如果当前线程没有获得该对象的锁,那么就会抛出 IllegalMonitorStateException 的异常(也就是说程序在没有执行对象的任何同步块或者同步方法时,仍然尝试调用 wait()/notify()/notifyAll()时)。由于该异常是 RuntimeExcpetion 的子类,所以该异常不一定要捕获(尽管你可以捕获只要你愿意).作为 RuntimeException,此类异常不会在 wait(),notify(),notifyAll()的方法签名提及。
6.Sleep()、suspend()和 wait()之间有什么区别?
Thread.sleep()使当前线程在指定的时间处于“非运行”(Not Runnable)状态。线程一直持有对象的监视器。比如一个线程当前在一个同步块或同步方法中,其它线程不能进入该块或方法中。如果另一线程调用了 interrupt()方法,它将唤醒那个“睡眠的”线程。
注意:sleep()是一个静态方法。这意味着只对当前线程有效,一个常见的错误是调用t.sleep
(),(这里的t是一个不同于当前线程的线程)。即便是执行t.sleep(),也是当前线程进入睡眠,而不是t线程。t.suspend()是过时的方法,使用 suspend()导致线程进入停滞状态,该线程会一直持有对象的监视器,suspend()容易引起死锁问题。
object.wait()使当前线程出于“不可运行”状态,和 sleep()不同的是 wait 是 object 的方法而不是 thread。调用 object.wait()时,线程先要获取这个对象的对象锁,当前线程必须在锁对象保持同步,把当前线程添加到等待队列中,随后另一线程可以同步同一个对象锁来调用 object.notify(),这样将唤醒原来等待中的线程,然后释放该锁。基本上 wait()/notify()与 sleep()/interrupt()类似,只是前者需要获取对象锁。
7.在静态方法上使用同步时会发生什么事?
同步静态方法时会获取该类的“Class”对象,所以当一个线程进入同步的静态方法中时,线程监视器获取类本身的对象锁,其它线程不能进入这个类的任何静态同步方法。它不像实例方法,因为多个线程可以同时访问不同实例同步实例方法。
8.当一个同步方法已经执行,线程能够调用对象上的非同步实例方法吗?
可以,一个非同步方法总是可以被调用而不会有任何问题。实际上,Java 没有为非同步方法做任何检查,锁对象仅仅在同步方法或者同步代码块中检查。如果一个方法没有声明为同步,即使你在使用共享数据 Java 照样会调用,而不会做检查是否安全,所以在这种情况下要特别小心。一个方法是否声明为同步取决于临界区访问(critial section access),如果方法不访问临界区(共享资源或者数据结构)就没必要声明为同步的。
下面有一个示例说明:Common 类有两个方法 synchronizedMethod1()和 method1(),MyThread 类在独立的线程中调用这两个方法。
public class Common {
public synchronized void synchronizedMethod1(){
System.out.println(“synchronizedMethod1 called”);
try {
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(“synchronizedMethod1 done”);
}
public void method1(){
System.out.println(“Method 1 called”);
try {
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(“Method 1 done”);
}
}
public class MyThread extends Thread {
private int id = 0;
private Common common;
public MyThread(String name, int no, Common object){
super(name);
common = object;
id = no;
}
public void run(){
System.out.println(“Running Thread” + this.getName());
if(id == 0){
common.synchronizedMethod1();
} else {
common.method1();
}
} catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args){
Common c = new Common();
MyThread t1 = new MyThread(“MyThread-1”, 0, c);
MyThread t2 = new MyThread(“MyThread-2”, 1, c);
t1.start();
t2.start();
}
}
这里是程序的输出:
Running ThreadMyThread-1
synchronizedMethod1 called
Running ThreadMyThread-2
Method 1 called
synchronizedMethod1 done
Method 1 done
结果表明即使 synchronizedMethod1()方法执行了,method1()也会被调用。
9.在一个对象上两个线程可以调用两个不同的同步实例方法吗?
不能,因为一个对象已经同步了实例方法,线程获取了对象的对象锁。所以只有执行完该方法释放对象锁后才能执行其它同步方法。看下面代码示例非常清晰:Common 类有 synchronizedMethod1()和 synchronizedMethod2()方法,MyThread 调用这两个方法。
public class Common {
public synchronized void synchronizedMethod1(){
System.out.println(“synchronizedMethod1 called”);
try {
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
System.out.println(“synchronizedMethod1 done”);
}
public synchronized void synchronizedMethod2(){
System.out.println(“synchronizedMethod2 called”);
try {
Thread.sleep(1000);
} catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(“synchronizedMethod2 done”);
}
}
public class MyThread extends Thread {
private int id = 0;
private Common common;
public MyThread(String name, int no, Common object){
super(name);
common = object;
id = no;
}
public void run(){
System.out.println(“Running Thread” + this.getName());
try {
if(id == 0){
common.synchronizedMethod1();
} else {
common.synchronizedMethod2();
}
} catch(Exception e){
e.printStackTrace();
}
}
public static void main(String[] args){
Common c = new Common();
MyThread t1 = new MyThread(“MyThread-1”, 0, c);
MyThread t2 = new MyThread(“MyThread-2”, 1, c);
t1.start();
t2.start();
}
10.什么是死锁
死锁就是两个或两个以上的线程被无限的阻塞,线程之间相互等待所需资源。这种情况可能发生在当两个线程尝试获取其它资源的锁,而每个线程又陷入无限等待其它资源锁的释放,除非一个用户进程被终止。就 JavaAPI 而言,线程死锁可能发生在一下情况。
当两个线程相互调用 Thread.join()
当两个线程使用嵌套的同步块,一个线程占用了另外一个线程必需的锁,互相等待时被阻塞就有可能出现死锁。
11.什么是线程饿死,什么是活锁?
线程饿死和活锁虽然不想是死锁一样的常见问题,但是对于并发编程的设计者来说就像一次邂逅一样。
当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。JavaAPI 中线程活锁可能发生在以下情形:
当所有线程在程序中执行 Object.wait(0),参数为 0 的 wait 方法。程序将发生活锁直到在相应的对象上有线程调用 Object.notify()或者 Object.notifyAll()。
当所有线程卡在无限循环中。
Java多线程访问控制管理分析 篇3
关键词:Java;多线程;同步控制
中图分类号:TP312
1 Java多线程的基本控制
在Java中,一个线程从创建到死亡称为线程的一个生命周期,它的生命周期有多个不同状态,一般为:Enabled、Running、Sleeping、Joining、Waiting、Locking。对于线程的这些状态,Java在Thread类中定义了一些方法可以在程序中手动控制。要结束一个线程的运行,可以使用stop()方法。但是stop()方法并不是非常安全的,它会强制中止一个线程的运行,但是会释放这个线程锁住的所有对象,这些对象被解锁后就可能被其他的线程直接访问,导致错误的结果。因此,要中止一个线程,最好是设置一个标志位,在run()方法中根据这个标记来判断是否继续保持状态。要中止线程的时候,可以使用setRunFlag()方法。
在暂停和恢复线程执行时也会出现同样的问题。可以使用Thread类中supspend()和resume()方法來暂停和恢复,但不建议使用,它们容易造成线程的死锁。解决的方法也是使用一定的标志变量。使用setsuspendFlag(true)可以暂停线程的执行,暂停时间的指定是通过sleep()方法来实现,暂停结束就回到等待队列,不是恢复执行状态。所以sleep()方法不能精确控制线程计划工作的时间尺度。
下面用模拟一个龟兔接力跑的实例来演示Java多线程的sleep()方法的控制。模拟的接力跑中有一只乌龟和一只兔子。当兔子跑完400米将接力棒交给乌龟,乌龟跑完400米后结束。部分代码如下:
2 Java多线程的同步控制
多线程程序中,多个线程可以共享相同资源,这可能导致冲突。Java使用同步机制来解决这个问题。这个同步机制使用synchronized关键字,它主要用于多线程共享数据的同步,使用加锁的机制,控制变量或代码块在某个时间段只能被一个线程访问。
Java中synchronized既可作为方法的修饰符,锁住方法,也可在方法内部锁住部分语句。Synchronized方法和synchronized代码块在使用时是有区别的。将访问共享数据的方法加上synchronized,例如把synchronized当作方法修饰符:
Public synchronized void m1(){……}
这就是同步方法。当某个线程调用这个同步方法时,会开始执行直到完毕为止,其它想要调用这个方法的线程会进入阻塞状态,一直到这个方法获得释放为止。使用synchronized来修饰一个比较大的方法时,也会锁住其它一些不需要锁住的数据,导致程序效率下降。Synchronized块则是把方法内的某段代码使用synchronized来修饰,可以减少同步范围。例如:Public void m1(){ …… Synchronized(so){ …… }}
使用synchronized修饰时,就代表着同步机制启动,某个时刻只能某个线程访问数据和资源,阻止其它线程访问,这种状态即使出现阻塞和死锁现象也不会解除。
3 Java多线程的死锁问题
为了控制资源的访问冲突,需要使用synchronized对访问资源进行加锁,但加锁会带来一个潜在的危险:死锁。如果两个或两个以上的线程在等待两个或两个以上的锁被释放,但实际上这些锁根本就不会被解锁,那么这些线程就会进入无限等待的状态,称之为死锁。线程等待解锁的这种状态有时也被称作堵塞。死锁例子部分代码如下:
4 Java多线程的优先级
在多线程编程中要预防死锁的发生,不然死锁后会导致浪费大量时间和资源。防止死锁的方法很多,最重要的就是防止循环等待的发生。可以使用设置线程的优先级来解决这一问题。同一时刻会有很多在排队等待资源的线程,它们各自的重要性是不同的。可以赋予每个线程不同的优先级,让任务更急的线程获得更高的优先级,更快的进入执行状态。
Java中提供了10个等级的线程优先级,最低为1,最高为10,默认优先级为5。对于一个新创建的线程,如果没有指定优先级,它的优先级将和它的父线程优先级一致。使用setPriority()方法可以为线程指定优先级。
例如:int newPrio=Thread.currentThread( ).getPriority( )+1;
Thread.currentThread( ).setPriority(newPrio);
5 结束语
Java多线程编程可以合理的调用多项任务,功能非常强大。但是多线程共享数据或资源时会导致执行的错误,因此多线程访问控制管理机制在解决数据不一致性、资源冲突等问题时是非常有用的。高质量的使用Java多线程访问控制管理机制可以提高程序运行效率,缩短任务执行的时间。
参考文献:
[1]明日科技.Java从入门到精通(3版)[M].北京:清华大学出版社,2013.
[2]路勇.Java多线程同步问题分析[J].软件,2012(04):31-33.
作者简介:梁德华(1977-),男,江西瑞昌人,华中科技大学软件工程硕士,讲师,研究方向:软件工程、软件技术教学。
Java的多线程机制分析与应用 篇4
关键词:Java语言,多线程,线程同步
0引言
随着计算机技术的迅猛发展, 多任务、分时操作系统可以支持在一台计算机上同时执行多个程序, 从而提高程序运行效率。例如在同一台计算机上一边听音乐, 一边聊天, 同时可下载文档。
面向过程的程序大多数是单线程的, 即一个程序只有一条从头到尾的线索。多线程是指在一个进程中同时运行多个不同的线程, 每个线程分别执行不同的任务。利用多线程程序设计可以将程序任务划分为若干个并行执行的子任务, 提高程序执行效率和系统资源利用率。
多线程的程序能更好地表述和解决现实世界的具体问题, 是应用开发和程序设计的必然趋势, 而多线程机制也是Java的一个重要特性, Java虚拟机正是通过多线程机制来提高程序效率。
1 Java中的线程模型与创建
每个Java程序都有一个默认的主线程, 对于Applica- tion, 主线程是main () 方法执行的线索, 对于Applet, 主线程指挥浏览器加载并执行Java小程序。线程有创建、就绪、运行、阻塞、消亡5种状态 (见图1) , 一个有生命的线程总是处于这5种状态之一。
在Java中开发自己的线程有两种方式:
由于Java中采用的是单一继承, 一个类只能唯一地继承另一个类, 因此在开发中第一种方式限制较大, 不适合多个线程共享资源, 而第二种方式既不影响继承其它类, 也不影响实现其它接口, 所以灵活性更好, 可以方便实现资源共享。
以下例子用于说明线程产生的方式不同而生成的线程的区别:
2 Java中线程同步问题
多线程程序中由于多个线程并发执行, 有时会带来严重的问题, 甚至引发错误, 例如一个银行账户同一时刻只能由一个用户操作, 同一张机票只能卖给一位顾客。为解决这些问题, 在多线程中需要使用同步技术, 以保证共享资源一次只能被一个线程使用, 即一个线程执行临界区的代码时, 其它线程不允许再进入临界区。
Java中当某个对象用synchronized关键字修饰时, 表明该对象在任一时刻只能有一个线程访问, 当有第二个对象访问时必须等待, 该关键字有两种用法:
(1) 方法同步。
(2) 对象同步。
3应用实例
设有若干个售票人员同时出售演唱会门票, 每个售票员的活动可视为一个线程, 待售的票即为各线程共享的资源, 开始售票前, 需要取得某张票的资源, 取不到的情况下可以等待, 开始售票后, 则需要取得对该票的独享控制, 售完票后通知其他售票员该张票已售出, 未售出时通知其他人可以拿到该票的出售权。
程序代码如下:
Java线程及多线程技术及应用 篇5
多个线程同时访问或操作同一资源时,很容易出现数据前后不一致的问题。请看下面的例子:
男孩拿着折子去北京银行海淀分行取钱
女孩拿着男孩的银行卡去西单百货疯狂购物
男孩走到柜台钱询问帐户余额
银行的业务员小姐亲切地告诉他:您还有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);
}
}
}
Java的多线程 篇6
多线程下载工具的出现,为网络上存储的丰富的信息资源提供了快速下载的技术和方法。多线程技术使得编程人员可以很方便地开发出具有多线程功能、能同时处理多个任务的功能强大的应用程序。本文中研究和实现的多线程下载工具具备良好的用户界面,并且用户操作简单,只需通过简单的“添加”、“删除”、“暂停”、“暂停所有任务”、“开始所有任务”以及“退出”等按钮就可以实现资源的下载和相关操作。因为软件的操作思路清晰,而且用户实际操作的内容较少,所以用户只需要熟练操作计算机即可。
1 下载工具功能需求
本文研究目标主要是使用Java开发一个多线程下载工具,本文的多线程下载工具包括主界面和添加下载资源的界面。下载工具的主界面主要向用户展示下载资源的信息,实现下载资源的暂停、开始和删除等相关操作,例如显示正在下载的资源、已下载完成的资源、暂停正在运行的资源、重新开始已经暂停的资源、暂停所有下载资源、开始所有暂停资源、删除选中的下载资源等等。添加下载资源界面主要让用户输入需要下载的资源url地址,让下载工具可以对该资源进行连接与下载,创建一个下载资源。同时该多线程下载工具还实现了断点续传功能,能够实现继续开启上次意外中断的下载,接着上次下载的功能。当用户单击“退出”按钮后系统便会退出整个系统。本文研究和实现的基于Java的多线程下载工具的目的就是要实现基于HTTP、支持断点续传、具有多任务同时下载能力的多线程下载工具。具体需求如下:
(1)下载任务实体类。对于一个多线程下载工具来说,下载资源的所有信息都被定义在一个实体类中。在下载之前,首先要知道资源对应的URL地址;其次要知道下载任务对应文件的大小,文件的大小用字节来衡量;然后需要知道下载任务使用几个线程;这样才能知道每个线程需要下载的文件大小;最后还需要知道文件的存放目录和文件名。此外为了支持断点续传功能,在下载实体类中还需要定义上次文件下载长度和当前文件长度;为了计算下载速度,还需要知道下载时间,下载任务实体类主要包括如上属性及相关方法。
(2)资源拆分块类。对于本文所研究的多线程下载工具来说,当程序获得下载任务对应文件的大小和下载任务使用的线程数之后,就需要将下载资源根据线程数进行均分,获得每个文件块的开始位置和结束位置。如果一个资源大小是100,被分成5份,那么第一个Part对象的开始点就是0,结束点就是19,长度是20。同时,为了实现断点续传功能,文件块类中需要定义一个当前文件长度。例如,当文件下载长度为10时,下载突然发生了中断,此时就要求记录当前的下载长度。
(3)线程下载类。线程下载类负责定义每个线程具体下载的实现细节,该类本身就是一个线程类,是一个继承于Thread类的子类。该类通过文件传输过来的文件块、资源对象和随机读写文件来执行文件块的下载功能。这个类主要是通过调用Java输入流对象Object Input Stream的read()方法实现读取下载资源,通过调用随机文件的write()方法将资源写入到临时创建的临时文件。
(4)多线程调度类。多线程调度类其实就是一个下载处理类。下载处理类是整个程序运行的核心,定义了线程下载的执行流程。它通过下载资源类对象的信息,获取资源大小,然后根据线程数将资源进行分块,对每个文件块分别创建一个线程下载类对象,最后调用Thread类的start()方法启动单个线程的下载。该下载处理类主要是用来实现多个线程的调度实现策略。
(5)系统工具类。系统工具类主要是负责删除临时创建的文件块、根据RUL地址获得文件名、根据文件块和资源获得临时文件的绝对路径等。
(6)资源信息管理。资源信息管理主要是通过一个工具栏进行实现,该工具栏上有新建、暂停、开始、开始所有任务、暂停所有任务和退出等资源管理操作。
2 下载工具设计与实现
本文研究和实现的基于Java的多线程下载工具主要包含三大功能模块即下载功能实现模块、断点续传实现模块和界面实现模块。其中下载功能实现模块主要包括单线程下载、多线程下载、多任务下载等功能。断点续传实现部分主要包括序列化资源信息、继续开启下载。界面实现模块主要包括工具栏的资源管理、资源列表信息显示和新建下载任务窗口。下载工具的功能结构如图1所示。
2.1 下载功能实现部分
(1)单线程下载:实现网络连接;对流进行处理、读出、写入;进行下载流的保存。
(2)多线程下载:每一个文件进行分块;每一分块的下载处理;下载完成后对每一分块文件的整合。
(3)多任务下载:新建下载任务;向下载信息表格中添加一个下载信息;进行多线程任务的调度。
2.2 断点续传实现部分
(1)下载过程中,意外中断,暂停下载资源,然后序列化该资源。
对下载的信息进行临时保存,主要是通过将全局状态变量设置为“PAUSE”状态,然后调用“PAUSE”的结构体对象,调用stopTimer()方法,关闭输入流,暂停该资源后就会对该资源进行序列化,以便下次继续下载。
(2)断点续传中,----继续开启下载,接着上次下载。
序列化一个对象,该对象必须实现Serializable接口,而且各个对象中的属性必须可以序列化的,同时为主界面类提供一个Serializable的方法,用于程序退出时进行序列化。这样当程序再次运行时,就会对上次的文件进行反系列化,实现接着上次下载。
多线程调度类对象主要是在进行文件分割前创建的,该对象主要用于处理文件的下载与继续下载,该类提供doDownload方法,用户新建下载资源时,可以调用该方法实现对多线程的调度。doDownload方法的部分实现如下。
2.3 界面实现部分
主界面工具栏资源管理的实现包括:新建任务;退出程序;暂停资源;开始资源;暂停所有下载资源;开始所有下载资源;删除。
3 结语
本文研究并实现的基于Java的多线程下载工具是一个具有基于Http的、支持断点续传的、具有多任务同时下载的多线程系统,该系统是一款方便易用且功能强大、便于扩展的下载软件。
参考文献
[1]李兴华.Java开发实战经典[M].北京:清华大学出版社,2009.
浅谈Java多线程编程 篇7
创建线程需要一定的步骤, 首先要创建线程, 然后为其指定工作, 当工作结整后再毙掉该线程。通常在Java中线程的编程形式通常有两种, 第一种所创建的线程类是经过继承Thread类实现的, 再用该线程重载run () ;另外一种是建立一个Runnable接口类, 因为Java无法支持多继承性, 因此如果要求类通过线程方式运行, 并且继承其它类, 就要采用唯一的方法——run () 来实现Runnable接口, 该方法就成为线程主函数。不同的程序运行方式不同所产生的结果也必然不同, 主要由于两个原因, 一是由于循环中有个随机暂停, 另外一个重要的原因是由于无法保证线程的执行时间。Java按照其时间表来运行这些进程, 每个线程可以有一个优先级与其相关联。
2 设置线程的优先级
利用调度程序来完成线程的运行, 调度方法有协作和抢先两形式, 相比之下抢先式在资源调度方面较之协作式有更大优势, Java恰恰支持抢先式, 所以分配优先级能力在多线程程式中十分重要, 线程调度的决策就是以其为参照进行, 程式中的某些部分也会根据其重要程度而变化, 从而保证它的优先级与其价值是相对应的。优先级的设这要注意几点:一是确定优先级必须采用1~10之间的整数;二是父线程的优先级要被子线程所继承;三是线程的优先级可以通过setpriority () 的调用进行改变。Java会先择最高优先级的线程来执行, 因此编写程式时, 要注意有些线程优先级较低, 但也要给其执行的机会, 此时优先级高的线程可以适当进入休眠状态。
3 同步机制
在多线程程式中, 所有线程都是各自独立的执行体, 不过线程代码的模式却是相同的, 如果这些线程要协同工作, 那么一定要注意其线程代码是不是能够重入控制, 所以Java就提供了相应的同步机制, 从而阻止多个线程的一个或者多个关键代码在任意时间执行该代码。该机制是建立在锁的概念和监视器基础上的, 其中监视器是关键代码周围的保护, 锁则是监视器阻止线程进入监视器的软件。其基本理念是:如果一个线程要进入监视器所监视的关键代码, 该线程要获取一个与之相关的锁;如果别的线程在使用这个锁, 那么Java就会强制性的要求其在一个与锁以及监视器相关的区域进行等待;当锁被释放时, Java就移出等待状态中的线程, 同意其获取琐, 并对监视器的关键代码进行相应处理。Java自带monitorexit以及monitorenter指令与锁和监视器同步工作, 不过这种级别相对较低, 我们可以使用以下两种方法执行线程同步:
3.1 synchronized方法
把synchronized的关键字加入到方法声明中, 来声明synchronized () 方法。在synchronized () 方法中, 类成员变量访问由synchronized () 方法控制, 每个类实例都会对应一个琐, 如果synchronized () 方法要执行就要获取相应的锁, 否则会阻塞所属的线程。一旦该方法进入执行状态就会独占一个锁, 并且直至其返回才会释放。这种机制可以确保在同一时间对每个类实例, 它的声明为synchronized的成员函数最多只能有一个处于执行状态, 从而避免了类成员变量访问产生冲突。JAVA中每个类都对应一把这样的锁, 因此要控制其访问类的静态成员变量, 就可以把类的静态成员函数声明为synchronized。
3.2 synchronized块
上述方法中, 如果代码量较多, 则声明的效率会大受影响, 因此我们可以用synchronized块来解决该问题。synchronized块中要获取SynObject对应的锁才可以执行, 因为可以对任何代码块并且上锁的对象可以任意指定, 因此相对synchronized块方法有较高的灵活性。
4 线程阻塞和死锁
既然进行多线程编程就不得不考虑访问共享资源的问题。虽然上述JAVA同步机制可以控制线程代码重入, 但很多时候多个线程在各自的执行过程中, 会访问诸如存储处理等同一处资源, 因为线程不同, 其所执行的时机也不一致, 所以为了防止不同线程在访问共享资源时修改其内容, JAVA就提供了相应的阻塞机制来解决该问题, 支持阻塞的方法有以下几种:
1) 线程阻塞的方法
第一种是sleep () :该方法同意指定毫秒为单位的时间段作为参数, 可以使线程在其指定时间段内不用得到CPU时间还可以进入阻塞状态, 当指定时间过去后, 线程即可进入执行状态;第二种是resume () 以及suspend () :要将这两种方法进行配合使用, suspend () 会使线程保持阻塞状态, 并且无法自动恢复, 只有当调用resume () 时, 才可以重新使线程进入执行状态;第三种是yield () :该方法使得线程放弃当前得到的CPU时间, 但是无法阻塞线程, 因此线程仍然处于执行状态中, 并且可以随时再取得CPU时间。当调用yield () 时其效果等价于调度程序, 认为该线程已经执行了足够的时间, 转达向另一个线程;第四种方法是notify () 以及wait () :要将两种方法配合使用。其中wait () 有两种形式使线程进入阻塞状态, 一种是同意指定一段时间为参数, 如果与其以对应的notify () 超出所指定的时间或者被调用, 线程可以重新执行;另外一种是不指定参数, 此时就要等到调用notify () 才可以解除。这种配套使用的方法与上述resume () 、suspend () 的区别就在于该方法阻塞时不会释放锁, 而resume () 、suspend () 刚好相反。
2) 注意事项
notify () 和wait () 属于Thread类, 而resume () 和suspend () 则属于Object类, 即所有对象都有这一对方法, 在阻塞时要释放所占用的锁。由于任何对象都有锁, 所以调用任何对象wait () 法都会导致线程阻塞, 且对象的锁也被释放;如果调用任何对象notify () 法, 就会导致调用该对象的wait () 法阻塞线程中随机选择任意一个解除阻塞。此外, 调用不指定超时期限的wait () 法以及suspend () 法, 都有产生死锁的可能, JAVA在语言级别上并不支持避免死锁, 因此在编程过程中要注意加以控制, 避免死锁。
参考文献
[1]王昕.Java多线程机制在并发编程中的应用[J].现代商贸工业, 2010 (17) .
[2]张迎, 徐洪珍, 汤彬.耘夕扣多线程技术及其在网络编程中的应用[J].科技广场, 2008 (5) .
[3]李丹塞.Java多线程编程技术试议[J].软件开发与设计, 2010 (2) .
[4]赖万钦.JAVA多线程编程技术探讨[J].福建电脑, 2008 (6) .
Java线程池的研究与实现 篇8
多线程技术主要是针对CPU解决高效执行任务的问题, 使用多线程技术可以增加CPU的并行处理能力, 但是在服务器上创建线程和销毁线程却是比较花费时间的, 而线程的执行时间取决于任务的本身。所以如果在服务器上创建线程和销毁线程的时间之和明显大于线程的执行时间, 那么就可以考虑采用线程池, 线程池这种技术就是为了解决减少线程的创建时间和销毁时间, 在客户端的任务请求来到之前, 服务器已经创建好了多个线程并把它放在线程池里以供客户端的任务使用, 从而提高服务器端的性能。为了达到 这种目的 , 实现的线程池主要包括以下4个基本组成部分:
(1) 线程池管理类 : 主要用于事先创建线程和添加客户端请求的新任务, 执行任务以及如何去回收已经执行完任务的线程。
(2) 工作线程类 : 线程池中线程 , 它主要用于处理任务队列中的任务。
(3) 任务类: 定义任务的各种属性, 以及要完成的操作等。
(4) 任务队列 : 按先来先服务的顺序用于存放新加入的任务, 以便让工作线程来执行。
2 任务队列的实现
任务队列用来存放要处理的任务, 它使用了一个集合类, 但是集合本质上是非多线程安全的, 通常的类Map、Set以及List都不是线程安全的 , 当多个线程与集合进行数据交互时 , 为了使多线程安全, 必须采取适当的措施, 在文中通过Col-lections.synchronized List将其转换为一个线程安全的类。任务队列定义如下所示:
3 任务类的实现
任务类 (Task) 其实就是一个线程类, 它实现了Runnable接口, 在run () 方法里面可以定义任务要完成的操作。主要代码如下:
4 工作线程类的实现
工作线程类也是一个线程类, 它也实现了Runnable接口, 在run () 方法里面它首先判断任务队列里面是否有任务, 如果没有就等待新任务加入, 如果有任务就从任务队列中取出任务并执行, 在线程池中可以设定工作线程的个数。在run () 方法中的主要代码如下:
5 线程池管理类的实现
线程池管理类的实现采用了单例设计模式, 单例设计模式是一种常用的软件设计模式。提供了对唯一实例的访问方式。通过单例设计模式可以保证系统中一个类只有一个实例被外界访问, 对于线程池来说, 单例设计模式是最好的实现。在线程池管理类中主要有增加任务的方法 (add Task) 、批量增加任务的方法 (batch Add Task) 、得到实例的方法 (get Instanse) 以及执行任务的方法 (execute) , 主要代码如下:
6 结语
介绍了线程池的原理以及优点, 并结合单例设计模式来实现线程池, 并给出了主要的代码。可以很容易把线程池应用在服务器对客户端任务的管理, 通过运用线程池, 可以提高线程资源的使用率, 从而提高系统的效率。
参考文献
[1]阎宏.Java与模式.电子工业出版社, 2002.
[2]Bruce Eckel.Java编程思想.4版.机械工业出版社, 2007.
Java多线程同步机制的应用分析 篇9
传统的程序大多是单线程的,即一个程序只有一条从头至尾的执行线索。然而很多过程都具有多条线索同时动作的特性。比如一个网络服务器可能需要同时处理多个客户机的请求。
从操作系统的角度来看,多个线程的并发执行,主要是指在逻辑上达到的“同时”。如果系统中只有一个CPU,那么真正意义上的“同时”是无法实现的。只是因为CPU的高执行速度使得当多个线程间快速进行切换时,用户根本无法察觉到这种变化的发生,因此用户就会认为多个线程都是同时在工作。
1 Java多线程概念
Java在语言级提供了对多线程程序设计的支持,并且还是第一个在语言级支持多线程程序设计的编程语言[1]。线程是程序中的一个执行流,多线程是指一个程序中包含多个执行流,多线程是实现并发机制的一种有效手段。线程又称轻量级进程,和进程一样,拥有独立的执行控制,由操作系统负责调度;线程没有独立的存储空间,和所属进程中的其它线程共享同一存储空间,因此线程间的通信远较进程简单。Java线程调度器主要支持不同优先级线程间时间片的抢占方式,但对相同优先级线程间的时间片轮换方式却并不支持。只是Java运行时,如果该系统所在的操作系统本身是支持时间片的轮换的,则线程调度器就能支持相同优先级线程间的时间片轮换[2]。
Java语言引入了包的概念,使得类的继承更简便,线程的创建就是一个最好的例子。在Java中要创建一个新的线程,首先要声明一个类。Java多线程的创建有两种方式:继承Thread类和实现Runnable接口。下面对这两种方式分析如下:
让一个类去继承Thread类,然后调用Thread类中的start方法,启动这个线程,JVM就会去调用该线程的run方法,而用户写在run方法中的代码就会被执行了。继承Thread类使用方法比较简单,但其有一个明显的缺点,因为java语言只允许类的单继承,若声明的类已经从一个类继承下来(小程序必须继承自Applet类),此时就无法再继承Thread类。
Java语言中提供的另外一种开发多线程的方法是实现Runnable接口,并且在该类中定义用于启动线程的run方法[3]。其实Thread类本身也实现了Runnable接口。用实现Runnable接口来创建多线程应用对象可以继承其它对象而不是必须继承Thread类,从而能够增加类定义上的逻辑性。使用Runnable接口来实现多线程能在一个类中包容所有的代码,有利于封装。其实实现Runnable接口和从Thread类派生而来,这两者的表现行为完全一样。通常情况下,如并不需要修改这个线程类当中除了run方法之外的其它行为的方法,可以实现Runnable接口。对于采用实现Runnable接口这种方式去创建一个线程,若有多个线程要访问同一种资源,是极为方便的[4]。本文将采用实现了Runnable接口来实现Java的多线程技术。
Java应用程序的多个线程共享同一进程的数据资源,多个用户线程在并发运行过程中可能同时访问临界区的内容,为了程序的正常运行,在Java中定义了线程同步的概念,实现对临界区共享资源的一致性的维护。利用synchronized关键字实现多线程同步可以通过synchronized块和synchronized方法两种方式。当然也可以在程序中使用synchronized同步方法,但程序的运行效率会有所降低。
2 多线程同步机制
2.1 实验
对于一个售票系统,需要有多个窗口同时为多个旅客服务。假如当前有四个窗口,则需要创建四个线程来满足售票的服务。具体的程序如下:
对这段程序进行编译并运行,但是该段程序的运行结果却出现了问题:当还剩下最后一张票时,由于时间片的关系,窗口会打印出0,-1不正确的票,最糟糕的情况是打印-2。虽然这种错误并不会经常性地出现,只会在系统长时间的运行中偶尔发生。但这样的错误一旦出现,其后果就是致命的。针对这种状况,可以通过Thread类中的sleep方法使进入到if语句的线程休眠一小段时间,以给出关于错误的清晰描述。在具体的运用时,此方法则会抛出throws Interrupted Exception的异常,需要对这个异常进行捕获。在后面程序中,由于还会用到此方法,此处就不再赘述了。现在给出运用sleep方法后的程序错误结果,如图1所示。
程序中有如下代码,词类代码被称为临界区。
所谓“临界区”是指在一个多线程的程序中,单独的并发的线程访问代码段中的同一对象,则此代码段就叫做临界区。同一进程的多个线程共享同一片存储空间,在带来方便的同时,也产生了访问冲突的问题。因此需要运用同步机制进行协调管理,即对临界区实施保护,从而避免程序可能出现不确定的状况。
2.2 Java同步机制的分析
同步是指有多个线程在临界区上的互相排斥等待和互通消息,可分为共享存储体的同步和分布式系统的同步。在本文中主要讨论共享存储体同步机制[5]。Java语言引用Synchronized关键字来定义临界资源和同步方法,对于临界资源的保护通常由一些同步块或同步方法完成,即通过运行系统给临界资源对象加锁得到保证。在Java语言中,每个对象都有一个互斥锁,而任何线程对象在进入synchronized锁保护的代码段时首先要获得该互斥锁,代码段执行结束时则释放此互斥锁,若某个线程想要获取的互斥锁已经被其他线程抢先占有,则此线程将会进到互斥锁的等待队列中,直到占用互斥锁的线程将其释放之后,该等待线程才可以进入到同步代码段去执行。
(1)synchronized方法实现线程同步
通过在方法声明中加入synchronized关键字来指定该方法为同步方法。同步方法控制对类成员变量的访问,控制过程如下:
类的所有对象都对应一把互斥锁,而每个同步方法只有在获得调用该方法的类的对象的互斥锁后才能执行程序,否则该线程将发生阻塞。同步方法一旦进入执行,就独占互斥锁,直至从该方法返回时才将互斥锁释放,被阻塞的线程方能获得互斥锁,从而进入可执行状态。此种机制确保了在同一时刻,对于每一个类实例,其所有声明为synchronized的成员方法中至多只有一个处于可执行状态,从而有效避免了类成员变量的访问冲突。需要注意的是,对于同步方法使用的互斥锁只能是this变量所代表的对象的锁。
(2)synchronized块实现线程同步
Java语言中,可通过synchronized关键字来声明synchronized块。语法如下:
synchronized块中的代码段必须要在线程获得对象obj的锁后才能执行,具体的同步过程与同步方法类似。但在同步块中所使用的对象锁可以是任意的,因而程序将具有更高的灵活性。本文主要采用同步机制中的同步块来解决临界区所产生的访问冲突问题。
2.3 利用同步机制后的正确程序
在实验对应的程序中加入synchronized关键字,并引入java多线程同步思想改进程序,编译后运行,结果表明完全正确。程序如下:
在程序中运用了多线程的同步机制,程序运行的结果显示为:票全部销售出去,并且票号连续。此时的运行结果如图2所示。
本段程序主要是利用同步机制对临界资源给予了相应的保护,从而确保了程序运行结果的正确。在Java语言中,关于同步思想,引入了“互斥锁”的概念。每个对象都对应于一个可称为“互斥锁”的标记,或者叫监视器(monitor),该锁用来保证在某一时刻,只能有一个线程访问该对象[6]。关键字synchronized与对象的锁相关联。当某个对象用synchronized声明时,则表示该对象在某一时刻只能由一个线程访问,因而实现了对临界资源的互斥访问。synchronized锁定一段代码,可称为创建了一个代码临界区,其后线程必须等候特定资源的所有权。
本例中同步机制的具体实施过程:当线程1进入到同步的代码段时,首先判断obj的监视器是否加锁,若还未加锁,则将obj的对象锁上,然后顺序执行,直到遇到sleep代码时,休眠10ms。线程2开始运行,运行到同步对象时,发现obj的监视器已经被加锁了,JVM就将线程2送入一个等待区域。同理,线程3,线程4也相继被送入等待区域中。当线程1被唤醒之后,继续向下执行,直到代码结束,将obj的监视器解锁。接下来,线程2进入到同步代码段中并获得该互斥锁。依次类推,于是形成了多个线程对同一个对象的“互斥”使用方式,该对象则称为“同步对象”。
3 与同步的其他相关问题
3.1 同步失效状况分析
当一个线程进入到临界区中,首先需要获得该对象的互斥锁,退出临界区后,则释放互斥锁。如此其他线程才可以得到该互斥锁而进入到临界区。为了防止有多个线程同时进入到临界区,所有的线程应该共享同一把互斥锁。如果每个线程都有自己的专属锁,那么就无须等待另一线程释放共享的互斥锁,所有线程都能进入临界区,此时的后果与第一段程序不加锁的情况是一致的,这也失去了线程同步的意义[7]。
为了防止上述情形的发生,所有的线程应竞争同一互斥锁,以此保证在某一时刻只能有一个线程可以进入相关的临界区。另外,由于线程需要运行run()方法,因此不能对各线程的run()方法加以同步,否则多线程试图同步一个对象,每个单位时间只有一个线程可以执行run()方法,将会出现的结果就是每个线程必须等待前一个线程运行结束后才开始执行,因而使同步彻底失效。
3.2 线程死锁分析
在Java程序中,多线程使用了同步机制,但可能出现这样的情况:有两个线程1和2。线程1锁住了对象A的监视器,等待对象B的监视器;线程2锁住了对象B的监视器,等待对象A的监视器,而每个线程都只能等待来获得锁,两个线程都不能顺利执行,因其都不能释放对方所需要的锁,程序此时就进入到了死锁的状况。“哲学家进餐的经典实例”很好地诠释了死锁问题。在Java.lang.Thread类中的两个方法suspend、resume就很容易造成线程间的死锁,所以已经建议不再使用这两个方法。
4 结束语
本文首先描述多线程的创建方式,然后分析了同步机制为多线程程序带来的保障,并借助多线程的同步机制有效地避免系统在票已售完后,还会继续打印出0票和负票的情形,从而控制了在售票系统中购票时有多个线程共享同一个变量时所发生的访问冲突问题。
摘要:在一个程序中代码段从单独的并发的线程当中访问了同一个对象,这个代码段是临界区。对“临界区”要进行保护,Java语言采用管程机制,提供同步语法对临界区进行保护。只要了解了同步内部的实现机制,编写出安全高效的多线程程序会更加灵活。利用Java多线程同步机制,可以协调管理由访问冲突所产生的一系列问题。将这种机制运用到售票系统中来解决具体的实际问题,得到了令人满意的结果。
关键词:临界区,多线程,同步,协调管理
参考文献
[1]耿祥义.Java2实用教程[M].第3版.北京:清华大学出版社,2010-10.
[2]张桂珠,张平,陈爱国.Java面向对象程序设计[M].第3版.北京:北京邮电大学出版社,2010-08.
[3]叶核亚.Java2程序设计实用教程[M].第2版.北京:电子工业出版社,2007-01.
[4]陈益.利用Java多线程并发机制解决生产者-消费者问题[J].电脑学习,2010(1):147-149.
[5]李双权,陈火炎,孙玉霞.Java多线程同步机制[J].现代计算机,2003(1):23-25.
[6]于晓阳,龚小兵.JAVA多线程的实现[J].科教文汇,2008(5):201.
Java的多线程 篇10
一、程序示例1
我们写一个简单卖票程序,使用多线程去卖10张票,4个窗口卖10张票,我们给出第一个Java多线程程序代码及运行结果:
源代码:
运行结果:
窗口1-----卖出的票9窗口3-----卖出的票8窗口4-----卖出的票7窗口1-----卖出的票6窗口2-----卖出的票5窗口3-----卖出的票4窗口4-----卖出的票3窗口2-----卖出的票2
窗口1-----卖出的票1窗口3-----卖出的票0窗口4-----卖出的票-1窗口2-----卖出的票-2
结果分析:出现卖出的票0,-1,-2张票,很显然不符合逻辑,原因在于线程休眠Thread.sleep(10)语句,当前线程进入休眠,但是CPU不会休息,马上会调入其他线程来处理,可是当前线程并没有处理完毕(卖出票),造成还有多余的票没有卖完,并由其他线程去卖(出现0,-1,-2张票)。如果没有线程休眠语句,运行结果符合逻辑,不会出现0,-1,-2张票。
二、程序示例2
同样写一个简单的多线程卖票程序,使用线程同步来处理共享资源块,使用Java关键字synchronized来写一个同步方法来卖票,该同步方法在同一时刻只能由一个线程来访问,其他线程等待,保证了资源的安全性。
源代码:
运行结果:
窗口1-----卖出的票10窗口1-----卖出的票9窗口1-----卖出的票8窗口3-----卖出的票7
窗口3-----卖出的票6窗口3-----卖出的票5窗口3-----卖出的票4窗口3-----卖出的票3
窗口3-----卖出的票2窗口3-----卖出的票1
结果分析:很显然运行结果符合我们的逻辑。
三、Java多线程产生安全性问题总结
Java多线程产生的安全性问题,使用同步方法能解决很好的解决多线程安全问题,特别是解决了多线程同时访问共享数据时产生的安全问题,这就需要我们在代码设计上入手,养成良好的编码习惯和规范。
参考文献
[1]传智播客高教产品研发部.java基础入门.清华大学出版社
[2]Eric.Java编程思想[M].第4版.机械工业出版社
相关文章:
演变特点02-16
产生特点02-16
动作特点02-16
糖尿病合并癫痫持续状态临床特点及治疗方案02-16
特点英语02-16
办学特点02-16
思维特点02-16
基本特点02-16
《水浒传》人物特点及其表现特点事例02-16