利用多线程模拟 3 个窗口卖票
第一种方法:继承 Thread 类
创建窗口类 TicketSell
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
|
package
com.ys.thread;
public
class
TicketSell
extends
Thread{
//定义一共有 50 张票,注意声明为 static,表示几个窗口共享
private
static
int
num =
50
;
//调用父类构造方法,给线程命名
public
TicketSell(String string) {
super
(string);
}
@Override
public
void
run() {
//票分 50 次卖完
for
(
int
i =
0
; i <
50
;i ++){
if
(num >
0
){
try
{
sleep(
10
);
//模拟卖票需要一定的时间
}
catch
(InterruptedException e) {
// 由于父类的 run()方法没有抛出任何异常,根据继承的原则,子类抛出的异常不能大于父类, 故我们这里也不能抛出异常
e.printStackTrace();
}
System.out.println(
this
.currentThread().getName()+
"卖出一张票,剩余"
+(--num)+
"张"
);
}
}
}
}
|
创建主线程测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package
com.ys.thread;
public
class
TestTicket {
public
static
void
main(String[] args) {
//创建 3 个窗口
TicketSell t1 =
new
TicketSell(
"A窗口"
);
TicketSell t2 =
new
TicketSell(
"B窗口"
);
TicketSell t3 =
new
TicketSell(
"C窗口"
);
//启动 3 个窗口进行买票
t1.start();
t2.start();
t3.start();
}
}
|
结果:这里我们省略了一些,根据电脑配置,结果会随机出现不同
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
B窗口卖出一张票,剩余
48
张
A窗口卖出一张票,剩余
47
张
C窗口卖出一张票,剩余
49
张
C窗口卖出一张票,剩余
46
张
B窗口卖出一张票,剩余
44
张
A窗口卖出一张票,剩余
45
张
A窗口卖出一张票,剩余
43
张
...
C窗口卖出一张票,剩余
5
张
A窗口卖出一张票,剩余
4
张
B窗口卖出一张票,剩余
3
张
A窗口卖出一张票,剩余
2
张
C窗口卖出一张票,剩余
3
张
B窗口卖出一张票,剩余
1
张
C窗口卖出一张票,剩余
0
张
A窗口卖出一张票,剩余-
1
张
|
第二种方法:实现 Runnable 接口
创建窗口类 TicketSellRunnable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package
com.ys.thread;
public
class
TicketSellRunnable
implements
Runnable{
//定义一共有 50 张票,继承机制开启线程,资源是共享的,所以不用加 static
private
int
num =
50
;
@Override
public
void
run() {
//票分 50 次卖完
for
(
int
i =
0
; i <
50
;i ++){
if
(num >
0
){
try
{
//模拟卖一次票所需时间
Thread.sleep(
10
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"卖出一张票,剩余"
+(--num)+
"张"
);
}
}
}
}
|
创建主线程测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
package
com.ys.thread;
public
class
TicketSellRunnableTest {
public
static
void
main(String[] args) {
TicketSellRunnable t =
new
TicketSellRunnable();
Thread t1 =
new
Thread(t,
"A窗口"
);
Thread t2 =
new
Thread(t,
"B窗口"
);
Thread t3 =
new
Thread(t,
"C窗口"
);
t1.start();
t2.start();
t3.start();
}
}
|
结果:同理为了篇幅我们也省略了中间的一些结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
B窗口卖出一张票,剩余
49
张
C窗口卖出一张票,剩余
48
张
A窗口卖出一张票,剩余
49
张
B窗口卖出一张票,剩余
47
张
A窗口卖出一张票,剩余
45
张
......
A窗口卖出一张票,剩余
4
张
C窗口卖出一张票,剩余
5
张
A窗口卖出一张票,剩余
3
张
B窗口卖出一张票,剩余
2
张
C窗口卖出一张票,剩余
1
张
B窗口卖出一张票,剩余
0
张
A窗口卖出一张票,剩余-
2
张
C窗口卖出一张票,剩余-
1
张
|
结果分析:这里出现了票数为 负数的情况,这在现实生活中肯定是不存在的,那么为什么会出现这样的情况呢?
解决办法分析:即我们不能同时让超过两个以上的线程进入到 if(num>0)的代码块中,不然就会出现上述的错误。我们可以通过以下三个办法来解决:
1、使用 同步代码块
2、使用 同步方法
3、使用 锁机制
①、使用同步代码块
1
2
3
4
5
6
7
8
9
|
语法:
synchronized
(同步锁) {
//需要同步操作的代码
}
同步锁:为了保证每个线程都能正常的执行原子操作,Java 线程引进了同步机制;同步锁也叫同步监听对象、同步监听器、互斥锁;
Java程序运行使用的任何对象都可以作为同步监听对象,但是一般我们把当前并发访问的共同资源作为同步监听对象
注意:同步锁一定要保证是确定的,不能相对于线程是变化的对象;任何时候,最多允许一个线程拿到同步锁,谁拿到锁谁进入代码块,而其他的线程只能在外面等着
|
实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public
void
run() {
//票分 50 次卖完
for
(
int
i =
0
; i <
50
;i ++){
//这里我们使用当前对象的字节码对象作为同步锁
synchronized
(
this
.getClass()) {
if
(num >
0
){
try
{
//模拟卖一次票所需时间
Thread.sleep(
10
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"卖出一张票,剩余"
+(--num)+
"张"
);
}
}
}
}
|
②、使用 同步方法
语法:即用 synchronized 关键字修饰方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
@Override
public
void
run() {
//票分 50 次卖完
for
(
int
i =
0
; i <
50
;i ++){
sell();
}
}
private
synchronized
void
sell(){
if
(num >
0
){
try
{
//模拟卖一次票所需时间
Thread.sleep(
10
);
}
catch
(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
"卖出一张票,剩余"
+(--num)+
"张"
);
}
}
|
注意:不能直接用 synchronized 来修饰 run() 方法,因为如果这样做,那么就会总是第一个线程进入其中,而这个线程执行完所有操作,即卖完所有票了才会出来。
③、使用 锁机制
1
|
public
interface
Lock
|
主要方法:
常用实现类:
1
2
3
|
public
class
ReentrantLock
extends
Object
implements
Lock, Serializable<br>
//一个可重入互斥Lock具有与使用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
34
35
36
37
38
39
|
package
com.ys.thread;
import
java.util.concurrent.locks.Lock;
import
java.util.concurrent.locks.ReentrantLock;
public
class
TicketSellRunnable
implements
Runnable{
//定义一共有 50 张票,继承机制开启线程,资源是共享的,所以不用加 static
private
int
num =
50
;
//创建一个锁对象
Lock l =
new
ReentrantLock();
@Override
public
void
run() {
//票分 50 次卖完
for
(
int
i =
0
; i <
50
;i ++){
//获取锁
l.lock();
try
{
if
(num >
0
){
//模拟卖一次票所需时间
Thread.sleep(
10
);
System.out.println(Thread.currentThread().getName()+
"卖出一张票,剩余"
+(--num)+
"张"
);
}
}
catch
(Exception e) {
e.printStackTrace();
}
finally
{
//释放锁
l.unlock();
}
}
}
private
void
sell(){
}
}
|