Java多线程编程基础.docx
- 文档编号:28150239
- 上传时间:2023-07-08
- 格式:DOCX
- 页数:33
- 大小:191.84KB
Java多线程编程基础.docx
《Java多线程编程基础.docx》由会员分享,可在线阅读,更多相关《Java多线程编程基础.docx(33页珍藏版)》请在冰豆网上搜索。
Java多线程编程基础
Java多线程设计模式-wait/notify机制
通常,多线程之间需要协调工作。
例如,浏览器的一个显示图片的线程displayThread想要执行显示图片的任务,必须等待下载线程downloadThread将该图片下载完毕。
如果图片还没有下载完,displayThread可以暂停,当downloadThread完成了任务后,再通知displayThread“图片准备完毕,可以显示了”,这时,displayThread继续执行。
以上逻辑简单的说就是:
如果条件不满足,则等待。
当条件满足时,等待该条件的线程将被唤醒。
在Java中,这个机制的实现依赖于wait/notify。
等待机制与锁机制是密切关联的。
例如:
synchronized(obj){
while(!
condition){
obj.wait();
}
obj.doSomething();
}
当线程A获得了obj锁后,发现条件condition不满足,无法继续下一处理,于是线程A就wait()。
在另一线程B中,如果B更改了某些条件,使得线程A的condition条件满足了,就可以唤醒线程A:
synchronized(obj){
condition=true;
obj.notify();
}
需要注意的概念是:
◆调用obj的wait(),notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj){...}代码段内。
◆调用obj.wait()后,线程A就释放了obj的锁,否则线程B无法获得obj锁,也就无法在synchronized(obj){...}代码段内唤醒A。
◆当obj.wait()方法返回后,线程A需要再次获得obj锁,才能继续执行。
◆如果A1,A2,A3都在obj.wait(),则B调用obj.notify()只能唤醒A1,A2,A3中的一个(具体哪一个由JVM决定)。
◆obj.notifyAll()则能全部唤醒A1,A2,A3,但是要继续执行obj.wait()的下一条语句,必须获得obj锁,因此,A1,A2,A3只有一个有机会获得锁继续执行,例如A1,其余的需要等待A1释放obj锁之后才能继续执行。
◆当B调用obj.notify/notifyAll的时候,B正持有obj锁,因此,A1,A2,A3虽被唤醒,但是仍无法获得obj锁。
直到B退出synchronized块,释放obj锁后,A1,A2,A3中的一个才有机会获得锁继续执行。
Java多线程编程基础之线程和多线程
随着计算机技术的发展,编程模型也越来越复杂多样化。
但多线程编程模型是目前计算机系统架构的最终模型。
随着CPU主频的不断攀升,X86架构的硬件已经成为瓶,在这种架构的CPU主频最高为4G。
事实上目前3.6G主频的CPU已经接近了顶峰。
如果不能从根本上更新当前CPU的架构(在很长一段时间内还不太可能),那么继续提高CPU性能的方法就是超线程CPU模式。
那么,作业系统、应用程序要发挥CPU的最大性能,就是要改变到以多线程编程模型为主的并行处理系统和并发式应用程序。
所以,掌握多线程编程模型,不仅是目前提高应用性能的手段,更是下一代编程模型的核心思想。
多线程编程的目的,就是"最大限度地利用CPU资源",当某一线程的处理不需要占用CPU而只和I/O,OEMBIOS等资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。
从根本上说,这就是多线程编程的最终目的。
[第一需要弄清的问题]
如同程序和进程的区别,要掌握多线程编程,第一要弄清的问题是:
线程对象和线程的区别。
线程对象是可以产生线程的对象。
比如在java平台中Thread对象,Runnable对象。
线程,是指正在执行的一个指点令序列。
在java平台上是指从一个线程对象的start()开始,运行run方法体中的那一段相对独立的过程。
鉴于作者的水平,无法用更确切的词汇来描述它们的定义。
但这两个有本质区别的概念请初学者细细体会,随着介绍的深入和例程分析的增加,就会慢慢明白它们所代表的真实含义。
天下难事必始于易,天下大事必始于细。
让我们先从最简单的"单线程"来入手:
(1)带引号说明只是相对而言的单线程,
(2)基于java。
classBeginClass{
publicstaticvoidmain(String[]args){
for(inti=0;i<100;i++)
System.out.println("Hello,World!
");
}
}
如果我们成功编译了该java文件,然后在命令行上敲入:
javaBeginClass
现在发生了什么呢?
每一个java程序员,从他开始学习java的第一分钟里都会接触到这个问题,但是,你知道它到底发生发什么?
JVM进程被启动,在同一个JVM进程中,有且只有一个进程,就是它自己。
然后在这个JVM环境中,所有程序的运行都是以线程来运行。
JVM最先会产生一个主线程,由它来运行指定程序的入口点。
在这个程序中,就是主线程从main方法开始运行。
当main方法结束后,主线程运行完成。
JVM进程也随之退出。
我们看到的是一个主线程在运行main方法,这样的只有一个线程执行程序逻辑的流程我们称
之为单线程。
这是JVM提供给我们的单线程环境,事实上,JVM底层还至少有垃圾回收这样的后台线程以及其它非java线程,但这些线程对我们而言不可访问,我们只认为它是单线程的。
主线程是JVM自己启动的,在这里它不是从线程对象产生的。
在这个线程中,它运行了main方法这个指令序列。
理解它,但它没有更多可以研究的内容。
[接触多线程]
classMyThreadextendsThread{
publicvoidrun(){
System.out.println("Threadsay:
Hello,World!
");
}
}
publicclassMoreThreads{
publicstaticvoidmain(String[]args){
newMyThread();
newMyThread().start();
System.out.println("Mainsay:
Hello,World");
}
}
执行这个程序,main方法第一行产生了一个线程对象,但并没有线程启动。
main方法第二行产生了一个线程对象,并启动了一个线程。
main方法第三行,产生并启动一个线程后,主线程自己也继续执行其它语句。
我们先不研究Thread对象的具体内容,稍微来回想一下上面的两个概念,线程对象和线程。
在JAVA中,线程对象是JVM产生的一个普通的Object子类。
而线程是CPU分配给这个对象的一个运行过程。
我们说的这个线程在干什么,不是说一个线程对象在干什么,而是这个运行过程在干什么。
如果一时想不明白,不要急,但你要记得它们不是一回事就行了。
累了吧?
为不么不继续了?
基于这种风格来介绍多线程,并不是每个人都喜欢和接受的,如果你不喜欢,正好不浪费你的时间了,而如果你接受的话,那就看下一节吧。
Java多线程编程基础之线程对象
在进入java平台的线程对象之前,基于基础篇
(一)的一些问题,我先插入两个基本概念。
[线程的并发与并行]
在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主),但无论如何,要通过不断切换需要运行的线程让其运行的方式就叫并发(concurrent)。
而在多CPU系统中,可以让两个以上的线程同时运行,这种可以同时让两个以上线程同时运行的方式叫做并行(parallel)。
在上面包括以后的所有论述中,请各位朋友谅解,我无法用最准确的词语来定义储如并发和并行这类术语,但我以我的经验能通俗地告诉大家它是怎么一回事,如果您看到我说的一些"标准"文档上说的不一样,只要意思一致,那您就不要挑刺了。
[JAVA线程对象]
现在我们来开始考察JAVA中线程对象。
在JAVA中,要开始一个线程,有两种方式。
一是直接调用Thread实例的start()方法,二是
将Runable实例传给一个Thread实例然后调用它的start()方法。
在前面已经说过,线程对象和线程是两个完全不同的概念。
这里我们再次深入一下,生成一个线程的实例,并不代表启动了线程。
而启动线程是说在某个线程对象上启动了该实例对应的线程,当该线程结束后,并不会就立即消失。
对于从很多书籍上可以看到的基础知识我就不用多说了。
既然是基础知识,我也着重于从普通文档上读不到的内容。
所以本节我重点要说的是两种线程对象产生线程方式的区别。
classMyThreadextendsThread{
publicintx=0;
publicvoidrun(){
for(inti=0;i<100;i++){
try{
Thread.sleep(10);
}catch(Exceptione){}
System.out.println(x++);
}
}
}
如果我们生成MyThread的一个实例,然后调用它的start()方法,那么就产生了这个实例对应的线程:
publicclassTest{
publicstaticvoidmain(String[]args)throwsException{
MyThreadmt=newMyThread();
mt.start();
}
}
不用说,最终会打印出0到99,现在我们稍微玩一点花样:
publicclassTest{
publicstaticvoidmain(String[]args)throwsException{
MyThreadmt=newMyThread();
mt.start();
System.out.println(101);
}
}
也不用说,在基础篇
(一)中我们知道由于单CPU的原因,一般会先打印101,然后打印0到99。
不过我们可以控制线程让它按我们的意思来运行:
publicclassTest{
publicstaticvoidmain(String[]args)throwsException{
MyThreadmt=newMyThread();
mt.start();
mt.join();
System.out.println(101);
}
}
好了,我们终于看到,mt实例对应的线程(假如我有时说mt线程请你不要怪我,不过我尽量不这么说)。
在运行完成后,主线程才打印101。
因为我们让当前线程(这里是主线程)等待mt线程的运行结束。
"在线程对象a上调用join()方法,就是让当前正在执行的线程等待线程对象a对应的线程运行完成后才继续运行。
"请大家一定要深刻理解并熟记这句话,而我这里引出这个知识点的目的是为了让你继续看下面的例子:
publicclassTest{
publicstaticvoidmain(String[]args)throwsException{
MyThreadmt=newMyThread();
mt.start();
mt.join();
Thread.sleep(3000);
mt.start();
}
}
当线程对象mt运行完成后,我们让主线程休息一下,然后我们再次在这个线程对象上启动线程。
结果我们看到:
Exceptioninthread"main"java.lang.IllegalThreadStateException
也就是这种线程对象一时运行一次完成后,它就再也不能运行第二次了。
我们可以看一下它有具体实现:
publicsynchronizedvoidstart(){
if(started)
thrownewIllegalThreadStateException();
started=true;
group.add(this);
start0();
}
一个Thread的实例一旦调用start()方法,这个实例的started标记就标记为true,事实中不管这个线程后来有没有执行到底,只要调用了一次start()就再也没有机会运行了,这意味着:
[通过Thread实例的start(),一个Thread的实例只能产生一个线程]
那么如果要在一个实例上产生多个线程(也就是我们常说的线程池),我们应该如何做呢?
这就是Runnable接口给我们带来的伟大的功能。
classRimplementsRunnable{
privateintx=0;
publicvoidrun(){
for(inti=0;i<100;i++){
try{
Thread.sleep(10);
}catch(Exceptione){}
System.out.println(x++);
}
}
}
正如它的名字一样,Runnable的实例是可运行的,但它自己并不能直接运行,它需要被Thread对象来包装才行运行:
publicclassTest{
publicstaticvoidmain(String[]args)throwsException{
newThread(newR()).start();
}
}
当然这个结果和mt.start()没有什么区别。
但如果我们把一个Runnable实例给Thread对象多次包装,我们就可以看到它们实际是在同一实例上启动线程:
publicclassTest{
publicstaticvoidmain(String[]args)throwsException{
Rr=newR();
for(inti=0;i<10;i++)
newThread(r).start();
}
}
x是实例对象,但结果是x被加到了999,说明这10个线程是在同一个r对象上运行的。
请大家注意,因为这个例子是在单CPU上运行的,所以没有对多个线程同时操作共同的对象进行同步。
这里是为了说明的方便而简化了同步,而真正的环境中你无法预知程序会在什么环境下运行,所以一定要考虑同步。
到这里我们做一个完整的例子来说明线程产生的方式不同而生成的线程的区别:
packagedebug;
importjava.io.*;
importjava.lang.Thread;
classMyThreadextendsThread{
publicintx=0;
publicvoidrun(){
System.out.println(++x);
}
}
classRimplementsRunnable{
privateintx=0;
publicvoidrun(){
System.out.println(++x);
}
}
publicclassTest{
publicstaticvoidmain(String[]args)throwsException{
for(inti=0;i<10;i++){
Threadt=newMyThread();
t.start();
}
Thread.sleep(10000);//让上面的线程运行完成
Rr=newR();
for(inti=0;i<10;i++){
Threadt=newThread(r);
t.start();
}
}
}
上面10个线程对象产生的10个线程运行时打印了10次1。
下面10个线程对象产生的10个线程运行时打印了1到10。
我们把下面的10个线程称为同一实例(Runnable实例)的多个线程。
下节我们将研究线程对象方法,还是那句话,一般文档中可以读到的内容我不会介绍太多
请大家自己了解。
Java多线程编程基础之非线程的方法
[wait(),notify()/notityAll()方法]
关于这两个方法,有很多的内容需要说明.在下面的说明中可能会有很多地方不能一下子明白,但在看完本节后,即使不能完全明白,你也一定要回过头来记住下面的两句话:
[wait(),notify()/notityAll()方法是普通对象的方法(Object超类中实现),而不是线程对象的方法]
[wait(),notify()/notityAll()方法只能在同步方法中调用]
[线程的互斥控制]
多个线程同时操作某一对象时,一个线程对该对象的操作可能会改变其状态,而该状态会影响另一线程对该对象的真正结果.
这个例子我们在太多的文档中可以看到,就象两个操售票员同时售出同一张票一样.
1.线程A在数据库中查询存票,发现票C可以卖出
2.线程A接受用户订票请求,准备出票.
3.这时切换到了线程B执行
4.线程B在数据库中查询存票,发现票C可以卖出
5.线程B将票卖了出去
6.切换到线程A执行,线程A卖了一张已经卖出的票
所以需要一种机制来管理这类问题的发生,当某个线程正在执行一个不可分割的部分时,其它线程不能不能同时执行这一部分.
象这种控制某一时刻只能有一个线程执行某个执行单元的机制就叫互斥控制或共享互斥(mutualexclusion)
在JAVA中,用synchornized关键字来实现互斥控制(暂时这样认为,JDK1.5已经发展了新的机制)
[synchornized关键字]
把一个单元声明为synchornized,就可以让在同一时间只有一个线程操作该方法.
有人说synchornized就是一把锁,事实上它确实存在锁,但是是谁的锁,锁谁,这是一个非常复杂的问题.
每个对象只有一把监视锁(monitorlock),一次只能被一个线程获取.当一个线程获取了这一个锁后,其它线程就只能等待这个线程释放锁才能再获取.
那么synchornized关键字到底锁什么?
得到了谁的锁?
对于同步块,synchornized获取的是参数中的对象锁:
synchornized(obj){
//...............
}
线程执行到这里时,首先要获取obj这个实例的锁,如果没有获取到线程只能等待.如果多个线程执行到这里,只能有一个线程获取obj的锁,然后执行{}中的语句,所以,obj对象的作用范围不同,控制程序不同.
假如:
publicvoidtest(){
Objecto=newObject();
synchornized(obj){
//...............
}
}
这段程序控制不了任何,多个线程之间执行到Objecto=newObject();时会各自产生一个对象然后获取这个对象有监视锁,各自皆大欢喜地执行.
而如果是类的属性:
classTest{
Objecto=newObject();
publicvoidtest(){
synchornized(o){
//...............
}
}
}
所有执行到Test实例的synchornized(o)的线程,只有一个线程可以获取到监视锁.
有时我们会这样:
publicvoidtest(){
synchornized(this){
//...............
}
}
那么所有执行Test实例的线程只能有一个线程执行.而synchornized(o)和synchornized(this)的范围是不同的,因为执行到Test实例的synchornized(o)的线程等待时,其它线程可以执行Test实例的synchornized(o1)部分,但多个线程同时只有一个可以执行Test实例的synchornized(this).]
而对于
synchornized(Test.class){
//...............
}
这样的同步块而言,所有调用Test多个实例的线程赐教只能有一个线程可以执行.
[synchornized方法]
如果一个方法声明为synchornized的,则等同于把在为个方法上调用synchornized(this).
如果一个静态方法被声明为synchornized,则等同于把在为个方法上调用synchornized(类.class).
现在进入wait方法和notify/notifyAll方法.这两个(或叫三个)方法都是Object对象的方法,而不是线程对象的方法.如同锁一样,它们是在线程中调用某一对象上执行的.
classTest{
publicsynchornizedvoidtest(){
//获取条件,intx要求大于100;
if(x<100)
wait();
}
}
这里为了说明方法没有加在try{}catch(){}中,如果没有明确在哪个对象上调用wait()方法,则为this.wait();
假如:
Testt=newTest();
现在有两个线程都执行到t.test();方法.其中线程A获取了t的对象锁,进入test()方法内.
这时x小于100,所以线程A进入等待.
当一个线程调用了wait方法后,这个线程就进入了这个对象的休息室(waitset),这是一个虚拟的对象,但JVM中一定存在这样的一个数据结构用来记录当前对象中有哪些程线程在等待.
当一个线程进入等待时,它就会释放锁,让其它线程来获取这个锁.
所以线程B有机会获得了线程A释放的锁,进入test()方法,如果这时x还是小于100,线程B也进入了t的休息室.
这两个线程只能等待其它线程调用notity[All]来唤醒.
但是如果调用的是有参数的wait(time)方法,则线程A,B都会在休息室中等待这个时间后自动唤醒.
[为什么真正的应用都是用while(条件)而不用if(条件)]
在实际的编程中我们看到大量的例子都是用?
while(x<100)
wait();go();而不是用if,为什么呢?
在多个线程同时执行时,if(x<100)是不安全的.因为如果线程A和线程B都在t的休息室中等待,这时另一个线程使x==100了,并调用notifyAll方法,线程A继续执行下面的go().而它执行完成后,x有可能又小于100,比如下面的程序中调用了--x,这时切换到线程B,线程B没有继续判断,直接执行go();就产生一个错误的条件,只有while才能保证线程B又继续检查一次。
[notify/notifyAll方法]
这两个方法都是把某个对象上休息区内的线程唤醒,notify只能唤醒一个
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Java 多线程 编程 基础
![提示](https://static.bdocx.com/images/bang_tan.gif)