Java 多线程编程

来源:互联网 发布:java中this与super 编辑:程序博客网 时间:2024/06/03 00:35

一、简介

1、什么是线程
Ly2A5p-}'w0

要说线程,就必须先说说进程,进程就是程序的运行时的一个实例。线程呢可以看作单独地占有CPU时间来执行相应的代码的。对早期的计算机(如DOS)而言,线程既是进程,进程既是进程,因为她是单线程的。当然一个程序可以是多线程的,多线程的各个线程看上去像是并行地独自完成各自的工作,就像一台一台计算机上运行着多个处理机一样。在多处理机计算机上实现多线程时,它们确实可以并行工作,而且采用适当的分时策略可以大大提高程序运行的效率。但是二者还是有较大的不同的,线程是共享地址空间的,也就是说多线程可以同时读取相同的地址空间,并且利用这个空间进行交换数据。

2、为什么要使用线程

  为什么要使用多线程呢?学过《计算机体系结构》的人都知道。将顺序执行程序和采用多线程并行执行程序相比,效率是可以大大地提高的。比如,有五个线程 thread1, thread2, thread3, thread4, thread5,所耗的CPU时间分别为4,5,1,2,7。(假设CPU轮换周期为4个CPU时间,而且线程之间是彼此独立的)顺序执行需要花费19个 CPU时间,而并行需要的时间肯定少于19个CPU时间,至于具体多少时间要看那些线程是可以同时执行的。这是在非常小规模的情况下,要是面对大规模的进程之间的交互的话,效率可以表现得更高。

3、java中是如何实现多线程的

  与其他语言不一样的是,线程的观念在java是语言中是重要的,根深蒂固的,因为在java语言中的线程系统是java语言自建的, java中有专门的支持多线程的API库,所以你可以以最快的速度写一个支持线程的程序。在使用java创建线程的时候,你可以生成一个Thread类或者他的子类对象,并给这个对象发送start()消息(程序可以向任何一个派生自 Runnable 接口的类对象发送 start() 消息的),这样一来程序会一直执行,直到run返回为止,此时该线程就死掉了。

在java语言中,线程有如下特点:

  § 在一个程序中而言,主线程的执行位置就是main。而其他线程执行的位置,程序员是可以自定义的。值得注意的是对Applet也是一样。3GEYE Yw9I.i ]Z-ZMZ
  § 每个线程执行其代码的方式都是一次顺序执行的。
5D#BlcRH{0  § 一个线程执行其代码是与其他线程独立开来的。如果诸线程之间又相互协作的话,就必须采用一定的交互机制。3GEYEj1^N1Ti"c^9c k
  § 前面已经说过,线程是共享地址空间的,如果控制不当,这里很有可能出现死锁。

  各线程之间是相互独立的,那么本地变量对一个线程而言就是完全独立,私有的。所以呢,线程执行时,每个线程都有各自的本地变量拷贝。对象变量 (instance variable)在线程之间是可以共享的,这也就是为什么在java中共享数据对象是如此的好用,但是java线程不能够武断地访问对象变量:他们是需要访问数据对象的权限的。

 
IB6D)G3O*b.Gs.uX @0

二、准备知识

  在分析这个例子之前,然我们先看看关于线程的几个概念,上锁,信号量,和java所提供的API。

上锁

  对于大多数的程序而言,他们都需要线程之间相互的通讯来完成整个线程的生命周期,二实现线程之间同步的最简单的办法就是上锁。为了防止相互关联的两个线程之间错误地访问共享资源,线程需要在访问资源的时候上锁和解锁,对于锁而言,有读锁,写锁和读写锁等不同的同步策略。在java中,所有的对象都有锁;线程只需要使用synchronized关键字就可以获得锁。在任一时刻对于给定的类的实例,方法或同步的代码块只能被一个线程执行。这是因为代码在执行之前要求获得对象的锁。

信号量

  通常情况下,多个线程所访问为数不多的资源,那怎么控制呢?一个比较非常经典而起非常简单的办法就是采用信号量机制。信号量机制的含义就是定义一个信号量,也就是说能够提供的连接数;当有一个线程占用了一个连接时,信号量就减一;当一个线程是放了连接时,信号量就加一。采用这种方法就可以简单有效地控制线程的同步问题,而且实现起来也特别方便。看下面的代码:

class Semaphore {3GEYE*W4G1W2|2h o"J.M
private int count;3GEYE[O7t^ ZI
public Semaphore(int count) {3GEYE7S Y O)}s_:^z
this.count = count;
E_"uuS"At0}
&d8slJ Yo0
6[R |].W)i0public synchronized void acquire() {3GEYE*a9I!^*_ @,s&vO1d
while(count == 0) {3GEYE:],m8|SZ^ G ?H
try {3GEYE3u1`/O-h{1T6{
wait();3GEYE4GF/w.E,`)e2D
} catch (InterruptedException e) {3GEYEJ+fMDi8GKx
//keep trying3GEYE}k,Sw;Rl]5r
}
!P3Dv*/4_:n-eUw0}
J0sG&pdj0count--;
};{^)aTMm_0}
%UR2r v.D03GEYE`gx+SDPn(w
public synchronized void release() {
:m)w.~ v(^X2QH'DKH0count++;
Th+D*oW!a)c0notify(); //alert a thread that´s blocking on this semaphore3GEYEP-MsE@6[w
}
2kp(BN3CpkK W%I'y0}

java中提供了哪些api以编写多线程程序

  这里只列出几个常用的方法和属性值。
@}{$L kw0  属性值,有三个MAX_PRIORITY,MIN_PRIORITY,NORM_PRIORITY

  方法:

Thread(); //建立一个线程
Y,aw8Qlz0void run(); //对于一个继承了Runnable接口的class而言,3GEYEy5se:s4c$Gu
//他运行一个线程,否着他什么都不做3GEYEX"XFX@)k9[
void setPriority(int newPriority); //设置优先级3GEYE.y%G|&/l#Q fH
void start(); //运行一个程序
5Aus4pp0void sleep(long millis); //线程睡眠millis毫秒
j+G1yR'kG)R:uPP0static void yield(); //临时pause一个程序以便起他线程运行

3GEYEnGe4{X%i

三、程序示例

  例一、

  让我们看看下面的例子。取钱的流程是输入密码,然后确定要取得金额,如果所取的金额小于或等于可以取出的金额,WITHDRAW则返回TRUE,然后ATM机出钱,然后打印清单;否则返回FALSE,然后打印清单。如下图:

public class AutomatedTellerMachine extends Teller {
j_`sQ8Ji"w0public void withdraw(float amount) {
q:~.ASJHE0Account a = getAccount();
1C2n$x N({:dO0if (a.deduct(amount))
2o:z1ewoA%f0dispense(amount);
-P%_ z F5C3w0printReceipt();
,h?jUGny;Z0}3GEYE8vVJ z%V1h1m
}3GEYEq#PB Hqm;a
3GEYEg/f T^mck-S
public class Account {3GEYELH$Z$J Qh#N Q4K]
private float total;3GEYE!z'qu$NoTq6q&Rs
public boolean deduct(float t) {3GEYEY pgW,M6V*k&X
if (t <= total) {
!HZY KI"t0total -= t;3GEYE&^v2b1~#V
return true;3GEYE.b&I4FIkw.r8] U
}3GEYEw V4k,uX*b
return false;
8s;A#_f~ s M0}3GEYE }-t*v9p$^ n[D
}

  就这个例子而言,假设有这种情况,对同一个账号可以在不同的地方取钱,在同一时间,不同地点,妻子和丈夫取钱,妻子输入了账号上的最大金额,丈夫也是一样,假如妻子输入后已经得到true的返回值,但是丈夫的线程所得到的值还没有更新,这样丈夫也能够得到true的返回值,这样就出现了问题!这个问题怎么解决呢?在java里面提供了控制机制以保证deduct操作时的原子性,那就是关键字synchronized。

  在Account的deduct方法加入synchronized就可以解决这个问题。

  例二、

  在这里我们用多线程中最典型的例子,生产者与消费者问题。在这个例子里面我们定义了生产者Producer,消费者Consumer和仓库Warehouse三个类,在整个程序的生命周期里,生产者随机地制造出产品放到仓库中,消费者也是随即地从仓库中取出产品。

import exception.ProducerConsumerException;3GEYEe7/Z3l6a#l2sx

7rV!t4k4u&Q/:S0/**3GEYE ZUfA&S%m y+o
* Consumer.java3GEYE5`'f PAub
* Consumer
j%gW'ZkW]0B T0* By: Jiabo
*BX$qp-w/Wc0* Date: Mar 21, 2004
E/^H;^6zF,y,sH|0* Time: 2:47:58 PM3GEYEz5W{Sh [ JE&U
*/3GEYEdT3q/T1ow'o2/
public class Consumer extends Thread {3GEYEd;w ^_$_wP)d
3GEYE2/U6BN"_s*Er` T1{
private Warehouse warehouse;3GEYE-Rb7pLxh m9p&?
private String id;3GEYE9I7r"C c4Sf v
3GEYEzC x2e"Ek
public Consumer(Warehouse warehouse, String id) {
y0e/ Z$~%z[0this.warehouse = warehouse;
'{p&`Fv7D Q0this.id = id;
5F4[ S~:W!?i0}3GEYE*X.^kv']SO
3GEYE8a M/dm
public void run() {3GEYE%uH&` Vd7w/Y!v(D
3GEYE7B9|;y|#jRa l
int tmp = (int) Math.random() * 10;
AF!l'r1EikHs03GEYEm8VlDdh7hB!C*j(a+D0G
try {
6x/YRA_5g0warehouse.get(tmp);3GEYEF z.Ed;O [9}5H
System.out.println("Consumer # " + this.id + " get " + tmp);
V"N0k1/i/pDq0} catch (ProducerConsumerException e) {
7Gp$r wf;IV0e.printStackTrace();
I2?,Z B:{)E~0}3GEYE]2oMA F-Ls
3GEYE9egm4}qT4f;G![
try {
8A g AkZKr3NH0sleep((int) (Math.random() * 100));3GEYE cJs.{Q(~ k
} catch (InterruptedException e) {
Jq^1T-Ea/E v&B;rQ0e.printStackTrace();3GEYE?.P @I P.d#b
}3GEYE^~3|-mr
}
ml(d2_4P `}4}0}

  在这个类中,值得注意的一点是run方法中必须使用try-catch,因为,消费者从仓库中取东西时有可能诸如仓库中的储量不够得异常,在消费者里面也是一样,只不过异常变为仓库已满。

import exception.*;3GEYE9?+jfJeE

/E / ~3t1A0/**
LW6Bfn0* Producer.java3GEYE6Z+[/UcFK/f
* Producer3GEYEf+H*J1U~ W m j
* By: Jiabo3GEYE_0t&t m+U.Rm!X
* Date: Mar 21, 20043GEYE@Cg$/0P{Z(/4d)O
* Time: 2:47:45 PM3GEYE8/*v^l/`;uSNh
*/
T`kpU h[0public class Producer extends Thread {
-jg.e*r"^6}wmja+I03GEYE[ U5Tq3Pl e|
private Warehouse warehouse;
gZ:z,l Fd&@0private String id;
p_Lf9k0
H}7])?C:I#^caf9Q0public Producer(Warehouse warehouse, String id) {
3H8L7fUt6VL0Z0this.warehouse = warehouse;
k)U L/'e-f0G8D0this.id = id;3GEYE-_NO7{Zt
}
6h%ff"JBA8s03GEYEC2/nPD
public void run() {3GEYE-oV&rcZi
3GEYE7[td'Dr/
int tmp = (int) Math.random() * 10;3GEYE{2t)B(En1^$G&K

l`qr#f+C0if (tmp != 0) {
:cG ["d:p&lV(o0try {
9n]*K#Z*c0warehouse.put(tmp);
;cS{ZjB h$U4w1B0System.out.println("Consumer # " + this.id + " put " + tmp);
v^Wf/n-GaZ K|0} catch (ProducerConsumerException e) {3GEYElJ%B_^-FyC
e.printStackTrace();3GEYEHFj2Y|,EP@
}
c"@ u-r1d3f`0}3GEYE9h9iz8A.i

+E'eQ z p(s^0try {
w2n/l E,B-oc0sleep((int) (Math.random() * 100));
%{b$qLh%|p*b0} catch (InterruptedException e) {3GEYEoq0aCzJ
e.printStackTrace();
)['w [*r1J'b0}
@._/ N2l!NoY0?Rd0}3GEYEfk#l5B]~d._
}

  最重要的一部分在Warehouse类,如上所说为了保证get何set的原子性,在这里使用了synchronized关键字,并且在操作时抛出了可能跑出的异常。

import exception.*;3GEYE|+Ph_}$M
3GEYE1ad.Jy^V+]n
/**
u `-d(s|ok?kuqq0* Warehouse3GEYET0QuTb/5ST0E ^2@
* By: Jiabo
o ]5c^'p)/0* Date: Mar 21, 2004
KV&RK9H uk0* Time: 2:48:10 PM
6JDoJ!o5W Q0*/3GEYEN)B L(h$w{;YU Ba-w
public class Warehouse {
!B;^q'rC ~Y(W({03GEYEP!X+/(v|t
// max capability of the warehouse3GEYEE)Ad$C @Fg-O
private int MAX;3GEYE7/2V1Byc!z6j4Um2P
private int contents;
x*`x+b/D-`0tR!?03GEYE5zC v5V z-_/gt1Lr
// init with max capacity3GEYE1{faqS#O'e Sl&L X
public Warehouse(int max) {3GEYET u_)~3Kut0T#y)zc
this.MAX = max;
4qeh~ f }X_#Qu"h#W0this.contents = 0;
#`I | EU0}
+eJRf~2G0
7zw^A1cg/:p3z+/0public synchronized void get(int amount) throws ProducerConsumerException {3GEYE S4m3e,V5?.g
3GEYEM!Y"S~_.N
// the amount you want to get is bigger than the contends that the warehouse stores
5[,q PHf,D N0if (amount > this.contents) {
S$aw-C{'W6Ou;n0throw new NotEnoughGoodsException();
"z6Bl5U4Bdp2]1}0}3GEYE)yS2p{};V!A`
3GEYEp.W:I F1uzZ;rW.]
amount -= contents;3GEYE$Sk/]/~-p{]5F
}
Y!U-g Xx^l{0
F&l X{4kd ?0public synchronized void put(int amount) throws ProducerConsumerException {3GEYE? L:H/&~2a/}J
3GEYEAxA6LV fT
// the amount you want to put is out of the capability of the warehouse3GEYE1M?ek cdi
if (amount > (this.MAX - this.contents)) {3GEYE#^H&lA K%a c(_9g
throw new WarehouseFullException();
6[!K cT#MP;kw m'q!}S0} else if (this.contents == 0) {
4e5] gf`2O} n+G0// warehouse is empty3GEYER+ZQJ-VhG
throw new WarehouseEmptyException();3GEYES:t!b.i,Z q
}
G$f$x;xU P"Z+F~2h0
3l#H?}JC.l0amount += contents;
(A ~o^b!Bx x0}3GEYEa1D&b'd_1C4F#E Ys
}

原创粉丝点击