java并发编程(1)火车票售票问题
2017-08-02 13:38
148 查看
Java的关于线程同步和互斥方面的策略有很多,比如synchronized、信号量、线程的wait和notify方法等等。讲解之前我们有必要区分对象、类和线程的所有权关系。首先声明一点:一个对象可以有多个线程共享,比如我们在一个类A中写十个内部类,这十个内部类都继承Thread类,然后类A中有十个属性,分别是十个内部线程类的对象,这种情况就属于多个线程共享一个对象;我们还必须清楚的认识到,一个类也可以有多个线程共享,比如我们有个类B继承了Thread类,我们在另外一个类中用循环new十个B的对象并启动线程,那么这十个线程就共享B类的所有静态属性。
下面一个一个来讲解:
我们先来看看原始的代码是怎样的,如下所示,这是一个售票的程序,共有十个售票员线程,想共同把150张票售完,代码如下所示:
_______________________________________________________________________________
public
class SynchorizedTest implements Runnable{
private
static int num=150;
//表示总共的票数 150,所有对象共享
private
int seller;
//表示售票员的编号
SynchorizedTest(int seller){
//构造方法
可以指定对象是哪个销售员
this.seller=seller;
}
private
void sell(){ //当前售票员的售票方法
4000
if(num>0){
System.out.println("seller"+this.seller+":ticket"+num+"
isselled!");
num--;
}
}
public
static void main(String[] args) {
for(int i=1;i<=10;i++){
SynchorizedTest st=new SynchorizedTest(i);
new Thread(st).start();
}
}
@Override
public
void run() { //每个售票员线程都执行相同的售票操作
while(true){
sell();
if(num<=0)break;
}
}
}
_______________________________________________________________________________
seller2:ticket 150 is selled!
seller1:ticket 150 is selled!
seller2:ticket 149 is selled!
seller10:ticket 150 is selled!
seller9:ticket 150 is selled!
seller7:ticket 150 is selled!
seller8:ticket 150 is selled!
seller6:ticket 150 is selled!
seller6:ticket 142 is selled!
seller6:ticket 141 is selled!
seller6:ticket 140 is selled!
seller3:ticket 150 is selled!
seller5:ticket 150 is selled! _______________________________________________________________________________
原本代码的执行结果如上所示,我们会发现第150号票被多次重复售出,这很明显是不合理的。
究其原因,是因为num-- 这个操作是CPU来做的,系统先把内存中num加载到CPU的寄存器中,然后执行加法操作,再从寄存器写到内存原来num的位置。如果是一个线程,这个过程没什么问题,但是这里是多线程的,很可能一个线程把num刚加载到CPU,还未来得及写回内存,这个线程的时间片用完了,第二个线程又开始把num加载到CPU执行运算,这样就是导致重复售票的问题。如果想要解决这个问题,关键是在一个线程开始把num加载到CPU,直到运算完写回CPU的过程中不能中断(这里指的是不能被其他9个线程横叉一笔,并不是说这个期间要一直占用CPU,因为还有其他程序的线程)。
接下来我们试试java提供的各种同步互斥的策略能不能解决这个问题。
1. synchronized关键字
synchronized关键字是java提供的一种比较简答的解决同步和互斥的策略。Synchronized可以锁定对象,代码块,静态方法和普通方法,下面我们分别讨论各种锁定的功能在这道题上的效果。
(1) 对象/代码块的锁定
Synchronized关键字锁定对象和代码块的格式为:
synchronized(对象)
{
互斥执行的代码块
}
Synchronized对对象进行锁定的时候,不管当前有多少个线程共用这个对象,在执行到我们锁定的代码块的时候当前对象只能被一个线程拥有,这是因为每个对象都有一个monitor,线程在使用对象的时候必须先获得对象monitor的所有权。如果我们想互斥的访问某个对象的某些属性,可以在对这些属性进行修改的地方把当前对象锁定。
对这道题来说,锁定对象似乎并没有什么作用,因为我们在main方法中是new了十个对象,这十个对象各是一个线程,这十个线程共享的是一个类,而不是一个对象,因此锁定代码块没有什么作用,不信吗?我们来试试!
下面的代码是对sell方法中当前对象进行锁定(其他代码不变):
_______________________________________________________________________________
private
void sell(){ //当前售票员的售票方法
synchronized(this){
if(num>0){
System.out.println("seller"+this.seller+":ticket
"+num+" isselled!");
num--;
}
}
}
_______________________________________________________________________________
执行的部分结果如下所示:
_______________________________________________________________________________
seller1:ticket 150 is selled!
seller2:ticket 150 is selled!
seller2:ticket 148 is selled!
seller2:ticket 147 is selled!
seller5:ticket 147 is selled!
seller5:ticket 145 is selled!
seller5:ticket 144 is selled!
seller5:ticket 143 is selled!
seller5:ticket 142 is selled!
_______________________________________________________________________________
经过多次执行测试,可以发现依旧存在重复售票的问题。
(2) 普通方法的锁定
如果一个非静态的方法声明中使用synchronized,这个方法的在执行期间当前对象只能被一个线程拥有。换句话说:synchronized void f() { /* body */ } 和voidf() { synchronized(this) { /* body */ } }是完全等价的。所以,和上面一样,锁定方法也不适用于售票问题。
(3) 静态方法的锁定
静态方法的锁定本质上是对类的锁定而不是对象的锁定,对于这道题来说,如果我们把sell方法声明为静态的,然后用synchronized锁定,可不可以解决重复售票的问题呢?我们拭目以待!
___________________________________________________________________________________
private
static synchronized
void sell(){ //当前售票员的售票方法
if(num>0){
System.out.println("ticket"+num+" isselled!");
num--;
}
}
_____________________________________________________________________
ticket 150 is selled!
ticket 149 is selled!
ticket 148 is selled!
ticket 147 is selled!
ticket 146 is selled!
ticket 145 is selled!
ticket 144 is selled!
ticket 143 is selled!
ticket 142 is selled!
……
ticket 5 is selled!
ticket 4 is selled!
ticket 3 is selled!
ticket 2 is selled!
ticket 1 is selled!
_____________________________________________________________________
因为静态方法中不能访问seller这个非静态的属性,所以不得不把销售员去掉了,但是大家可以发现重复售票的问题确实被解决了,但是不知道那个售票员卖出的票心里总觉得不放心,那么我们在线程的run方法中把这个seller输出:
_____________________________________________________________________
@Override
public
void run() { //每个售票员线程都执行相同的售票操作
while(true){
sell();
System.out.println(seller+"售票成功!");
if(num<=0)break;
}
}
_____________________________________________________________________
ticket 150 is selled!
ticket 149 is selled!
10售票成功!
1售票成功!
ticket 148 is selled!
5售票成功!
ticket 147 is selled!
5售票成功!
ticket 146 is selled!
5售票成功!
ticket 145 is selled!
5售票成功!
ticket 144 is selled!
_____________________________________________________________________
多次执行结果表明,每个售票员确实是并发售票的,而且完美的解决了重复售票的问题!只是静态方法不能售票的同事显示售票员,所以看起来不是很美观。
(4) 对象属性的锁定
如果我们在num声明的时候直接指定num为互斥变量,结果会怎么样呢?很可惜,java里不能直接使用synchronized声明和修饰一个普通类型的变量(如果一个对象作为一个类的属性,是可以被synchronized修饰的,注意这里是修饰,不是声明),而是使用synchronized去修饰一个代码块或一个方法。
下面我们测试一下,如果把类的一个属性(前提是这个属性是某类的对象),那么类会不会也会被锁定呢?
测试代码如下:我们的目的是让加法的线程和减法的线程互斥执行,也就是先把num加大最大,然后减到最小,中间不要有加减法交叉。
_______________________________________________________________________________
public
class SynchorizedTest {
private
int num;
private String
flag;
SynchorizedTest(){
this.num=0;
this.flag="Test";
}
private
void addNum(){
synchronized (this.flag){
this.num++;
System.out.print(this.num+"
");
};
}
private
void absNum(){
synchronized (this.flag){
this.num--;
System.out.print(this.num+"
");
};
}
public
void launch(){
new ThreadAdd().start();
new ThreadAbs().start();
}
public
static void main(String[] args) {
SynchorizedTest test=new SynchorizedTest();
test.launch();
}
private
class ThreadAdd extends Thread{
@Override
public
void run() {
while(true){
addNum();
if(num>10)break;
}
}
}
private
class ThreadAbs extends Thread{
@Override
public
void run() {
while(true){
absNum();
if(num<-10)break;
}
}
}
}
_______________________________________________________________________________
代码执行的结果如下所示:
_______________________________________________________________________________
1 2 3 4 5 6 7 8 9 10 11 10 9 8 7 6 5 4 3 2 1 0 -1 -2 -3 -4 -5 -6 -7 -8 -9-10 -11
_______________________________________________________________________________
我们会发现结果相当好,多次执行结果表明两个线程看起来是成功互斥执行。但是为什么要重新定义一个String类型的属性,为什么不能直接用synchronized修饰num呢?用synchronized修饰num我也想啊,可是java规定synchronized不能修饰基本数据类型,只能修饰对象,方法或者代码块,因此我们只能定义一个String类型的对象,但是锁定对象的某个属性和锁定对象一样吗?为什么能通过锁定属性实现互斥对象呢?再一次证明了
答案是我们不能把锁定对象的属性和锁定对象混为一谈,如果我们把加法的上限加到10000,把减法的下限减到-10000,会发现两个线程依旧存在交叉执行的情况,因此可以发现,num和flag都是对象的属性,锁定flag属性并不能保证num属性也处于锁定状态。
下面一个一个来讲解:
我们先来看看原始的代码是怎样的,如下所示,这是一个售票的程序,共有十个售票员线程,想共同把150张票售完,代码如下所示:
_______________________________________________________________________________
public
class SynchorizedTest implements Runnable{
private
static int num=150;
//表示总共的票数 150,所有对象共享
private
int seller;
//表示售票员的编号
SynchorizedTest(int seller){
//构造方法
可以指定对象是哪个销售员
this.seller=seller;
}
private
void sell(){ //当前售票员的售票方法
4000
if(num>0){
System.out.println("seller"+this.seller+":ticket"+num+"
isselled!");
num--;
}
}
public
static void main(String[] args) {
for(int i=1;i<=10;i++){
SynchorizedTest st=new SynchorizedTest(i);
new Thread(st).start();
}
}
@Override
public
void run() { //每个售票员线程都执行相同的售票操作
while(true){
sell();
if(num<=0)break;
}
}
}
_______________________________________________________________________________
seller2:ticket 150 is selled!
seller1:ticket 150 is selled!
seller2:ticket 149 is selled!
seller10:ticket 150 is selled!
seller9:ticket 150 is selled!
seller7:ticket 150 is selled!
seller8:ticket 150 is selled!
seller6:ticket 150 is selled!
seller6:ticket 142 is selled!
seller6:ticket 141 is selled!
seller6:ticket 140 is selled!
seller3:ticket 150 is selled!
seller5:ticket 150 is selled! _______________________________________________________________________________
原本代码的执行结果如上所示,我们会发现第150号票被多次重复售出,这很明显是不合理的。
究其原因,是因为num-- 这个操作是CPU来做的,系统先把内存中num加载到CPU的寄存器中,然后执行加法操作,再从寄存器写到内存原来num的位置。如果是一个线程,这个过程没什么问题,但是这里是多线程的,很可能一个线程把num刚加载到CPU,还未来得及写回内存,这个线程的时间片用完了,第二个线程又开始把num加载到CPU执行运算,这样就是导致重复售票的问题。如果想要解决这个问题,关键是在一个线程开始把num加载到CPU,直到运算完写回CPU的过程中不能中断(这里指的是不能被其他9个线程横叉一笔,并不是说这个期间要一直占用CPU,因为还有其他程序的线程)。
接下来我们试试java提供的各种同步互斥的策略能不能解决这个问题。
1. synchronized关键字
synchronized关键字是java提供的一种比较简答的解决同步和互斥的策略。Synchronized可以锁定对象,代码块,静态方法和普通方法,下面我们分别讨论各种锁定的功能在这道题上的效果。
(1) 对象/代码块的锁定
Synchronized关键字锁定对象和代码块的格式为:
synchronized(对象)
{
互斥执行的代码块
}
Synchronized对对象进行锁定的时候,不管当前有多少个线程共用这个对象,在执行到我们锁定的代码块的时候当前对象只能被一个线程拥有,这是因为每个对象都有一个monitor,线程在使用对象的时候必须先获得对象monitor的所有权。如果我们想互斥的访问某个对象的某些属性,可以在对这些属性进行修改的地方把当前对象锁定。
对这道题来说,锁定对象似乎并没有什么作用,因为我们在main方法中是new了十个对象,这十个对象各是一个线程,这十个线程共享的是一个类,而不是一个对象,因此锁定代码块没有什么作用,不信吗?我们来试试!
下面的代码是对sell方法中当前对象进行锁定(其他代码不变):
_______________________________________________________________________________
private
void sell(){ //当前售票员的售票方法
synchronized(this){
if(num>0){
System.out.println("seller"+this.seller+":ticket
"+num+" isselled!");
num--;
}
}
}
_______________________________________________________________________________
执行的部分结果如下所示:
_______________________________________________________________________________
seller1:ticket 150 is selled!
seller2:ticket 150 is selled!
seller2:ticket 148 is selled!
seller2:ticket 147 is selled!
seller5:ticket 147 is selled!
seller5:ticket 145 is selled!
seller5:ticket 144 is selled!
seller5:ticket 143 is selled!
seller5:ticket 142 is selled!
_______________________________________________________________________________
经过多次执行测试,可以发现依旧存在重复售票的问题。
(2) 普通方法的锁定
如果一个非静态的方法声明中使用synchronized,这个方法的在执行期间当前对象只能被一个线程拥有。换句话说:synchronized void f() { /* body */ } 和voidf() { synchronized(this) { /* body */ } }是完全等价的。所以,和上面一样,锁定方法也不适用于售票问题。
(3) 静态方法的锁定
静态方法的锁定本质上是对类的锁定而不是对象的锁定,对于这道题来说,如果我们把sell方法声明为静态的,然后用synchronized锁定,可不可以解决重复售票的问题呢?我们拭目以待!
___________________________________________________________________________________
private
static synchronized
void sell(){ //当前售票员的售票方法
if(num>0){
System.out.println("ticket"+num+" isselled!");
num--;
}
}
_____________________________________________________________________
ticket 150 is selled!
ticket 149 is selled!
ticket 148 is selled!
ticket 147 is selled!
ticket 146 is selled!
ticket 145 is selled!
ticket 144 is selled!
ticket 143 is selled!
ticket 142 is selled!
……
ticket 5 is selled!
ticket 4 is selled!
ticket 3 is selled!
ticket 2 is selled!
ticket 1 is selled!
_____________________________________________________________________
因为静态方法中不能访问seller这个非静态的属性,所以不得不把销售员去掉了,但是大家可以发现重复售票的问题确实被解决了,但是不知道那个售票员卖出的票心里总觉得不放心,那么我们在线程的run方法中把这个seller输出:
_____________________________________________________________________
@Override
public
void run() { //每个售票员线程都执行相同的售票操作
while(true){
sell();
System.out.println(seller+"售票成功!");
if(num<=0)break;
}
}
_____________________________________________________________________
ticket 150 is selled!
ticket 149 is selled!
10售票成功!
1售票成功!
ticket 148 is selled!
5售票成功!
ticket 147 is selled!
5售票成功!
ticket 146 is selled!
5售票成功!
ticket 145 is selled!
5售票成功!
ticket 144 is selled!
_____________________________________________________________________
多次执行结果表明,每个售票员确实是并发售票的,而且完美的解决了重复售票的问题!只是静态方法不能售票的同事显示售票员,所以看起来不是很美观。
(4) 对象属性的锁定
如果我们在num声明的时候直接指定num为互斥变量,结果会怎么样呢?很可惜,java里不能直接使用synchronized声明和修饰一个普通类型的变量(如果一个对象作为一个类的属性,是可以被synchronized修饰的,注意这里是修饰,不是声明),而是使用synchronized去修饰一个代码块或一个方法。
下面我们测试一下,如果把类的一个属性(前提是这个属性是某类的对象),那么类会不会也会被锁定呢?
测试代码如下:我们的目的是让加法的线程和减法的线程互斥执行,也就是先把num加大最大,然后减到最小,中间不要有加减法交叉。
_______________________________________________________________________________
public
class SynchorizedTest {
private
int num;
private String
flag;
SynchorizedTest(){
this.num=0;
this.flag="Test";
}
private
void addNum(){
synchronized (this.flag){
this.num++;
System.out.print(this.num+"
");
};
}
private
void absNum(){
synchronized (this.flag){
this.num--;
System.out.print(this.num+"
");
};
}
public
void launch(){
new ThreadAdd().start();
new ThreadAbs().start();
}
public
static void main(String[] args) {
SynchorizedTest test=new SynchorizedTest();
test.launch();
}
private
class ThreadAdd extends Thread{
@Override
public
void run() {
while(true){
addNum();
if(num>10)break;
}
}
}
private
class ThreadAbs extends Thread{
@Override
public
void run() {
while(true){
absNum();
if(num<-10)break;
}
}
}
}
_______________________________________________________________________________
代码执行的结果如下所示:
_______________________________________________________________________________
1 2 3 4 5 6 7 8 9 10 11 10 9 8 7 6 5 4 3 2 1 0 -1 -2 -3 -4 -5 -6 -7 -8 -9-10 -11
_______________________________________________________________________________
我们会发现结果相当好,多次执行结果表明两个线程看起来是成功互斥执行。但是为什么要重新定义一个String类型的属性,为什么不能直接用synchronized修饰num呢?用synchronized修饰num我也想啊,可是java规定synchronized不能修饰基本数据类型,只能修饰对象,方法或者代码块,因此我们只能定义一个String类型的对象,但是锁定对象的某个属性和锁定对象一样吗?为什么能通过锁定属性实现互斥对象呢?再一次证明了
答案是我们不能把锁定对象的属性和锁定对象混为一谈,如果我们把加法的上限加到10000,把减法的下限减到-10000,会发现两个线程依旧存在交叉执行的情况,因此可以发现,num和flag都是对象的属性,锁定flag属性并不能保证num属性也处于锁定状态。
相关文章推荐
- Java多线程4—线程同步问题+火车票售票系统
- Java中创建线程的两个方法____解决火车票或售票问题
- Java多线程4—线程同步问题+火车票售票系统
- Java多线程窗口售票问题实例
- Java并发编程(5):volatile变量修饰符—意料之外的问题(含代码)
- iOS-多线程(模拟火车票售票系统)
- 火车票售票中心分配一定数量的票,由若干个售票窗口进行出售
- 售票问题
- 又见火车票问题
- 关于多线程实现火车票售票模拟
- 关于火车票订购网出现的一些问题
- 模拟火车票销售系统--线程同步+安全问题(初期 1)
- 简单的火车票售票系统-单线
- “春节”期间北京火车票问题
- 多线程火车票售票系统——互斥对象实现线程同步
- [全国十大城市火车票售票点、订票电话(买票再也不用去火车站排队)] – [旅游] – [校内论坛]
- 抛砖引玉:使用二进制位操作,解决铁道部火车票的数据查询和存储问题,超轻量级的解决方案
- java中的线程同步问题 模拟出售火车票
- Java并发编程-问题(三)
- Java并发编程-活跃度问题