我在编写多线程银行排队代码时,使用栈来赋值所取号码,但是出现了部分号码缺失的情况,应该如何解决?
代码如下:
import java.io.File;
import java.io.IOException;
import java.util.*;
class Stack{
int size=1000;
int []data;
int top=-1;
public Stack(){
this.data=new int[size];
for(int i=1000;i>0;i--){
this.top++;
this.data[this.top]=i;
}
}
public int pop(){
int item=this.data[this.top];
this.top--;
return item;
}
}
class Custom{
private final String account;
private final String ctype;//所需服务
private double inout;
public Custom(String account,String ctype){
this.account=account;
this.ctype=ctype;
}
public Custom(String account,String type,double inout){
this.account=account;
this.ctype=type;
this.inout=inout;
}
public String getAccount() {
return account;
}
public String getCtype() {
return ctype;
}
public double getInout() {
return inout;
}
}
class ATM implements Runnable{
private double []surplus=new double[200];//余额
private int visa;
Map<String,Double> m=new HashMap<>();
String type;
public ATM(String type){
this.type=type;
}
public void fget(){//从文件读取数据
int i=0;
try {
Scanner sc=new Scanner(new File("余额.txt"));
while (sc.hasNextLine()){
surplus[i]=Double.parseDouble(sc.nextLine());
i++;
}
}catch (IOException e){
e.printStackTrace();
}
}
public void link(){//建立键值对
int i=0;
int temp=100;
for(;i<200;i++)
{
m.put(String.valueOf(temp),surplus[i]);
temp++;
}
}
public boolean classify(String account){//是否为跨行
char firstCh=account.charAt(0);
return firstCh == '1';
}
public void serve(String account,double inout,boolean same,int x){//修改余额
double temp;
double commission=2.0;//手续费
if(same){
temp=m.get(account)+inout;
m.put(account,temp);
System.out.println(x+"号 "+account+"***"+" 余额变更"+inout+"元 手续费:0元");
}
else {
temp=m.get(account)+inout-commission;
m.put(account,temp);
System.out.println(x+"号 "+account+"***"+" 余额变更"+inout+"元 手续费:"+commission+"元");
}
}
public void serve(String account,int x,String type){//服务机
System.out.println(x+"号 "+account+"***"+" 选择了"+type);
}
static Stack s=new Stack();
public void run(){
String []select={"g","p","s"};
int x=1;
while (x<20){
x=s.pop();
synchronized (this){
int num=(int)(100+Math.random()*199);
int temp=(int)(Math.random()*3);//随机选择服务
String account=String.valueOf(num);//随机账户
double inout=Math.random()*100-50;
String middle=String.format("%.2f",inout);//转为两位小数
Custom c;
fget();
link();
inout=Double.parseDouble(middle);
if(inout>=0&&temp!=2){
temp=1;
}
else if(temp!=2){
temp=0;
}
if(classify(account)){
if(inout>m.get(account)){
System.out.println("账户余额不足");
}
}
else {
if(inout>(m.get(account)-2)){
System.out.println("账户余额不足");
}
}
if(select[temp].equals("s")){
c=new Custom(account,select[temp]);
}
else {
c=new Custom(account,select[temp],inout);
}
if(type.equals("服务机")&&c.getCtype().equals("s"))
{
System.out.println(type+"空闲");
System.out.println(type+"正在办理"+x+"号"+c.getAccount()+"***"+"的业务");
serve(c.getAccount(),x,type);
}
else if((!c.getAccount().equals("s"))&&type.equals("存取款机")){
System.out.println(type+"空闲");
System.out.println(type+"正在办理"+x+"号"+c.getAccount()+"***"+"的业务");
boolean same=classify(c.getAccount());
serve(c.getAccount(),c.getInout(),same,x);
}
else if (c.getCtype().equals("p")&&type.equals("存款机")) {
System.out.println(type+"空闲");
System.out.println(type+"正在办理"+x+"号"+c.getAccount()+"***"+"的业务");
boolean same=classify(c.getAccount());
serve(c.getAccount(),c.getInout(),same,x);
}
else if (c.getCtype().equals("g")&&type.equals("取款机")) {
System.out.println(type+"空闲");
System.out.println(type+"正在办理"+x+"号"+c.getAccount()+"***"+"的业务");
boolean same=classify(c.getAccount());
serve(c.getAccount(),c.getInout(),same,x);
}
else {
continue;
}
try {
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(type+"办理业务已完成");
}
}
}
}
public class TestATM {
public static void main(String []args){
ATM atm1=new ATM("服务机");
Thread t1=new Thread(atm1);
t1.start();
ATM atm2=new ATM("存取款机");
Thread t2=new Thread(atm2);
t2.start();
ATM atm3=new ATM("取款机");
Thread t3=new Thread(atm3);
t3.start();
ATM atm4=new ATM("存款机");
Thread t4=new Thread(atm4);
t4.start();
}
}
部分结果:
jiyugtt
提供的代码中,出现了多线程环境下的问题,导致部分号码丢失。问题出在以下代码块:
static Stack s = new Stack();
这里的 Stack 是你自己实现的栈数据结构,并且使用了 static 修饰,这意味着所有的线程共享同一个栈对象。在多线程环境下,多个线程同时操作栈,会导致数据竞争和不确定的结果。
解决该问题的方法是使用线程安全的数据结构来存储号码,以避免数据竞争。可以考虑使用 java.util.concurrent 包下的 ConcurrentLinkedQueue 或 BlockingQueue 来存储号码。这些数据结构是线程安全的,可以在多线程环境下安全地进行入队和出队操作。
下面是修改后的代码示例,使用 ConcurrentLinkedQueue 来存储号码:
import java.util.concurrent.ConcurrentLinkedQueue;
class CustomQueue {
private static ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>();
public static void initQueue() {
for (int i = 1000; i > 0; i--) {
queue.add(i);
}
}
public static int pop() {
return queue.poll();
}
}
class ATM implements Runnable {
// ...
public void run() {
// ...
int x = 1;
while (x < 20) {
x = CustomQueue.pop();
synchronized (this) {
// ...
}
}
}
}
public class TestATM {
public static void main(String[] args) {
CustomQueue.initQueue();
// ...
}
}
在修改后的代码中,使用 ConcurrentLinkedQueue 作为号码队列,并在程序启动时调用 CustomQueue.initQueue() 初始化队列。每个线程通过调用 CustomQueue.pop() 方法来获取号码,保证了多线程环境下的安全性。
通过这种方式,你应该能够避免部分号码丢失的问题,并且在多线程环境下正常地进行银行排队取号码的操作
多线程情况下 static Stack s=new Stack(); 这一句可能出现多个对象,这种情况会导致线程不安全。
可以使用加锁,单例等形式来做,
例如做一个单例方法,获取对象直接 Stack.instance();
修改了Stack 和 ATM两个类,修改的地方都有注释。
/**
* 修改为单例模式。
*/
public class Stack {
int size=1000;
int []data;
int top=-1;
private static Stack instance = new Stack();
private Stack(){
this.data=new int[size];
for(int i=1000;i>0;i--){
this.top++;
this.data[this.top]=i;
}
}
public static Stack getInstance(){
return instance;
}
public synchronized int pop(){
int item=this.data[this.top];
this.top--;
return item;
}
}
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
class ATM implements Runnable{
private double []surplus=new double[200];//余额
private int visa;
Map<String,Double> m=new HashMap<>();
String type;
public ATM(String type){
this.type=type;
}
public void fget(){//从文件读取数据
int i=0;
try {
Scanner sc=new Scanner(new File("余额.txt"));
while (sc.hasNextLine()){
surplus[i]=Double.parseDouble(sc.nextLine());
i++;
}
}catch (IOException e){
e.printStackTrace();
}
}
public void link(){//建立键值对
int i=0;
int temp=100;
for(;i<200;i++)
{
m.put(String.valueOf(temp),surplus[i]);
temp++;
}
}
public boolean classify(String account){//是否为跨行
char firstCh=account.charAt(0);
return firstCh == '1';
}
public void serve(String account,double inout,boolean same,int x){//修改余额
double temp;
double commission=2.0;//手续费
if(same){
temp=m.get(account)+inout;
m.put(account,temp);
System.out.println(x+"号 "+account+"***"+" 余额变更"+inout+"元 手续费:0元");
}
else {
temp=m.get(account)+inout-commission;
m.put(account,temp);
System.out.println(x+"号 "+account+"***"+" 余额变更"+inout+"元 手续费:"+commission+"元");
}
}
public void serve(String account,int x,String type){//服务机
System.out.println(x+"号 "+account+"***"+" 选择了"+type);
}
//这里修改了Stack的创建方式。
Stack s= Stack.getInstance();
public void run(){
String []select={"g","p","s"};
int x=1;
while (x<20){
x=s.pop();
synchronized (this){
int num=(int)(100+Math.random()*199);
int temp=(int)(Math.random()*3);//随机选择服务
String account=String.valueOf(num);//随机账户
double inout=Math.random()*100-50;
String middle=String.format("%.2f",inout);//转为两位小数
Custom c;
fget();
link();
inout=Double.parseDouble(middle);
if(inout>=0&&temp!=2){
temp=1;
}
else if(temp!=2){
temp=0;
}
if(classify(account)){
if(inout>m.get(account)){
System.out.println("账户余额不足");
}
}
else {
if(inout>(m.get(account)-2)){
System.out.println("账户余额不足");
}
}
if(select[temp].equals("s")){
c=new Custom(account,select[temp]);
}
else {
c=new Custom(account,select[temp],inout);
}
if(type.equals("服务机")&&c.getCtype().equals("s"))
{
System.out.println(type+"空闲");
System.out.println(type+"正在办理"+x+"号"+c.getAccount()+"***"+"的业务");
serve(c.getAccount(),x,type);
}
else if((!c.getAccount().equals("s"))&&type.equals("存取款机")){
System.out.println(type+"空闲");
System.out.println(type+"正在办理"+x+"号"+c.getAccount()+"***"+"的业务");
boolean same=classify(c.getAccount());
serve(c.getAccount(),c.getInout(),same,x);
}
else if (c.getCtype().equals("p")&&type.equals("存款机")) {
System.out.println(type+"空闲");
System.out.println(type+"正在办理"+x+"号"+c.getAccount()+"***"+"的业务");
boolean same=classify(c.getAccount());
serve(c.getAccount(),c.getInout(),same,x);
}
else if (c.getCtype().equals("g")&&type.equals("取款机")) {
System.out.println(type+"空闲");
System.out.println(type+"正在办理"+x+"号"+c.getAccount()+"***"+"的业务");
boolean same=classify(c.getAccount());
serve(c.getAccount(),c.getInout(),same,x);
}
else {
//这里补充了一段打印。
System.out.println("正在办理"+x+"号"+c.getAccount()+"***"+"的业务,但不匹配,type = " + type + ", c.getCtype() = " + c.getCtype());
continue;
}
try {
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(type+"办理业务已完成");
}
}
}
}
通过加锁的方式,确保线程安全
试试这个
package 排队;
public class Test01 {
public static void main(String[] args){
St st = new St();
Custm[] cus = new Custm[20];
int[] ran = {1,1,1,0,1,0,1,1,1,0,1};
//int[] ran = {1,0,1,1,0,1,0,1,0,1,1};
for(int i=0;i<20;i++){
int a = (int)(Math.random()*10);
cus[i] = new Custm(i+1,ran[a]);
st.add(cus[i]);
}
Worke wort = new Worke(st, "vip", true);//vip窗口,线程1
Thread t1 = new Thread(wort);
t1.start();
Worke wort1 = new Worke(st, "1号" , false);//普通窗口,线程2,
Thread t2 = new Thread(wort1);
t2.start();
Worke wort2 = new Worke(st, "2号" , false);//普通窗口,线程3,
Thread t3 = new Thread(wort2);
t3.start();
Worke wort4 = new Worke(st, "3号" , false);//普通窗口,线程4,
Thread t4 = new Thread(wort4);
t4.start();
}
}
class Custm{
int key;
int num;
public Custm(int n, int k){
num=n;
key=k;
}
}
class base {
}
class Worke implements Runnable{
boolean isvip;
St s;
String ThreadName;
public Worke(St ss,String n ,boolean vip){
s=ss;
ThreadName = n;
isvip = vip;
}
boolean asd=true;
public void run(){
while(asd){
if(s.isEmpty()==false){
if(isvip){
// synchronized(this){
Custm cu = s.dell().cus;
if(cu.key==0){
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口无人,请"+cu.num+"号【vip】顾客准备!");
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【vip】顾客就绪!");
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【vip】顾客正在办理!");
try {
Thread.sleep((int)(Math.random()*500));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【vip】顾客已离开-------!");
// }else{
// try {
// Thread.sleep((int)(Math.random()*500));
// } catch (InterruptedException e) {
// TODO Auto-generated catch block
// e.printStackTrace();
// }
// }
}
}else{
// synchronized(this){
Custm cu = s.dell().cus;
if(cu.key==0){
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口无人,请"+cu.num+"号【vip】顾客准备!");
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【vip】顾客就绪!");
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【vip】顾客正在办理!");
try {
Thread.sleep((int)(Math.random()*500));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【vip】顾客已离开-------!");
}else{
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口无人,请"+cu.num+"号【普通】顾客准备!");
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【普通】顾客就绪!");
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【普通】顾客正在办理!");
try {
Thread.sleep((int)(Math.random()*500));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【普通】顾客已离开-------!");
}
// }
}
}else{
asd=false;
}
}
}
}
class St{
Node head;
Node tail;
public St(){
tail=head=null;
}
public boolean isEmpty(){
if(head==null){
return true;
}
return false;
}
public void add(Custm c){
Node node = new Node(c);
if(isEmpty()){
head=tail=node;
head.front=null;
tail.next=null;
}else{
tail.next=node;
node.front=tail;
tail=node;
tail.next=null;
}
}
public Node dell(){
if(isEmpty()==false){
if(head.next==null){
Node nod=head;
head=null;
return nod;
}else{
Node nod=head;
head=head.next;
head.front=null;
return nod;
}
}else{
System.out.println("队列空了");
return null;
}
}
public Node del(){
if(isEmpty()==false){
Node nod = findvip();
if(nod==null){
Node temp=head;
if(head.next!=null){
Node node = head.next;
node.front=null;
head=node;
}else{
head=null;
}
//head.front=null;
temp.next=null;
return temp;
}else{
if(nod==head){
nod=head;
head=null;
return nod;
}else if(nod==tail){
Node tm = nod;
nod.front.next=null;
return tm;
}else{
nod.front.next=nod.next;
nod.next.front=nod.front;
return nod;
}
}
}else{
System.out.println("队列空了");
return null;
}
}
public Node findvip(){
if(isEmpty()==false){
Node temp = head;
while(temp!=null){
if(temp.cus.key==0){
return temp;
}
temp = temp.next;
}
}
return null;
}
}
class Node{
Custm cus;
Node front;
Node next;
public Node(Custm c){
cus =c;
front = next = null;
}
}
以下答案由GPT-4.0Plus大模型与博主A努力中.......共同编写:
从你的代码和描述来看,号码缺失的情况很有可能是由于多线程操作同一个Stack对象导致的。
具体来说:
你的ATM类中的四个线程都共享同一个Stack对象s,用于弹出下一个号码。
当多个线程同时执行s.pop()操作时,由于Thread schedular的调度,有可能导致某些号码被跳过,出现缺失。
要解决这个问题,有两种方法:
java
ATM atm1=new ATM("服务机");
Stack s1 = new Stack();
atm1.s = s1;
//...
java
public class Stack {
//...
public synchronized int pop(){
//...
}
}
这种方法对所有方法加锁,可能会影响效率。你也可以只对关键方法如pop()加锁。
除此之外,你还可以使用更高级的并发容器如BlockingQueue来实现这个功能,这可能会更高效和简洁。
总而言之,出现这个问题的关键是多线程同时访问和修改同一个共享资源,解决办法就是确保任意时刻只有一个线程在访问该资源。
可以借鉴下
package 排队;
public class Test01 {
public static void main(String[] args){
St st = new St();
Custm[] cus = new Custm[20];
int[] ran = {1,1,1,0,1,0,1,1,1,0,1};
//int[] ran = {1,0,1,1,0,1,0,1,0,1,1};
for(int i=0;i<20;i++){
int a = (int)(Math.random()*10);
cus[i] = new Custm(i+1,ran[a]);
st.add(cus[i]);
}
Worke wort = new Worke(st, "vip", true);//vip窗口,线程1
Thread t1 = new Thread(wort);
t1.start();
Worke wort1 = new Worke(st, "1号" , false);//普通窗口,线程2,
Thread t2 = new Thread(wort1);
t2.start();
Worke wort2 = new Worke(st, "2号" , false);//普通窗口,线程3,
Thread t3 = new Thread(wort2);
t3.start();
Worke wort4 = new Worke(st, "3号" , false);//普通窗口,线程4,
Thread t4 = new Thread(wort4);
t4.start();
}
}
class Custm{
int key;
int num;
public Custm(int n, int k){
num=n;
key=k;
}
}
class base {
}
class Worke implements Runnable{
boolean isvip;
St s;
String ThreadName;
public Worke(St ss,String n ,boolean vip){
s=ss;
ThreadName = n;
isvip = vip;
}
boolean asd=true;
public void run(){
while(asd){
if(s.isEmpty()==false){
if(isvip){
// synchronized(this){
Custm cu = s.dell().cus;
if(cu.key==0){
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口无人,请"+cu.num+"号【vip】顾客准备!");
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【vip】顾客就绪!");
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【vip】顾客正在办理!");
try {
Thread.sleep((int)(Math.random()*500));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【vip】顾客已离开-------!");
// }else{
// try {
// Thread.sleep((int)(Math.random()*500));
// } catch (InterruptedException e) {
// TODO Auto-generated catch block
// e.printStackTrace();
// }
// }
}
}else{
// synchronized(this){
Custm cu = s.dell().cus;
if(cu.key==0){
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口无人,请"+cu.num+"号【vip】顾客准备!");
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【vip】顾客就绪!");
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【vip】顾客正在办理!");
try {
Thread.sleep((int)(Math.random()*500));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【vip】顾客已离开-------!");
}else{
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口无人,请"+cu.num+"号【普通】顾客准备!");
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【普通】顾客就绪!");
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【普通】顾客正在办理!");
try {
Thread.sleep((int)(Math.random()*500));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程id" + Thread.currentThread().getId() +"\t"+ThreadName+"---窗口"+cu.num+"号【普通】顾客已离开-------!");
}
// }
}
}else{
asd=false;
}
}
}
}
class St{
Node head;
Node tail;
public St(){
tail=head=null;
}
public boolean isEmpty(){
if(head==null){
return true;
}
return false;
}
public void add(Custm c){
Node node = new Node(c);
if(isEmpty()){
head=tail=node;
head.front=null;
tail.next=null;
}else{
tail.next=node;
node.front=tail;
tail=node;
tail.next=null;
}
}
public Node dell(){
if(isEmpty()==false){
if(head.next==null){
Node nod=head;
head=null;
return nod;
}else{
Node nod=head;
head=head.next;
head.front=null;
return nod;
}
}else{
System.out.println("队列空了");
return null;
}
}
public Node del(){
if(isEmpty()==false){
Node nod = findvip();
if(nod==null){
Node temp=head;
if(head.next!=null){
Node node = head.next;
node.front=null;
head=node;
}else{
head=null;
}
//head.front=null;
temp.next=null;
return temp;
}else{
if(nod==head){
nod=head;
head=null;
return nod;
}else if(nod==tail){
Node tm = nod;
nod.front.next=null;
return tm;
}else{
nod.front.next=nod.next;
nod.next.front=nod.front;
return nod;
}
}
}else{
System.out.println("队列空了");
return null;
}
}
public Node findvip(){
if(isEmpty()==false){
Node temp = head;
while(temp!=null){
if(temp.cus.key==0){
return temp;
}
temp = temp.next;
}
}
return null;
}
}
class Node{
Custm cus;
Node front;
Node next;
public Node(Custm c){
cus =c;
front = next = null;
}
}
借鉴chatgpt4:
在编写多线程银行排队代码时,使用栈来赋值所取号码可能会出现竞争条件(race condition)的问题。竞争条件可在多个线程同时访问和修改共享变量时发生,导致预期之外的行为。
为了避免这种情况,您可以使用线程安全的数据结构,例如并发集合(concurrency collections)来存储号码。另外一种方法是使用同步机制,例如锁定(locks),以确保只有一个线程可以同时访问和修改共享变量。
重要的是要确保以确定性的方式为号码赋值,以避免序列中出现缺失或重复的情况。您可以通过使用原子计数器(atomic counter)或同步块(synchronized block)来增加和分配号码,从而实现此目的。