对于单个窗口,我们会写一个简单的循环语句:
public class Ticket {
int tick = 100;
public void sale() {
while(true) {
if (tick > 0) {
System.out.println("剩余车票: 售出车票号码为:" + tick--);
}else {
break;
}
}
}
public static void main(String[] args) {
Ticket ticket = new Ticket();
ticket.sale();
}
}
如果是三个窗口同时开展售票业务,就要使用多线程解决这个问题了。Java实现多线程的步骤如下:
- 定义接口实现类,实现Runnable接口。
- 重写Runnable接口中的run方法。
- 通过Thread类含参构造器创建线程对象。
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
- 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法
class Sale implements Runnable{
//定义接口实现类,实现Runnable接口
@Override
public void run() {
//子类中重写Runnable接口中的run方法。
System.out.println("多线程执行语句blablabla");
}
public static void main(String[] args){
//通过Thread类含参构造器创建线程对象。
//将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
//调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法
new Thread(new Sale()).start();
new Thread(new Sale()).start();
new Thread(new Sale()).start();
}
}
-------------------------
多线程执行语句blablabla
多线程执行语句blablabla
多线程执行语句blablabla
我们可以给三个线程命名,并且在显示的时候输出它们的名字:
class Sale implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"多线程执行语句blablabla");
}
public static void main(String[] args){
new Thread(new Sale(),"1号窗口").start();
new Thread(new Sale(),"2号窗口").start();
new Thread(new Sale(),"3号窗口").start();
}
}
-------------------------
2号窗口多线程执行语句blablabla
1号窗口多线程执行语句blablabla
3号窗口多线程执行语句blablabla
然后将前面写好的语句,放到Sale类里,就是下面这样,运行:
package indi.huishi.method;
class Sale implements Runnable{
int tick = 100;
@Override
public void run() {
// System.out.println(Thread.currentThread().getName()+"多线程执行语句blablabla");
while(true) {
if (tick > 0) {
try {
//切换到另外的线程
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售出车票,售出车票号码为: " + tick--);
}else {
break;
}
}
}
public static void main(String[] args){
new Thread(new Sale(),"1号窗口").start();
new Thread(new Sale(),"2号窗口").start();
new Thread(new Sale(),"3号窗口").start();
}
}
-------------------------
2号窗口售出车票,售出车票号码为: 100
3号窗口售出车票,售出车票号码为: 100
1号窗口售出车票,售出车票号码为: 100
1号窗口售出车票,售出车票号码为: 99
2号窗口售出车票,售出车票号码为: 99
3号窗口售出车票,售出车票号码为: 99
...
3号窗口售出车票,售出车票号码为: 2
1号窗口售出车票,售出车票号码为: 2
2号窗口售出车票,售出车票号码为: 2
1号窗口售出车票,售出车票号码为: 1
2号窗口售出车票,售出车票号码为: 1
3号窗口售出车票,售出车票号码为: 1
发现每个窗口都售出了100张票?这是因为我们new了3个Sale
对象,所以每个线程都有各卖各的100张票。而这里的资源(ticket)是三个线程的共享数据,所以应该只new一个Sale
对象。
public static void main(String[] args){
Sale sale = new Sale();
new Thread(sale,"1号窗口").start();
new Thread(sale,"2号窗口").start();
new Thread(sale,"3号窗口").start();
}
1号窗口售出车票,售出车票号码为: 100
3号窗口售出车票,售出车票号码为: 99
2号窗口售出车票,售出车票号码为: 98
...
1号窗口售出车票,售出车票号码为: 8
2号窗口售出车票,售出车票号码为: 6
3号窗口售出车票,售出车票号码为: 7
3号窗口售出车票,售出车票号码为: 5
2号窗口售出车票,售出车票号码为: 4
1号窗口售出车票,售出车票号码为: 3
3号窗口售出车票,售出车票号码为: 1
1号窗口售出车票,售出车票号码为: 2
2号窗口售出车票,售出车票号码为: 0
因为售出车票号码从100开始到1,但是却出现了0号车票。而且,剩余车票应该是每卖出一张就减少1,但是我们发现它的剩余车票并不是递减变化的。
这是由于共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程就参与进来执行。比如一个线程比其他两个线程先做了ticket--
,而它的输出却比另外两个线程慢。如果同时有多个线程满足ticket>0
条件进入if条件下面的语句,如果此时ticket=1
,这样就会输出车票0和-1的情况。
解决问题的办法就是:多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行,也就是使用同步机制。
Java可以使用synchronized关键字实现同步机制。
synchronized可以应用于:
- 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
- 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
- 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
类的对象可以作为锁,这是synchronized
实现同步的基础。所以我们建立一个对象obj
作为锁。使用锁的原则是:使用同一个资源的多个线程共用一把锁,所以obj
不能在下面的循环体中循环创建。
class Sale implements Runnable{
int tick = 100;
Object obj = new Object();
@Override
public void run() {
while(true) {
if (tick > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售出车票,售出车票号码为: " + tick--);
}else {
break;
}
}
}
前面提到在同步范围内,对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。这样就不会出现票号码的乱序,或者号码为0或者-1的情况。
我们应该需要确定同步的范围。既不能太小,没锁住所有有安全问题的代码,也不能太大,没发挥多线程的功能。原则是:所有操作共享数据的这些语句都要放在同步范围中。
如果synchronized
在if
里面,起不到同步的效果,因为tick>0
是操作共享数据的语句,必须要放在同步范围中。
如果synchronized
在where
外面,那么第一个线程开始执行,等到执行完,就是tick=0
的情况,那么这个窗口卖完了所有的票。其他两个线程开始执行的时候,tick=0
,已经没有票了。
所以synchronized
应该在if
和where
中间,while
每循环一次,会有一个线程来执行里面 操作共享数据的多条语句(if
语句)。完整代码如下:
package indi.huishi.method;
class Sale implements Runnable{
int tick = 100;
Object obj = new Object();
@Override
public void run() {
while(true) {
synchronized (obj) {
if (tick > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售出车票,售出车票号码为: " + tick--);
}else {
break;
}
}
}
}
public static void main(String[] args){
Sale sale = new Sale();
new Thread(sale,"1号窗口").start();
new Thread(sale,"2号窗口").start();
new Thread(sale,"3号窗口").start();
}
}
1号窗口售出车票,售出车票号码为: 100
1号窗口售出车票,售出车票号码为: 99
1号窗口售出车票,售出车票号码为: 98
1号窗口售出车票,售出车票号码为: 97
1号窗口售出车票,售出车票号码为: 96
1号窗口售出车票,售出车票号码为: 95
1号窗口售出车票,售出车票号码为: 94
1号窗口售出车票,售出车票号码为: 93
...
2号窗口售出车票,售出车票号码为: 7
2号窗口售出车票,售出车票号码为: 6
2号窗口售出车票,售出车票号码为: 5
2号窗口售出车票,售出车票号码为: 4
2号窗口售出车票,售出车票号码为: 3
2号窗口售出车票,售出车票号码为: 2
2号窗口售出车票,售出车票号码为: 1