http://www.cnblogs.com/rollenholt/archive/2011/08/28/2156357.html
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口。
对于直接继承Thread的类来说,代码大致框架是:
1
2
3
4
5
6
7
8
9
10
11
12
|
class
类名
extends
Thread{
方法
1
;
方法
2
;
…
public
void
run(){
// other code…
}
属性
1
;
属性
2
;
…
}
|
先看一个简单的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
/**
* @author Rollen-Holt 继承Thread类,直接调用run方法
* */
class
hello
extends
Thread {
public
hello() {
}
public
hello(String name) {
this
.name = name;
}
public
void
run() {
for
(
int
i =
0
; i <
5
; i++) {
System.out.println(name +
"运行 "
+ i);
}
}
public
static
void
main(String[] args) {
hello h1=
new
hello(
"A"
);
hello h2=
new
hello(
"B"
);
h1.run();
h2.run();
}
private
String name;
}
|
【运行结果】:
A运行 0
A运行 1
A运行 2
A运行 3
A运行 4
B运行 0
B运行 1
B运行 2
B运行 3
B运行 4
我们会发现这些都是顺序执行的,说明我们的调用方法不对,应该调用的是start()方法。
当我们把上面的主函数修改为如下所示的时候:
1
2
3
4
5
6
|
public
static
void
main(String[] args) {
hello h1=
new
hello(
"A"
);
hello h2=
new
hello(
"B"
);
h1.start();
h2.start();
}
|
然后运行程序,输出的可能的结果如下:
A运行 0
B运行 0
B运行 1
B运行 2
B运行 3
B运行 4
A运行 1
A运行 2
A运行 3
A运行 4
因为需要用到CPU的资源,所以每次的运行结果基本是都不一样的,呵呵。
注意:虽然我们在这里调用的是start()方法,但是实际上调用的还是run()方法的主体。
那么:为什么我们不能直接调用run()方法呢?
我的理解是:线程的运行需要本地操作系统的支持。
如果你查看start的源代码的时候,会发现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public
synchronized
void
start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if
(threadStatus !=
0
||
this
!= me)
throw
new
IllegalThreadStateException();
group.add(
this
);
start0();
if
(stopBeforeStart) {
stop0(throwableFromStop);
}
}
private
native
void
start0();
|
注意我用红色加粗的那一条语句,说明此处调用的是start0()。并且这个这个方法用了native关键字,次关键字表示调用本地操作系统的函数。因为多线程的实现需要本地操作系统的支持。
但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。
通过实现Runnable接口:
大致框架是:
1
2
3
4
5
6
7
8
9
10
11
12
|
class
类名
implements
Runnable{
方法
1
;
方法
2
;
…
public
void
run(){
// other code…
}
属性
1
;
属性
2
;
…
}
|
来先看一个小例子吧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/**
* @author Rollen-Holt 实现Runnable接口
* */
class
hello
implements
Runnable {
public
hello() {
}
public
hello(String name) {
this
.name = name;
}
public
void
run() {
for
(
int
i =
0
; i <
5
; i++) {
System.out.println(name +
"运行 "
+ i);
}
}
public
static
void
main(String[] args) {
hello h1=
new
hello(
"线程A"
);
Thread demo=
new
Thread(h1);
hello h2=
new
hello(
"线程B"
);
Thread demo1=
new
Thread(h2);
demo.start();
demo1.start();
}
private
String name;
}
|
【可能的运行结果】:
线程A运行 0
线程B运行 0
线程B运行 1
线程B运行 2
线程B运行 3
线程B运行 4
线程A运行 1
线程A运行 2
线程A运行 3
线程A运行 4
关于选择继承Thread还是实现Runnable接口?
其实Thread也是实现Runnable接口的:
1
2
3
4
5
6
7
8
|
class
Thread
implements
Runnable {
//…
public
void
run() {
if
(target !=
null
) {
target.run();
}
}
}
|
其实Thread中的run方法调用的是Runnable接口的run方法。不知道大家发现没有,Thread和Runnable都实现了run方法,这种操作模式其实就是代理模式。关于代理模式,我曾经写过一个小例子呵呵,大家有兴趣的话可以看一下:http://www.cnblogs.com/rollenholt/archive/2011/08/18/2144847.html
Thread和Runnable的区别:
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/**
* @author Rollen-Holt 继承Thread类,不能资源共享
* */
class
hello
extends
Thread {
public
void
run() {
for
(
int
i =
0
; i <
7
; i++) {
if
(count >
0
) {
System.out.println(
"count= "
+ count--);
}
}
}
public
static
void
main(String[] args) {
hello h1 =
new
hello();
hello h2 =
new
hello();
hello h3 =
new
hello();
h1.start();
h2.start();
h3.start();
}
private
int
count =
5
;
}
|
【运行结果】:
count= 5
count= 4
count= 3
count= 2
count= 1
count= 5
count= 4
count= 3
count= 2
count= 1
count= 5
count= 4
count= 3
count= 2
count= 1
大家可以想象,如果这个是一个买票系统的话,如果count表示的是车票的数量的话,说明并没有实现资源的共享。
我们换为Runnable接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
/**
* @author Rollen-Holt 继承Thread类,不能资源共享
* */
class
hello
implements
Runnable {
public
void
run() {
for
(
int
i =
0
; i <
7
; i++) {
if
(count >
0
) {
System.out.println(
"count= "
+ count--);
}
}
}
public
static
void
main(String[] args) {
hello he=
new
hello();
new
Thread(he).start();
}
private
int
count =
5
;
}
|
【运行结果】:
count= 5
count= 4
count= 3
count= 2
count= 1
总结一下吧:
实现Runnable接口比继承Thread类所具有的优势:
1):适合多个相同的程序代码的线程去处理同一个资源
2):可以避免java中的单继承的限制
3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。
所以,本人建议大家劲量实现接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/**
* @author Rollen-Holt
* 取得线程的名称
* */
class
hello
implements
Runnable {
public
void
run() {
for
(
int
i =
0
; i <
3
; i++) {
System.out.println(Thread.currentThread().getName());
}
}
public
static
void
main(String[] args) {
hello he =
new
hello();
new
Thread(he,
"A"
).start();
new
Thread(he,
"B"
).start();
new
Thread(he).start();
}
}
|
【运行结果】:
A
A
A
B
B
B
Thread-0
Thread-0
Thread-0
说明如果我们没有指定名字的话,系统自动提供名字。
提醒一下大家:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。
判断线程是否启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/**
* @author Rollen-Holt 判断线程是否启动
* */
class
hello
implements
Runnable {
public
void
run() {
for
(
int
i =
0
; i <
3
; i++) {
System.out.println(Thread.currentThread().getName());
}
}
public
static
void
main(String[] args) {
hello he =
new
hello();
Thread demo =
new
Thread(he);
System.out.println(
"线程启动之前---》"
+ demo.isAlive());
demo.start();
System.out.println(
"线程启动之后---》"
+ demo.isAlive());
}
}
|
【运行结果】
线程启动之前---》false
线程启动之后---》true
Thread-0
Thread-0
Thread-0
主线程也有可能在子线程结束之前结束。并且子线程不受影响,不会因为主线程的结束而结束。
线程的强制执行:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
/**
* @author Rollen-Holt 线程的强制执行
* */
class
hello
implements
Runnable {
public
void
run() {
for
(
int
i =
0
; i <
3
; i++) {
System.out.println(Thread.currentThread().getName());
}
}
public
static
void
main(String[] args) {
hello he =
new
hello();
Thread demo =
new
Thread(he,
"线程"
);
demo.start();
for
(
int
i=
0
;i<
50
;++i){
if
(i>
10
){
try
{
demo.join();
//强制执行demo
}
catch
(Exception e) {
e.printStackTrace();
}
}
System.out.println(
"main 线程执行-->"
+i);
}
}
}
|
【运行的结果】:
main线程执行-->0
main线程执行-->1
main线程执行-->2
main线程执行-->3
main线程执行-->4
main线程执行-->5
main线程执行-->6
main线程执行-->7
main线程执行-->8
main线程执行-->9
main线程执行-->10
线程
线程
线程
main线程执行-->11
main线程执行-->12
main线程执行-->13
...
线程的休眠:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/**
* @author Rollen-Holt 线程的休眠
* */
class
hello
implements
Runnable {
public
void
run() {
for
(
int
i =
0
; i <
3
; i++) {
try
{
Thread.sleep(
2000
);
}
catch
(Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + i);
}
}
public
static
void
main(String[] args) {
hello he =
new
hello();
Thread demo =
new
Thread(he,
"线程"
);
demo.start();
}
}
|
【运行结果】:(结果每隔2s输出一个)
线程0
线程1
线程2
线程的中断:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
/**
* @author Rollen-Holt 线程的中断
* */
class
hello
implements
Runnable {
public
void
run() {
System.out.println(
"执行run方法"
);
try
{
Thread.sleep(
10000
);
System.out.println(
"线程完成休眠"
);
}
catch
(Exception e) {
System.out.println(
"休眠被打断"
);
return
;
//返回到程序的调用处
}
System.out.println(
"线程正常终止"
);
}
public
static
void
main(String[] args) {
hello he =
new
hello();
Thread demo =
new
Thread(he,
"线程"
);
demo.start();
try
{
Thread.sleep(
2000
);
}
catch
(Exception e) {
e.printStackTrace();
}
demo.interrupt();
//2s后中断线程
}
}
|
【运行结果】:
执行run方法
休眠被打断
在java程序中,只要前台有一个线程在运行,整个java程序进程不会小时,所以此时可以设置一个后台线程,这样即使java进程小时了,此后台线程依然能够继续运行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
/**
* @author Rollen-Holt 后台线程
* */
class
hello
implements
Runnable {
public
void
run() {
while
(
true
) {
System.out.println(Thread.currentThread().getName() +
"在运行"
);
}
}
public
static
void
main(String[] args) {
hello he =
new
hello();
Thread demo =
new
Thread(he,
"线程"
);
demo.setDaemon(
true
);
demo.start();
}
}
|
虽然有一个死循环,但是程序还是可以执行完的。因为在死循环中的线程操作已经设置为后台运行了。
线程的优先级:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
/**
* @author Rollen-Holt 线程的优先级
* */
class
hello
implements
Runnable {
public
void
run() {
for
(
int
i=
0
;i<
5
;++i){
System.out.println(Thread.currentThread().getName()+
"运行"
+i);
}
}
public
static
void
main(String[] args) {
Thread h1=
new
Thread(
new
hello(),
"A"
);
Thread h2=
new
Thread(
new
hello(),
"B"
);
Thread h3=
new
Thread(
new
hello(),
"C"
);
h1.setPriority(
8
);
h2.setPriority(
2
);
h3.setPriority(
6
);
h1.start();
h2.start();
h3.start();
}
}
|
【运行结果】:
A运行0
A运行1
A运行2
A运行3
A运行4
B运行0
C运行0
C运行1
C运行2
C运行3
C运行4
B运行1
B运行2
B运行3
B运行4
。但是请读者不要误以为优先级越高就先执行。谁先执行还是取决于谁先去的CPU的资源、
另外,主线程的优先级是5.
线程的礼让。
在线程操作中,也可以使用yield()方法,将一个线程的操作暂时交给其他线程执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
/**
* @author Rollen-Holt 线程的优先级
* */
class
hello
implements
Runnable {
public
void
run() {
for
(
int
i=
0
;i<
5
;++i){
System.out.println(Thread.currentThread().getName()+
"运行"
+i);
if
(i==
3
){
System.out.println(
"线程的礼让"
);
Thread.currentThread().yield();
}
}
}
public
static
void
main(String[] args) {
Thread h1=
new
Thread(
new
hello(),
"A"
);
Thread h2=
new
Thread(
new
hello(),
"B"
);
h1.start();
h2.start();
}
}
|
A运行0
A运行1
A运行2
A运行3
线程的礼让
A运行4
B运行0
B运行1
B运行2
B运行3
线程的礼让
B运行4
同步和死锁:
【问题引出】:比如说对于买票系统,有下面的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
/**
* @author Rollen-Holt
* */
class
hello
implements
Runnable {
public
void
run() {
for
(
int
i=
0
;i<
10
;++i){
if
(count>
0
){
try
{
Thread.sleep(
1000
);
}
catch
(InterruptedException e){
e.printStackTrace();
}
System.out.println(count--);
}
}
}
public
static
void
main(String[] args) {
hello he=
new
hello();
Thread h1=
new
Thread(he);
Thread h2=
new
Thread(he);
Thread h3=
new
Thread(he);
h1.start();
h2.start();
h3.start();
}
private
int
count=
5
;
}
|
【运行结果】:
5
4
3
2
1
0
-1
这里出现了-1,显然这个是错的。,应该票数不能为负值。
如果想解决这种问题,就需要使用同步。所谓同步就是在统一时间段中只有有一个线程运行,
其他的线程必须等到这个线程结束之后才能继续执行。
【使用线程同步解决问题】
采用同步的话,可以使用同步代码块和同步方法两种来完成。
【同步代码块】:
语法格式:
synchronized(同步对象){
//需要同步的代码
}
但是一般都把当前对象this作为同步对象。
比如对于上面的买票的问题,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
/**
* @author Rollen-Holt
* */
class
hello
implements
Runnable {
public
void
run() {
for
(
int
i=
0
;i<
10
;++i){
synchronized
(
this
) {
if
(count>
0
){
try
{
Thread.sleep(
1000
);
}
catch
(InterruptedException e){
e.printStackTrace();
}
System.out.println(count--);
}
}
}
}
public
static
void
main(String[] args) {
hello he=
new
hello();
Thread h1=
new
Thread(he);
Thread h2=
new
Thread(he);
Thread h3=
new
Thread(he);
h1.start();
h2.start();
h3.start();
}
private
int
count=
5
;
}
|
【运行结果】:(每一秒输出一个结果)
5
4
3
2
1
【同步方法】
也可以采用同步方法。
语法格式为synchronized方法返回类型方法名(参数列表){
//其他代码
}
现在,我们采用同步方法解决上面的问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
/**
* @author Rollen-Holt
* */
class
hello
implements
Runnable {
public
void
run() {
for
(
int
i =
0
; i <
10
; ++i) {
sale();
}
}
public
synchronized
void
sale() {
if
(count >
0
) {
try
{
Thread.sleep(
1000
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
System.out.println(count--);
}
}
public
static
void
main(String[] args) {
hello he =
new
hello();
Thread h1 =
new
Thread(he);
Thread h2 =
new
Thread(he);
Thread h3 =
new
Thread(he);
h1.start();
h2.start();
h3.start();
}
private
int
count =
5
;
}
|
【运行结果】(每秒输出一个)
5
4
3
2
1
提醒一下,当多个线程共享一个资源的时候需要进行同步,但是过多的同步可能导致死锁。
此处列举经典的生产者和消费者问题。
【生产者和消费者问题】
先看一段有问题的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
class
Info {
public
String getName() {
return
name;
}
public
void
setName(String name) {
this
.name = name;
}
public
int
getAge() {
return
age;
}
public
void
setAge(
int
age) {
this
.age = age;
}
private
String name =
"Rollen"
;
private
int
age =
20
;
}
/**
* 生产者
* */
class
Producer
implements
Runnable{
private
Info info=
null
;
Producer(Info info){
this
.info=info;
}
public
void
run(){
boolean
flag=
false
;
for
(
int
i=
0
;i<
25
;++i){
if
(flag){
this
.info.setName(
"Rollen"
);
try
{
Thread.sleep(
100
);
}
catch
(Exception e) {
e.printStackTrace();
}
this
.info.setAge(
20
);
flag=
false
;
}
else
{
this
.info.setName(
"chunGe"
);
try
{
Thread.sleep(
100
);
}
catch
(Exception e) {
e.printStackTrace();
}
this
.info.setAge(
100
);
flag=
true
;
}
}
}
}
/**
* 消费者类
* */
class
Consumer
implements
Runnable{
private
Info info=
null
;
public
Consumer(Info info){
this
.info=info;
}
public
void
run(){
for
(
int
i=
0
;i<
25
;++i){
try
{
Thread.sleep(
100
);
}
catch
(Exception e) {
e.printStackTrace();
}
System.out.println(
this
.info.getName()+
"<---->"
+
this
.info.getAge());
}
}
}
/**
* 测试类
* */
class
hello{
public
static
void
main(String[] args) {
Info info=
new
Info();
Producer pro=
new
Producer(info);
Consumer con=
new
Consumer(info);
new
Thread(pro).start();
new
Thread(con).start();
}
}
|
【运行结果】:
Rollen<---->100
chunGe<---->20
chunGe<---->100
Rollen<---->100
chunGe<---->20
Rollen<---->100
Rollen<---->100
Rollen<---->100
chunGe<---->20
chunGe<---->20
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
Rollen<---->100
chunGe<---->20
大家可以从结果中看到,名字和年龄并没有对于。
那么如何解决呢?
<!--[if !supportLists]-->1)<!--[endif]-->加入同步
<!--[if !supportLists]-->2)<!--[endif]-->加入等待和唤醒
先来看看加入同步会是如何。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
|
class
Info {
public
String getName() {
return
name;
}
public
void
setName(String name) {
this
.name = name;
}
public
int
getAge() {
return
age;
}
public
void
setAge(
int
age) {
this
.age = age;
}
public
synchronized
void
set(String name,
int
age){
this
.name=name;
try
{
Thread.sleep(
100
);
}
catch
(Exception e) {
e.printStackTrace();
}
this
.age=age;
}
public
synchronized
void
get(){
try
{
Thread.sleep(
100
);
}
catch
(Exception e) {
e.printStackTrace();
}
System.out.println(
this
.getName()+
"<===>"
+
this
.getAge());
}
private
String name =
"Rollen"
;
private
int
age =
20
;
}
/**
* 生产者
* */
class
Producer
implements
Runnable {
private
Info info =
null
;
Producer(Info info) {
this
.info = info;
}
public
void
run() {
boolean
flag =
false
;
for
(
int
i =
0
; i <
25
; ++i) {
if
(flag) {
this
.info.set(
"Rollen"
,
20
);
flag =
false
;
}
else
{
this
.info.set(
"ChunGe"
,
100
);
flag =
true
;
}
}
}
}
/**
* 消费者类
* */
class
Consumer
implements
Runnable {
private
Info info =
null
;
public
Consumer(Info info) {
this
.info = info;
}
public
void
run() {
for
(
int
i =
0
; i <
25
; ++i) {
try
{
Thread.sleep(
100
);
}
catch
(Exception e) {
e.printStackTrace();
}
this
.info.get();
}
}
}
/**
* 测试类
* */
class
hello {
public
static
void
main(String[] args) {
Info info =
new
Info();
Producer pro =
new
Producer(info);
Consumer con =
new
Consumer(info);
new
Thread(pro).start();
new
Thread(con).start();
}
}
|
【运行结果】:
Rollen<===>20
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
Rollen<===>20
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
ChunGe<===>100
从运行结果来看,错乱的问题解决了,现在是Rollen对应20,ChunGe对于100
,但是还是出现了重复读取的问题,也肯定有重复覆盖的问题。如果想解决这个问题,就需要使用Object类帮忙了、
,我们可以使用其中的等待和唤醒操作。
要完成上面的功能,我们只需要修改Info类饥渴,在其中加上标志位,并且通过判断标志位完成等待和唤醒的操作,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
class
Info {
public
String getName() {
return
name;
}
public
void
setName(String name) {
this
.name = name;
}
public
int
getAge() {
return
age;
}
public
void
setAge(
int
age) {
this
.age = age;
}
public
synchronized
void
set(String name,
int
age){
if
(!flag){
try
{
super
.wait();
}
catch
(Exception e) {
e.printStackTrace();
}
}
this
.name=name;
try
{
Thread.sleep(
100
);
}
catch
(Exception e) {
e.printStackTrace();
}
this
.age=age;
flag=
false
;
super
.notify();
}
public
synchronized
void
get(){
if
(flag){
try
{
super
.wait();
}
catch
(Exception e) {
e.printStackTrace();
}
}
try
{
Thread.sleep(
100
);
}
catch
(Exception e) {
e.printStackTrace();
}
System.out.println(
this
.getName()+
"<===>"
+
this
.getAge());
flag=
true
;
super
.notify();
}
private
String name =
"Rollen"
;
private
int
age =
20
;
private
boolean
flag=
false
;
}
/**
* 生产者
* */
class
Producer
implements
Runnable {
private
Info info =
null
;
Producer(Info info) {
this
.info = info;
}
public
void
run() {
boolean
flag =
false
;
for
(
int
i =
0
; i <
25
; ++i) {
if
(flag) {
this
.info.set(
"Rollen"
,
20
);
flag =
false
;
}
else
{
this
.info.set(
"ChunGe"
,
100
);
flag =
true
;
}
}
}
}
/**
* 消费者类
* */
class
Consumer
implements
Runnable {
private
Info info =
null
;
public
Consumer(Info info) {
this
.info = info;
}
public
void
run() {
for
(
int
i =
0
; i <
25
; ++i) {
try
{
Thread.sleep(
100
);
}
catch
(Exception e) {
e.printStackTrace();
}
this
.info.get();
}
}
}
/**
* 测试类
* */
class
hello {
public
static
void
main(String[] args) {
Info info =
new
Info();
Producer pro =
new
Producer(info);
Consumer con =
new
Consumer(info);
new
Thread(pro).start();
new
Thread(con).start();
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
【程序运行结果】:
Rollen<===>
20
ChunGe<===>
100
Rollen<===>
20
ChunGe<===>
100
Rollen<===>
20
ChunGe<===>
100
Rollen<===>
20
ChunGe<===>
100
Rollen<===>
20
ChunGe<===>
100
Rollen<===>
20
ChunGe<===>
100
Rollen<===>
20
ChunGe<===>
100
Rollen<===>
20
ChunGe<===>
100
Rollen<===>
20
ChunGe<===>
100
Rollen<===>
20
ChunGe<===>
100
Rollen<===>
20
ChunGe<===>
100
Rollen<===>
20
ChunGe<===>
100
Rollen<===>
20
先在看结果就可以知道,之前的问题完全解决。
|
《完》
http://www.cnblogs.com/rollenholt/archive/2011/09/15/2178030.html
之前也总结了一篇文章《java多线程总结》,地址:http://www.cnblogs.com/rollenholt/archive/2011/08/28/2156357.html
这个就叫做第二篇吧,呵呵。
线程一般有6个状态:
新建状态:NEW
可运行状态:RUNNABLE
休眠状态:TIMED_WAITING
等待状态:WAITING
阻塞状态:BLOCKED
终止状态“TERMINATED
当我们使用new创建线程之后,线程处于新建状态,当调用start方法之后,线程出于可运行状态,当线程需要获得对象的内置锁,而这个锁被其他线程所占用的时候,线程就出于阻塞状态,当线程等待其他线程通知调度表可以运行时,线程处于等待状态,当一个含有时间参数的方法,必须sleep()方法,可以让线程处于计时等待状态,当run()方法运行完毕或者出现异常,线程处于终止状态。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
package
Thread;
public
class
ThreadStateDemo{
public
static
void
main(String[] args)
throws
Exception{
ThreadState state =
new
ThreadState();
Thread demo =
new
Thread(state);
System.out.println(
"新建状态:"
+ demo.getState());
demo.start();
System.out.println(
"可运行状态:"
+ demo.getState());
Thread.sleep(
100
);
System.out.println(
"休眠状态:"
+ demo.getState());
Thread.sleep(
1000
);
System.out.println(
"等待状态:"
+ demo.getState());
state.notifyWait();
System.out.println(
"阻塞状态:"
+ demo.getState());
Thread.sleep(
1000
);
System.out.println(
"终止状态“"
+ demo.getState());
}
}
class
ThreadState
implements
Runnable{
@Override
public
void
run(){
try
{
waitForASecond();
waitForAYear();
}
catch
(Exception e){
e.printStackTrace();
}
}
// 当前线程等待1秒
public
synchronized
void
waitForASecond()
throws
Exception{
wait(
1000
);
}
// 当前线程一直等待
public
synchronized
void
waitForAYear()
throws
Exception{
wait();
}
// 唤醒线程
public
synchronized
void
notifyWait()
throws
Exception{
notify();
}
}
|
【运行结果】:
新建状态:NEW
可运行状态:RUNNABLE
休眠状态:TIMED_WAITING
等待状态:WAITING
阻塞状态:BLOCKED
终止状态“TERMINATED
线程组表示一个线程线程的集合,线程组中也可以包含其他的线程组。线程组构成一棵树。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
package
Thread;
import
java.util.ArrayList;
import
java.util.List;
public
class
ThreadGroupDemo{
public
static
void
main(String[] args){
for
(String str : getThreadGroups(GetRootThreadGroups())){
System.out.println(str);
}
}
// 获得根线程组
private
static
ThreadGroup GetRootThreadGroups(){
// 获得当前的线程组
ThreadGroup rootGroup = Thread.currentThread().getThreadGroup();
while
(
true
){
if
(rootGroup.getParent() !=
null
){
rootGroup = rootGroup.getParent();
}
else
{
break
;
}
}
return
rootGroup;
}
// 获得给定线程组中所有线程名
public
static
List<String> getThreads(ThreadGroup group){
List<String> threadList =
new
ArrayList<String>();
Thread[] threads =
new
Thread[group.activeCount()];
int
count = group.enumerate(threads,
false
);
for
(
int
i =
0
; i < count; i++){
threadList.add(group.getName() +
" 线程组 "
+ threads[i].getName());
}
return
threadList;
}
// 获得线程组中所有子线程组
public
static
List<String> getThreadGroups(ThreadGroup group){
List<String> threadList = getThreads(group);
ThreadGroup[] groups =
new
ThreadGroup[group.activeGroupCount()];
int
count = group.enumerate(groups,
false
);
for
(
int
i =
0
; i < count; i++){
threadList.addAll(getThreads(groups[i]));
}
return
threadList;
}
}
|
【运行结果】:
system线程组 Reference Handler
system线程组 Finalizer
system线程组 Signal Dispatcher
system线程组 Attach Listener
main线程组 main
java中的线程分为2类,用户线程和守护线程,守护线程主要为其他线程提供服务,守护线程会随时被中断,所以一般不要再守护线程中使用需要释放资源的资源,比如输入输出流等,守护线程一般都是后台线程,如果虚拟机只剩下守护线程,虚拟机就会退出。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
package
Thread;
public
class
DaemonThreadTest{
public
static
void
main(String[] args){
Thread worker =
new
Thread(
new
Worker());
Thread timer =
new
Thread(
new
Timer());
//设置守护线程
timer.setDaemon(
true
);
worker.start();
timer.start();
}
}
class
Worker
implements
Runnable{
@Override
public
void
run(){
for
(
int
i =
0
; i <
5
; ++i){
System.out.println(
"rollen真帅! 第"
+ i +
"次"
);
}
}
}
class
Timer
implements
Runnable{
@Override
public
void
run(){
long
currentTime = System.currentTimeMillis();
long
processTime =
0
;
while
(
true
){
if
((System.currentTimeMillis() - currentTime) > processTime){
processTime = System.currentTimeMillis() - currentTime;
System.out.println(
"程序运行时间:"
+ processTime);
}
}
}
}
|
rollen真帅!第0次
程序运行时间:1
程序运行时间:2
程序运行时间:3
程序运行时间:4
程序运行时间:5
rollen真帅!第1次
rollen真帅!第2次
rollen真帅!第3次
rollen真帅!第4次
程序运行时间:6
虽然在Thread类中提供了stop()方法可以终止线程,但是由于其固有的不安全性,所以一般不要采用,本例子只是起到抛砖引玉的作用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
package
Thread;
import
java.awt.FlowLayout;
import
java.awt.event.ActionEvent;
import
java.awt.event.ActionListener;
import
javax.swing.JButton;
import
javax.swing.JFrame;
import
javax.swing.JLabel;
import
javax.swing.JPanel;
public
class
ThreadStopDemo
extends
JFrame{
public
ThreadStopDemo(){
panel.setLayout(
new
FlowLayout(FlowLayout.CENTER));
panel.add(label);
panel.add(startButton);
panel.add(endButton);
setContentPane(panel);
setSize(
200
,
300
);
setVisible(
true
);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
startButton.addActionListener(
new
ActionListener(){
@Override
public
void
actionPerformed(ActionEvent e){
counter =
new
CounterThread();
new
Thread(counter).start();
}
});
endButton.addActionListener(
new
ActionListener(){
@Override
public
void
actionPerformed(ActionEvent e){
if
(counter ==
null
){
return
;
}
counter.setStop(
false
);
}
});
}
public
static
void
main(String[] args){
new
ThreadStopDemo();
}
class
CounterThread
implements
Runnable{
@Override
public
void
run(){
while
(
this
.flag){
try
{
Thread.sleep(
500
);
}
catch
(Exception e){
e.printStackTrace();
}
label.setText(
"更新"
+ (count++) +
"更新"
);
}
}
public
void
setStop(
boolean
flag){
this
.flag = flag;
}
private
int
count =
0
;
private
boolean
flag =
true
;
}
private
CounterThread counter =
null
;
private
final
JPanel panel =
new
JPanel();
private
final
JLabel label =
new
JLabel(
"更新0次"
);
private
final
JButton startButton =
new
JButton(
"开始"
);
private
final
JButton endButton =
new
JButton(
"结束"
);
}
|
【运行结果】:
在编写多线程的程序的时候,经常会遇到让一个线程优先于另外i个线程运行的情况,此时,除了设置这个线程的优先级高(不推荐这种方法)之外,更加直接的办法是采用Thread类中的join()方法。当插队的线程运行结束之后,其他的线程才能运行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
package
Thread;
public
class
ThreadJoinDemo{
public
static
void
main(String[] args){
Thread demo1 =
new
Thread(
new
EmergencyThread());
demo1.start();
for
(
int
i =
0
; i <
5
; ++i){
try
{
Thread.sleep(
100
);
}
catch
(InterruptedException e){
e.printStackTrace();
}
System.out.println(
"正常情况 :"
+ i +
"号车开始出发"
);
try
{
//开始插队
demo1.join();
}
catch
(InterruptedException e){
e.printStackTrace();
}
}
}
}
class
EmergencyThread
implements
Runnable{
@Override
public
void
run(){
for
(
int
i =
0
; i <
5
; ++i){
try
{
Thread.sleep(
100
);
}
catch
(InterruptedException e){
e.printStackTrace();
}
System.out.println(
"紧急情况 :"
+ i +
"号车开始出发"
);
}
}
}
|
【运行结果】:
正常情况 :0号车开始出发
紧急情况:0号车开始出发
紧急情况:1号车开始出发
紧急情况:2号车开始出发
紧急情况:3号车开始出发
紧急情况:4号车开始出发
正常情况 :1号车开始出发
正常情况 :2号车开始出发
正常情况 :3号车开始出发
正常情况 :4号车开始出发
如果我们去掉join哪一行的话,运行结果:(结果不唯一)
紧急情况:0号车开始出发
正常情况 :0号车开始出发
紧急情况:1号车开始出发
正常情况 :1号车开始出发
正常情况 :2号车开始出发
紧急情况:2号车开始出发
紧急情况:3号车开始出发
正常情况 :3号车开始出发
正常情况 :4号车开始出发
紧急情况:4号车开始出发
多线程编程的一个重要原因是实现数据的共享,但是如果两个线程同时修改一个数据的话,则会产生同步问题。
下面采用一个2个人同时往银行存钱的例子,银行卡初始金额为100元。每次存10元,大家仔细查看余额。(对于简单的多线程,出错的概率很小,今天很不巧,我一向地下的RP今天居然爆发了,实验了很多次,都没错,最后终于出现了)
先看一下结果吧:
案例说两侧不能出现一样余额的。
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
package
Thread;
import
java.awt.GridLayout;
import
java.awt.event.ActionEvent;
import
java.awt.event.ActionListener;
import
javax.swing.JButton;
import
javax.swing.JFrame;
import
javax.swing.JLabel;
import
javax.swing.JPanel;
import
javax.swing.JScrollPane;
import
javax.swing.JTextArea;
public
class
UnSynBank
extends
JFrame{
public
UnSynBank(){
panel.setLayout(
new
GridLayout(
2
,
2
,
3
,
3
));
panel.add(label1);
panel.add(label2);
JScrollPane js1 =
new
JScrollPane(oneArea,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
panel.add(js1);
JScrollPane js2 =
new
JScrollPane(twoArea,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
panel.add(js2);
panel2.add(panel);
panel2.add(statrButton);
setContentPane(panel2);
statrButton.addActionListener(
new
ActionListener(){
@Override
public
void
actionPerformed(ActionEvent e){
Thread demo1 =
new
Thread(
new
Transfer(bank, oneArea));
demo1.start();
Thread demo2 =
new
Thread(
new
Transfer(bank, twoArea));
demo2.start();
}
});
setSize(
300
,
400
);
setVisible(
true
);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public
static
void
main(String[] args){
new
UnSynBank();
}
private
final
Bank bank =
new
Bank();
JPanel panel =
new
JPanel();
JPanel panel2 =
new
JPanel();
private
final
JButton statrButton =
new
JButton(
"开始存钱"
);
private
final
JLabel label1 =
new
JLabel(
"一号线程"
);
private
final
JLabel label2 =
new
JLabel(
"二号线程"
);
private
final
JTextArea oneArea =
new
JTextArea(
5
,
10
);
private
final
JTextArea twoArea =
new
JTextArea(
5
,
10
);
}
/**
* 表示银行的类
* */
class
Bank{
public
Bank(){
}
public
void
deposit(
int
money){
account += money;
}
public
int
getAccount(){
return
account;
}
private
int
account =
100
;
}
/**
* 表示往账户存钱
* */
class
Transfer
implements
Runnable{
public
Transfer(){
}
public
Transfer(Bank bank, JTextArea textArea){
this
.bank = bank;
this
.textArea = textArea;
}
@Override
public
void
run(){
for
(
int
i =
0
; i <
20
; ++i){
bank.deposit(
10
);
String str = textArea.getText();
textArea.setText(str +
"账户余额为:"
+ bank.getAccount() +
"\n"
);
}
}
private
Bank bank =
null
;
private
JTextArea textArea =
null
;
}
|
因为一个进程中的所有线程会共享进程中的资源,所以当一个线程还没有将修改之后的结果保存的时候,另外一个线程却进行读取,这样自然会产生错误,所以这个时候就需要我们采用同步来解决问题的。
所谓同步方法,就是用synchronized修饰的方法,之所以这十几个字母能解决困难的同步问题,这是和java中的内置锁密切相关的,每一个java对象中有一个内置锁,如果方法使用了synchronized进行修饰的话,内置锁会保护整个方法,也就是在调用方法之前,需要、获得内置锁,否则就会处于阻塞状态,当然这个关键字也可以修饰静态方法,如果调用静态方法,就会锁住整个类
现在我们来看看例子,我们显著需要修改Bank类中的deposit方法,修改为:
1
2
3
|
public
synchronized
void
deposit(
int
money){
account += money;
}
|
然后无论你的RP再好,在怎么运行,也不会出现两个文本域中出现一样的问题、另外读者可以思考一样,为什么需要锁住这个方法呢?其实:account += money;的执行是分3步运行的,先读取account的值,然后计算account和money的和,最后在存入account中,在多线程中,有可能两个线程同时读取account的值,这样就会少计算一次money的值。
至于修改之后的运行结果,我就不粘贴了。
但是要提醒一下大家,同步是一种高开销的操作,所以应该尽量减少需要同步的内容
上面的例子中采用synchronized这个关键字同步了那个方法,其实我们会发现,就本例子中,之所以出现同步问题的原因在于对于域account的读取上,那么我们就可以将account设置为特殊域变量。使用关键字volatile
volatile提供了一种免锁的机制,使用这个关键字修饰的域相当于告诉虚拟机,这个域可能会被其他的线程跟新,因此每次读取这个域的时候都需要重新计算,而不是使用寄存器中的值,这个关键字不会提供任何的原子操作,也不能用来修饰final类型的变量、
现在我们修改Bank方法:
privatevolatileintaccount = 100;
我们只需要加一个关键字就行了。
提醒一下:关于安全域的并发访问:
多线程中的非同步问题出现在对于域的读写上的时候,如果让域自身避免这个问题的话,则不需要修改操作的方法,在java中有3中域自身就可以避免非同步问题:final域,使用volatile域,以及有锁保护的域。
1
2
|
import
java.util.concurrent.locks.Lock;
import
java.util.concurrent.locks.ReentrantLock;
|
可以在程序中添加上面两行代码,。然后将Bank修改为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
class
Bank{
public
Bank(){
}
public
void
deposit(
int
money){
lock.lock();
try
{
account += money;
}
finally
{
lock.unlock();
}
}
public
int
getAccount(){
return
account;
}
private
final
Lock lock =
new
ReentrantLock();
private
int
account =
100
;
}
|
这样也可以解决非同步问题。至于这个类,大家可以自行去查看API,我只是在这里提醒一下,如果synchronized能够满足需求的话,就使用synchronized关键字,因为这个可以简化代码,如果需要更加高级的功能的时候,就使用Lock对象,在使用ReentrantLock的时候,一定要注意及时释放锁,否则程序会出现死锁。
这个例子演示的是两个线程同时修改一个变量,运行结果:
可以发现,每个线程完成修改之后的副本是完全独立的,如果使用TreadLocal来管理变量,则每个使用这个变量的线程都会获得这个变量的一个副本。,并且可以随意修改这个副本,每个线程之间不会影响。
TreadLocal和同步机制都是为了解决多线程中的相同变量访问冲突的问题的,前者采用的是空间还时间,后者采用的是时间换空间
代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class
Bank{
public
Bank(){
}
public
void
deposit(
int
money){
account.set(account.get() + money);
}
public
int
getAccount(){
return
account.get();
}
private
static
ThreadLocal<Integer> account =
new
ThreadLocal<Integer>(){
@Override
protected
Integer initialValue(){
return
100
;
}
};
}
|
还记得我在我的笔记java IO总结中给出了一个使用管道流进行线程之间通信的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
package
Thread;
import
java.io.IOException;
import
java.io.PipedInputStream;
import
java.io.PipedOutputStream;
/**
* 使用管道流进行线程之间的通信
* */
public
class
PipedTreadDemo{
public
static
void
main(String[] args){
Sender send =
new
Sender();
Reciver rec =
new
Reciver();
try
{
send.getOut().connect(rec.getReciver());
}
catch
(Exception e){
e.printStackTrace();
}
new
Thread(send).start();
new
Thread(rec).start();
}
}
/**
* 消息发送类
* */
class
Sender
implements
Runnable{
private
PipedOutputStream out =
null
;
public
Sender(){
out =
new
PipedOutputStream();
}
public
PipedOutputStream getOut(){
return
this
.out;
}
@Override
public
void
run(){
String str =
"rollen holt"
;
try
{
out.write(str.getBytes());
}
catch
(IOException e){
e.printStackTrace();
}
try
{
out.close();
}
catch
(IOException e){
e.printStackTrace();
}
}
}
/**
* 消息接受类
* */
class
Reciver
implements
Runnable{
private
PipedInputStream input =
null
;
public
Reciver(){
input =
new
PipedInputStream();
}
public
PipedInputStream getReciver(){
return
this
.input;
}
@Override
public
void
run(){
byte
[] bytes =
new
byte
[
1024
];
int
len =
0
;
try
{
len = input.read(bytes);
}
catch
(IOException e){
e.printStackTrace();
}
try
{
input.close();
}
catch
(IOException e){
e.printStackTrace();
}
System.out.println(
"读取的内容为:"
+
new
String(bytes,
0
, len));
}
}
|
【运行结果】:
读取的内容为:rollen holt
下面,我们在同步的前提下,在举出一个线程通信的例子:
首先摆出运行结果再说:
程序代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
|
package
Thread;
/**
* 线程之间的通信
* */
import
java.awt.GridLayout;
import
java.awt.event.ActionEvent;
import
java.awt.event.ActionListener;
import
javax.swing.JButton;
import
javax.swing.JFrame;
import
javax.swing.JLabel;
import
javax.swing.JPanel;
import
javax.swing.JScrollPane;
import
javax.swing.JTextArea;
public
class
TreadCommunicate
extends
JFrame{
public
TreadCommunicate(){
panel.setLayout(
new
GridLayout(
2
,
2
,
3
,
3
));
panel.add(label1);
panel.add(label2);
JScrollPane js1 =
new
JScrollPane(oneArea,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
panel.add(js1);
JScrollPane js2 =
new
JScrollPane(twoArea,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
panel.add(js2);
statrButton.addActionListener(
new
ActionListener(){
@Override
public
void
actionPerformed(ActionEvent e){
Sender sender =
new
Sender();
Thread demo1 =
new
Thread(sender);
Thread demo2 =
new
Thread(
new
Receiver(sender));
demo1.start();
demo2.start();
}
});
panel2.add(panel);
panel2.add(statrButton);
setContentPane(panel2);
setSize(
300
,
400
);
setVisible(
true
);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public
static
void
main(String[] args){
new
TreadCommunicate();
}
/**
* 卖家
* */
class
Sender
implements
Runnable{
@Override
public
void
run(){
for
(
int
i =
0
; i <
5
; ++i){
// 如果已经发送,那么就等待
while
(isValid){
Thread.yield();
}
product = products[i];
String text = oneArea.getText();
oneArea.setText(text +
"发送"
+ product +
"\n"
);
try
{
Thread.sleep(
1000
);
}
catch
(Exception e){
e.printStackTrace();
}
isValid =
true
;
}
}
public
boolean
isisValid(){
return
this
.isValid;
}
public
void
setValid(
boolean
flag){
this
.isValid = flag;
}
public
String getProduct(){
return
product;
}
private
volatile
String[] products = {
"《***》"
,
"《红楼梦》"
,
"《平凡的世界》"
,
"《流氓老师》"
,
"《西游记》"
};
private
volatile
boolean
isValid =
false
;
private
volatile
String product;
}
// end sender
/**
* 买家
* */
class
Receiver
implements
Runnable{
public
Receiver(){
}
public
Receiver(Sender sender){
this
.sender = sender;
}
@Override
public
void
run(){
for
(
int
i =
0
; i <
5
; ++i){
// 如果没有发送,就等待
while
(!sender.isisValid()){
Thread.yield();
}
String test = twoArea.getText();
twoArea.setText(test +
"接受到"
+ sender.getProduct() +
"\n"
);
try
{
Thread.sleep(
1000
);
}
catch
(Exception e){
e.printStackTrace();
}
sender.setValid(
false
);
}
}
private
Sender sender;
}
JPanel panel =
new
JPanel();
JPanel panel2 =
new
JPanel();
private
final
JButton statrButton =
new
JButton(
"开始交易"
);
private
final
JLabel label1 =
new
JLabel(
"卖家"
);
private
final
JLabel label2 =
new
JLabel(
"买家"
);
private
final
JTextArea oneArea =
new
JTextArea(
5
,
10
);
private
final
JTextArea twoArea =
new
JTextArea(
5
,
10
);
}
|
下面绝对不是本人蛋疼的写出这个一个更加叫人蛋疼的程序。只是给出了一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
/**
* 简单的死锁
* */
public
class
DeadLockDemo
implements
Runnable{
@Override
public
void
run(){
// 获得当前线程的名字
String str = Thread.currentThread().getName();
System.out.println(str +
": flag= "
+ flag);
if
(flag){
synchronized
(obj1){
try
{
Thread.sleep(
1000
);
}
catch
(Exception e){
e.printStackTrace();
}
System.out.println(str +
"已经进入同步快obj1,准备进入同步快obj2"
);
synchronized
(obj2){
System.out.println(str +
"已经进入同步快obj2"
);
}
}
}
if
(!flag){
synchronized
(obj2){
try
{
Thread.sleep(
1000
);
}
catch
(Exception e){
e.printStackTrace();
}
System.out.println(str +
"已经进入同步快obj2,准备进入同步快obj1"
);
synchronized
(obj1){
System.out.println(str +
"已经进入同步快obj1"
);
}
}
}
}
public
static
void
main(String[] args){
DeadLockDemo demo1 =
new
DeadLockDemo();
DeadLockDemo demo2 =
new
DeadLockDemo();
demo1.flag =
true
;
demo2.flag =
false
;
new
Thread(demo1).start();
new
Thread(demo2).start();
}
private
boolean
flag;
private
final
Object obj1 =
new
Object();
private
final
Object obj2 =
new
Object();
}
|
【运行结果】
(我承认我今天RP爆发,我运行了10次,还是没出现死锁那种情况,但是这个程序确实可以产生死锁的,哪位运行这个程序,要是产生了死锁,麻烦说一下,谢谢)
这个例子使用的是Executors类,读者自行查看API,因为要解说的话,就太多了。
下面这个例子给出了使用线程池和不使用线程池的情况下的效率的问题。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
package
Thread;
import
java.util.concurrent.ExecutorService;
import
java.util.concurrent.Executors;
/**
* 使用线程池优化多线程编程
* */
public
class
ThreadPoolDemo{
public
static
void
main(String[] args){
Runtime run = Runtime.getRuntime();
// 为了减少误差
run.gc();
long
currentTime = System.currentTimeMillis();
long
freemonery = run.freeMemory();
for
(
int
i =
0
; i <
10000
; ++i){
new
Thread(
new
Temo()).start();
}
System.out.println(
"独立运行10000个线程占用内存为:"
+ (freemonery - run.freeMemory()));
System.out.println(
"独立运行10000个线程占用时间为:"
+ (System.currentTimeMillis() - currentTime));
// 下面使用线程池来试试
run.gc();
freemonery = run.freeMemory();
currentTime = System.currentTimeMillis();
ExecutorService executorService = Executors.newFixedThreadPool(
3
);
for
(
int
i =
0
; i <
10000
; ++i){
executorService.submit(
new
Temo());
}
System.out.println(
"使用线程池运行10000个线程占用内存为:"
+ (freemonery - run.freeMemory()));
System.out.println(
"使用线程池运行10000个线程占用时间为:"
+ (System.currentTimeMillis() - currentTime));
}
}
class
Temo
implements
Runnable{
@Override
public
void
run(){
count++;
}
private
int
count =
0
;
}
|
【运行结果】:
独立运行10000个线程占用内存为:3490440
独立运行10000个线程占用时间为:1808
使用线程池运行10000个线程占用内存为:1237424
使用线程池运行10000个线程占用时间为:62
由于代码比较长,所以单独列出为一篇文章
地址:http://www.cnblogs.com/rollenholt/archive/2011/09/15/2178004.html
现在我们继续回答之前银行存款的问题,相信大家还没有忘记,哈哈,真是不好意思,本来线程同步这一块应该整理在一起的。
一个信号量有3中操作,而且他们全部都是原子的,初始化,增加,减少。增加可以为一个进程解除阻塞,减少可以为一个进程进入阻塞。
Semaphore类是一个技术信号量,从概念上信号量维持了一个许可集。
现在我们继续看上面的银行存款问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
package
Thread;
import
java.awt.GridLayout;
import
java.awt.event.ActionEvent;
import
java.awt.event.ActionListener;
import
java.util.concurrent.Semaphore;
import
javax.swing.JButton;
import
javax.swing.JFrame;
import
javax.swing.JLabel;
import
javax.swing.JPanel;
import
javax.swing.JScrollPane;
import
javax.swing.JTextArea;
public
class
synDemo
extends
JFrame{
public
synDemo(){
panel.setLayout(
new
GridLayout(
2
,
2
,
3
,
3
));
panel.add(label1);
panel.add(label2);
JScrollPane js1 =
new
JScrollPane(oneArea,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
panel.add(js1);
JScrollPane js2 =
new
JScrollPane(twoArea,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
panel.add(js2);
panel2.add(panel);
panel2.add(statrButton);
setContentPane(panel2);
statrButton.addActionListener(
new
ActionListener(){
@Override
public
void
actionPerformed(ActionEvent e){
Thread demo1 =
new
Thread(
new
Transfer1(bank, oneArea,
semaphore));
demo1.start();
Thread demo2 =
new
Thread(
new
Transfer1(bank, twoArea,
semaphore));
demo2.start();
}
});
setSize(
300
,
400
);
setVisible(
true
);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public
static
void
main(String[] args){
new
synDemo();
}
Semaphore semaphore =
new
Semaphore(
1
,
true
);
private
final
Bank1 bank =
new
Bank1();
JPanel panel =
new
JPanel();
JPanel panel2 =
new
JPanel();
private
final
JButton statrButton =
new
JButton(
"开始存钱"
);
private
final
JLabel label1 =
new
JLabel(
"一号线程"
);
private
final
JLabel label2 =
new
JLabel(
"二号线程"
);
private
final
JTextArea oneArea =
new
JTextArea(
5
,
10
);
private
final
JTextArea twoArea =
new
JTextArea(
5
,
10
);
}
/**
* 表示银行的类
* */
class
Bank1{
public
Bank1(){
}
public
void
deposit(
int
money){
account += money;
}
public
int
getAccount(){
return
account;
}
private
int
account;
}
/**
* 表示往账户存钱
* */
class
Transfer1
implements
Runnable{
public
Transfer1(){
}
public
Transfer1(Bank1 bank, JTextArea textArea, Semaphore semaphore){
this
.bank = bank;
this
.textArea = textArea;
this
.semaphore = semaphore;
}
@Override
public
void
run(){
for
(
int
i =
0
; i <
20
; ++i){
// 获得许可
try
{
semaphore.acquire();
}
catch
(InterruptedException e){
e.printStackTrace();
}
bank.deposit(
10
);
String str = textArea.getText();
textArea.setText(str +
"账户余额为:"
+ bank.getAccount() +
"\n"
);
// 释放许可
semaphore.release();
}
}
// 注意
private
Semaphore semaphore;
private
Bank1 bank =
null
;
private
JTextArea textArea =
null
;
}
|
运行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
package
Thread;
import
java.awt.GridLayout;
import
java.awt.event.ActionEvent;
import
java.awt.event.ActionListener;
import
java.util.concurrent.atomic.AtomicInteger;
import
javax.swing.JButton;
import
javax.swing.JFrame;
import
javax.swing.JLabel;
import
javax.swing.JPanel;
import
javax.swing.JScrollPane;
import
javax.swing.JTextArea;
public
class
synDemo
extends
JFrame{
public
synDemo(){
panel.setLayout(
new
GridLayout(
2
,
2
,
3
,
3
));
panel.add(label1);
panel.add(label2);
JScrollPane js1 =
new
JScrollPane(oneArea,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
panel.add(js1);
JScrollPane js2 =
new
JScrollPane(twoArea,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
panel.add(js2);
panel2.add(panel);
panel2.add(statrButton);
setContentPane(panel2);
statrButton.addActionListener(
new
ActionListener(){
@Override
public
void
actionPerformed(ActionEvent e){
Thread demo1 =
new
Thread(
new
Transfer1(bank, oneArea));
demo1.start();
Thread demo2 =
new
Thread(
new
Transfer1(bank, twoArea));
demo2.start();
}
});
setSize(
300
,
400
);
setVisible(
true
);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public
static
void
main(String[] args){
new
synDemo();
}
private
final
Bank1 bank =
new
Bank1();
JPanel panel =
new
JPanel();
JPanel panel2 =
new
JPanel();
private
final
JButton statrButton =
new
JButton(
"开始存钱"
);
private
final
JLabel label1 =
new
JLabel(
"一号线程"
);
private
final
JLabel label2 =
new
JLabel(
"二号线程"
);
private
final
JTextArea oneArea =
new
JTextArea(
5
,
10
);
private
final
JTextArea twoArea =
new
JTextArea(
5
,
10
);
}
/**
* 表示银行的类
* */
class
Bank1{
public
Bank1(){
}
public
void
deposit(
int
money){
account.addAndGet(money);
}
public
int
getAccount(){
return
account.get();
}
//注意
private
final
AtomicInteger account =
new
AtomicInteger(
100
);
}
/**
* 表示往账户存钱
* */
class
Transfer1
implements
Runnable{
public
Transfer1(){
}
public
Transfer1(Bank1 bank, JTextArea textArea){
this
.bank = bank;
this
.textArea = textArea;
}
@Override
public
void
run(){
for
(
int
i =
0
; i <
20
; ++i){
bank.deposit(
10
);
String str = textArea.getText();
textArea.setText(str +
"账户余额为:"
+ bank.getAccount() +
"\n"
);
}
}
private
Bank1 bank =
null
;
private
JTextArea textArea =
null
;
}
|
【运行结果】:
http://www.blogjava.net/sinpo/archive/2008/10/22/236061.html
关于线程间的交互和共享数据通常有轮询和通知机制。一下举例说明:Thread1和Thread2共享一块数据ShareData,Thread1使用数据,Thread2更新数据。当Thread1使用数据时发现数据没有更新就可以先休眠(sleep())一段时间然后再去判断是否更新,如此反复直到数据可用,这就是所述的轮询机制。可以看出轮询机制需要不断的轮询数据状态,很耗费资源;当采用通知机制时过程是这样的,Thread1发现数据不可用就在ShareData上等待(ShareData.wait()),当Thread2更新数据后就通知所有在ShareData上等待的线程(ShareData.notifyAll()),这样Thread1受到通知继续运行。
关于等待和休眠还有另一个区别就是当线程等待时,该线程锁定的资源是释放掉的,这时其它线程是可以锁定这些资源的,当线程被唤醒或者等待时限到时线程重新获取资源才能继续运行;而当线程休眠时线程锁定的资源是不被释放的。
还有一点就是要在对象lock上等待时是必须先要获取lock的对象锁才能进行的,即必须要类似下面的逻辑 synchronized(lock){ lock.wait()}
以下为一个简单的示例:
package sinpo.usagedemo; /** * 该例子说明线程休眠与等待以及注意事项。 * * @author 徐辛波([email protected]) * Oct 22, 2008 */ public class PendingThreadDemo { public Console console = new Console () ; private void writeToConsole1 () { synchronized ( console ){ try { Thread.sleep ( 1 * 1000 ) ; //NOTE:sleep时并未释放console别的线程是不能锁定console的 //TODO do things } catch ( InterruptedException e ) { e.printStackTrace () ; } } } private void writeToConsole2 () { synchronized ( console ){ try { console.wait ( 1 * 1000 ) ; //NOTE:wait时别的线程是可以锁定console的 //TODO do things } catch ( InterruptedException e ) { e.printStackTrace () ; } } } } //控制台类 class Console { //TODO implements me }