在JavaSE中,多线程是一个重要的内容。

我们要了解多线程的概念,就要先了解进程的概念;要了解进程的概念,就离不开操作系统的概念。

在一台正常运行的电脑中,计算机硬件(如CPU、内存、硬盘、网卡、显示器、键盘、鼠标等)提供了基础的硬件环境;在硬件之上,是操作系统,这是系统级的软件程序,用来管理计算机的各项软件资源,是人机交互中的关键环节;在操作系统之上,则是各类应用软件程序,比如QQ、微信、浏览器等。

进程是一个动态的概念,程序则是一个静态的概念。程序在操作系统中运行起来时,需要操作系统为其分配相应的内存、CPU等资源,成为进程。不同的进程,独享各自的内存、CPU等资源。这也是进程安全性所不可缺少的。所以,进程是操作系统分配CPU及各类系统资源的基本单位。

当进程运行起来时,通常会有多个子任务,以游戏为例,里面需要绘制游戏面板,如果游戏里有多个角色的人物进行并发活动,还需要能够获得相应的CPU资源,从而取得CPU的执行权,以进入运行状态。同时,在同一个游戏中,每个角色的有关状态数据,可以为该游戏中的其他角色所获取。由此可见,一个进程中可以有多个子任务,这些子任务需要并发轮流获取CPU的执行权,同时可以共享进程的内存资源。进程中的这些子任务就被称为线程,多个子任务就是多个线程。线程是进程中分配CPU资源的基本单位,线程不可以脱离进程而存在。

Java语言本身就是一种多线程语言。比如,在JavaSE的一个Java工程中,JVM通过main入口函数运行起来时,同时还会有GC(垃圾回收器)运行起来,以等待回收Java堆内存中变为垃圾的对象,以节省堆内存空间。

在做完上述知识铺垫和准备后,我接下来通过一个赛跑游戏,来为大家演示Java多线程的运用。

在这里,我先将该赛跑游戏的运行效果予以演示:

每次登录该游戏,会弹出一个提示框,告知用户是第几次登录,并有温馨提示。

点击“确定”或关闭该提示框,则进入游戏界面。该界面分为上下两部分,上半部分为游戏跑道,4个赛手,共4个跑道;下半部分为游戏控制面板,提示用户选择赛手,输入押注金额,以及点击“开始”按钮开始赛跑,还有“重新开始”按钮以回到比赛准备的状态等。

点击“开始”按钮后,进入赛跑游戏,游戏面板中的4个赛手开始奔跑,对于先跑到终点的赛手,会弹出提示框予以庆祝;如果用户押注失败,则用户本金会予以相应减少;如果用户押注成功,则用户本金会予以相应增加。

好了,演示完上述游戏后,接下来谈下开发思路。

首先,完成游戏前端部分。先绘制一个游戏窗体,窗体上添加面板,面板上添加各类所需的组件。

其次,游戏前端部分完成后,根据面向对象的思想,将赛手抽象为一个类,将与赛手有关的各种属性与功能均由该类实现;同时,主线程负责绘制游戏前端,各个赛手要运行时,则通过开辟子线程来实现。

再次,当赛跑游戏结束时,根据产生的第一名,刷新本金,弹出庆祝框,同时提示用户是成功或者失败;此外,将用户的本金与登录次数存入一个属性文件,用户本金与登录次数的变化会实时更新到属性文件中。

Step 1

请看游戏前端代码:

package runningGame;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Properties;
import java.util.ResourceBundle; import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField; /**
* 游戏 面板设计
* @author 李章勇
* 常量分析:
* 4个赛手
* 赛手编号
* 胜利感言
* 跑道面板
* 4个跑道,每个跑道一张背景图
* 每个跑道上各有一个准备状态的赛手
* 终点线
* 控制面板
* 4个单选按钮[表示赛手】
* 提示选择赛手、输入押注,押注输入框
* 我的本金提示
* 开始按钮,重新开始按钮
* 多个子线程共用同一段代码,赛手类
*/
public class GameJFrame extends JFrame{
//赛道背景面板
GamePanel jp_game=new GamePanel();
//4张跑道背景图
Image[] img_back=new Image[StaticIntString.NUM];
//每个跑道上处于准备状态的小人
Image[] img_ready=new Image[StaticIntString.NUM];
//每个跑道上奔跑中的小人
Image[] img_run=new Image[StaticIntString.NUM]; //游戏控制面板
JPanel jp_control=new JPanel();
//4个单选按钮
static JRadioButton[] jrb_mans=new JRadioButton[StaticIntString.NUM];
//按钮组
ButtonGroup bg=new ButtonGroup();
//提示选择姓名并输入金额
JLabel jl_inputTips=new JLabel(StaticIntString.INPUT_TIPS1);
//输入押注金额
JTextField jtf_cost=new JTextField(10);
JLabel jl_winFailTips=new JLabel("本次输赢:");
JLabel jl_winFail=new JLabel(StaticIntString.WIN_FAIL);
//本金提示
JLabel jl_ownMoneyTips=new JLabel("我的本金:");
static Properties pro=new Properties();
//从属性文件里读取本金
static ResourceBundle resource=ResourceBundle.getBundle("runningGame.infos");
String ownMoney=resource.getString("ownMoney");
BigInteger bi_times=new BigInteger(resource.getString("times"));
static BigInteger bi_bankruptcy=new BigInteger(resource.getString("bankruptcy"));
String ownMoney_System=resource.getString("ownMoney_System");
//本金
JLabel jl_ownMoney=new JLabel(ownMoney);
//开始按钮
JButton jbt_start=new JButton("开始");
//重新开始按钮
JButton jbt_restart=new JButton("重新开始");
static String selectedName;
RunningMan[] mans=new RunningMan[StaticIntString.NUM]; public GameJFrame() {
if(bi_times.toString().equals("1")){
JOptionPane.showMessageDialog(this, "首次登陆,欢迎光临!\n系统赠予您10000元,\n祝您玩得开心愉快!\n只是玩玩,不要沉溺赌博哦");
}else{
JOptionPane.showMessageDialog(this, "您这是第 "+bi_times.toString()+" 次登陆,欢迎光临!\n祝您玩得开心愉快!\n只是玩玩,不要沉溺赌博哦");
}
bi_times=bi_times.add(new BigInteger("1"));
try {
BufferedInputStream fis=new BufferedInputStream(new FileInputStream("src//runningGame//infos.properties"));
BufferedOutputStream fos=new BufferedOutputStream(new FileOutputStream("src//runningGame//infos.properties"));
String ownMoney_System=resource.getString("ownMoney_System");;
try {
pro.load(fis);
} catch (IOException e1) {
e1.printStackTrace();
}
pro.setProperty("times", bi_times.toString());
pro.setProperty("ownMoney", ownMoney);
pro.setProperty("bankruptcy", bi_bankruptcy.toString());
pro.setProperty("ownMoney_System", ownMoney_System);
try {
pro.store(fos, "");
fis.close();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} for(int i=0;i<StaticIntString.NUM;i++){
img_back[i]=Toolkit.getDefaultToolkit().createImage("imgBack//"+(i+1)+".jpg");
img_ready[i]=Toolkit.getDefaultToolkit().createImage("imgReady//"+(i+1)+"2.gif");
img_run[i]=Toolkit.getDefaultToolkit().createImage("imgRun//"+(i+1)+"1.gif");
jrb_mans[i]=new JRadioButton(StaticIntString.NAMES[i]);
bg.add(jrb_mans[i]);
jp_control.add(jrb_mans[i]);
}
jbt_start.addActionListener(new ActionListener(){
boolean isSelected=false;
public boolean judgeSelected(){
for(JRadioButton j:jrb_mans){
if(j.isSelected()){
isSelected=true;
}
}
return isSelected;
} @Override
public void actionPerformed(ActionEvent arg0) {
if(!judgeSelected()){
JOptionPane.showMessageDialog(GameJFrame.this, "您还没选人呢",
"看着点", JOptionPane.WARNING_MESSAGE);
return;
}
if(jtf_cost.getText().trim().length()==0){
JOptionPane.showMessageDialog(GameJFrame.this, "您还没下注呢",
"看着点", JOptionPane.WARNING_MESSAGE);
return;
}
char[] cs=jtf_cost.getText().toCharArray();
for(int i=0;i<cs.length;i++){
if(cs[i]<'0' || cs[i]>'9'){
JOptionPane.showMessageDialog(GameJFrame.this, "您输入的不是数字",
"看着点", JOptionPane.WARNING_MESSAGE);
jtf_cost.setText("");
return;
}
if(cs[0]=='0'){
JOptionPane.showMessageDialog(GameJFrame.this, "数字首位不能是0",
"看着点", JOptionPane.WARNING_MESSAGE);
jtf_cost.setText("");
return;
}
}
BigInteger bi_jtf_cost=new BigInteger(jtf_cost.getText());
BigInteger bi_jl_ownMoney=new BigInteger(jl_ownMoney.getText()); if((bi_jtf_cost.compareTo(bi_jl_ownMoney))>0){
JOptionPane.showMessageDialog(GameJFrame.this, "您的下注金额不能超过本金哦",
"看着点", JOptionPane.WARNING_MESSAGE);
jtf_cost.setText("");
return;
} for(JRadioButton j:jrb_mans){
if(j.isSelected()){
selectedName=j.getText();
break;
}
}
for(JRadioButton j:jrb_mans){
j.setEnabled(false);
} jtf_cost.setEditable(false);
jl_inputTips.setText(StaticIntString.INPUT_TIPS2);
jbt_restart.setEnabled(true);
jbt_start.setEnabled(false);
RunningMan.isEnd=false;
RunningMan.canRun=true;
RunningMan.num=0;
for(int i=0;i<StaticIntString.NUM;i++){
mans[i].img=img_run[i];
mans[i].start();
}
}
});
jbt_restart.addActionListener(new ActionListener(){ @Override
public void actionPerformed(ActionEvent arg0) { for(int i=0;i<StaticIntString.NUM;i++){
jrb_mans[i].setText(StaticIntString.NAMES[i]);
jrb_mans[i].setEnabled(true);
}
jl_inputTips.setText(StaticIntString.INPUT_TIPS1);
jtf_cost.setText("");
jtf_cost.setEditable(true);
jl_winFail.setForeground(Color.BLACK);
jl_winFail.setText(StaticIntString.WIN_FAIL);
jbt_start.setEnabled(true);
jbt_restart.setEnabled(false);
RunningMan.isEnd=true;
RunningMan.canRun=false;
bg.clearSelection();
for(int i=0;i<StaticIntString.NUM;i++){
mans[i]=new RunningMan(StaticIntString.NAMES[i], 0, (jp_game.getHeight()*i)/4+30, img_ready[i], GameJFrame.this);
}
}
}); jp_control.setBorder(BorderFactory.createTitledBorder("游戏控制系统"));
jp_control.add(jl_inputTips);
jp_control.add(jtf_cost);
jp_control.add(jl_winFailTips);
jp_control.add(jl_winFail);
jp_control.add(jl_ownMoneyTips);
jp_control.add(jl_ownMoney);
jp_control.add(jbt_start);
jp_control.add(jbt_restart);
this.add(jp_game,BorderLayout.CENTER);
this.add(jp_control, BorderLayout.SOUTH);
setResizable(false);
setTitle("赛跑游戏 ---作者:李章勇");
setBounds(200, 100, 1000, 600);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
for(int i=0;i<StaticIntString.NUM;i++){
mans[i]=new RunningMan(StaticIntString.NAMES[i], 0, (jp_game.getHeight()*i)/4+30, img_ready[i], this);
}
} class GamePanel extends JPanel{
@Override
public void paint(Graphics g) {
super.paint(g);
for(int i=0;i<StaticIntString.NUM;i++){
g.drawImage(img_back[i], 0, (this.getHeight()*i)/4,this.getWidth(),this.getHeight()/4+30,this);
mans[i].drawSelf(g, this); }
g.setColor(Color.RED);
g.fillRect(850, 0, 5, this.getHeight());
}
} public static void main(String[] args) {
new GameJFrame();
}
}

Step 2

将赛手抽象为一个类,同时创建一个接口工具,存储常数,便于对数据的统一管理;再创建一个名为“infos.properties”的属性文件,将用户本金、登录次数等信息存储到属性文件里,以备更新。

package runningGame;

public interface StaticIntString {
public static final int NUM=4;
public static final String[] NAMES={ "1号选手", "2号选手", "3号选手", "4号选手" };
public static final String[] WINS={ "预测未来的最好方法,就是创造未来.", "人生重要的不是所站的位置,而是所朝的方向.",
"如你想要拥有完美无暇的友谊,可能一辈子找不到兄弟姐妹","正因年轻咱们一无所有,也正正因年轻咱们将拥有一切." };
public static final String INPUT_TIPS1="请选择赛手并输入金额:";
public static final String INPUT_TIPS2="本次下注金额:";
public static final String GET_WIN="[获胜]";
public static final String WIN_FAIL="[Win/Fail]";
public static final String WIN_STATE="[Win]";
public static final String FAIL_STATE="[Fail]";
} 
属性文件infos.properties: 
#破产次数,登录次数,系统默认赠予的本金,用户的实际本金;只要用户本金小于等于0,系统会自动为用户赠予初始本金。
#Fri Dec 01 09:34:34 CST 2017
bankruptcy=0
times=1
ownMoney_System=10000
ownMoney=10000  
赛手类RunningMan: 
package runningGame;

import java.awt.Graphics;
import java.awt.Image;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.Random; import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton; /**
* 赛手的属性:姓名,坐标(x/y),自身图像,准备状态的图像,关联的窗体
* 位移所需的随机数Random,
* 第一名NO1
* 是否能跑的状态canRun
* 比赛是否结束isEnd
* 每个赛手跑完时计数num
*
* 赛手画自己的方法drawSelf
* 赛手跑步位移的方法move
* 子线程运行的run方法
*
* @author 李章勇
*
*/
public class RunningMan extends Thread {
public String name;
private int x_point,y_poinit;
public Image img;
private Image imgReady;
private GameJFrame jf_game;
static Random rand=new Random();
static RunningMan NO1;
static boolean canRun=true;
static boolean isEnd=false;
static int num=0;
static BigInteger bi_ownMoney; public RunningMan(String name,int x_point,int y_point,Image img,GameJFrame jf_game) {
this.name=name;
this.x_point=x_point;
this.y_poinit=y_point;
this.img=img;
this.imgReady=img;
this.jf_game=jf_game;
} public void setName2(String name){
this.name=name;
}
public String getName2(){
return name;
} public void drawSelf(Graphics g,JPanel jp){
g.drawImage(img, x_point, y_poinit, jp);
} public void move(){
x_point+=10+rand.nextInt(30);
} @Override
public void run() {
while(x_point<=850 && canRun){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
move();
}
synchronized(String.class){
if(canRun){
x_point=850;
img=imgReady;
num++;
}
if(!isEnd){
NO1=this;
isEnd=true;
}
if(num==4){
System.out.println("本次比赛第一名:"+NO1.name);
//根据比赛结果,修改金额 类
bi_ownMoney=new GetResult(jf_game).getRightMoney();
new WinDialog();
try {
BufferedInputStream fis = new BufferedInputStream(new FileInputStream("src//runningGame//infos.properties"));
BufferedOutputStream fos=new BufferedOutputStream(new FileOutputStream("src//runningGame//infos.properties"));
try {
GameJFrame.pro.load(fis);
if((bi_ownMoney.compareTo(new BigInteger("0"))<=0)){
JOptionPane.showMessageDialog(jf_game, "您已没有本金,\n系统不差钱儿,再次赠予您 10000 元!\n祝您玩得开心哦!",
"看着点", JOptionPane.WARNING_MESSAGE);
jf_game.jl_ownMoney.setText(GameJFrame.resource.getString("ownMoney_System"));
GameJFrame.pro.setProperty("ownMoney", GameJFrame.resource.getString("ownMoney_System"));
BigInteger bi_bankruptcy=new BigInteger(GameJFrame.resource.getString("bankruptcy")).add(new BigInteger("1"));
GameJFrame.pro.setProperty("bankruptcy", bi_bankruptcy.toString()); }else{
jf_game.jl_ownMoney.setText(bi_ownMoney.toString());
GameJFrame.pro.setProperty("ownMoney", bi_ownMoney.toString());
}
GameJFrame.pro.store(fos, "");
fis.close();
fos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
for(JRadioButton j:GameJFrame.jrb_mans){
if(NO1.name.equals(j.getText())){
j.setText(j.getText()+StaticIntString.GET_WIN);
}
}
}
}
}
}

 刷新用户本金的类: 

package runningGame;

import java.awt.Color;
import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger; import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JRadioButton;
import javax.swing.JTextField; /**
* 需要的属性
* 代表四个赛手的按钮
* 输入的金额
* 输赢状态
* 本金
* 关联的窗体
* 获得比赛后、发生变化的钱数getRightMoney
* 设置本金新的数值setNewMoney
* @author 李章勇
*
*/
public class GetResult {
private JRadioButton[] jrb_mans;
private JTextField jtf_cost;
private JLabel jl_winFail;
private JLabel jl_ownMoney; public GetResult(GameJFrame jf_game) {
this.jrb_mans=jf_game.jrb_mans;
this.jtf_cost=jf_game.jtf_cost;
this.jl_winFail=jf_game.jl_winFail;
this.jl_ownMoney=jf_game.jl_ownMoney;
} public BigInteger getRightMoney(){
BigInteger bi_ownMoney=new BigInteger(jl_ownMoney.getText());
BigInteger bi_cost=new BigInteger(jtf_cost.getText());
if(GameJFrame.selectedName!=null && GameJFrame.selectedName.equals(RunningMan.NO1.name)){
jl_winFail.setForeground(Color.GREEN);
jl_winFail.setText(StaticIntString.WIN_STATE);
bi_ownMoney=bi_ownMoney.add(bi_cost);
jl_ownMoney.setText(bi_ownMoney.toString());
}else{
jl_winFail.setForeground(Color.RED);
jl_winFail.setText(StaticIntString.FAIL_STATE);
bi_ownMoney=bi_ownMoney.subtract(bi_cost);
jl_ownMoney.setText(bi_ownMoney.toString());
}
return bi_ownMoney;
}
}

  弹出庆祝框的类WinDialog:

package runningGame;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.util.Random; import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* 对话框背景图
* 4个赛手获胜时的图片
* 自定义面板
* 随机数,随机获取胜利宣言
* @author 李章勇
*
*/
public class WinDialog extends JDialog{
Image img_back=Toolkit.getDefaultToolkit().createImage("imgWin//winbg.jpg");
Image[] img_wins=new Image[StaticIntString.NUM];
WinJPanel wp=new WinJPanel();
Random rand=new Random();
int num=0; public WinDialog() {
for(int i=0;i<img_wins.length;i++){
img_wins[i]=Toolkit.getDefaultToolkit().createImage("imgWin//"+(i+1)+"3.gif");
}
wp.setBounds(100, 100, 400, 300);
add(wp);
setTitle("胜利墙");
setBounds(300, 300, 400, 300);
setResizable(false);
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
setVisible(true);
num=rand.nextInt(4);
} class WinJPanel extends JPanel{ @Override
public void paint(Graphics g) {
super.paint(g);
g.drawImage(img_back, 0, 0, this.getWidth(), this.getHeight(), this); switch(RunningMan.NO1.name){
case "1号选手":
g.drawImage(img_wins[0], 50, 100, this);
break;
case "2号选手":
g.drawImage(img_wins[1], 50, 100, this);
break;
case "3号选手":
g.drawImage(img_wins[2], 50, 100, this);
break;
case "4号选手":
g.drawImage(img_wins[3], 50, 100, this);
break;
default:
break;
}
g.setColor(Color.RED);
g.drawString("本次获胜选手:"+RunningMan.NO1.name, 20, 30);
g.drawString(StaticIntString.WINS[num], 20, 50);
}
}
}

  到此,代码部分就结束了。至于代码中用到的图片,读者参考下述对图片的命名规则,收集相应的图片即可。

  到此,整个单机赛跑游戏是完成了。然而,目前在运行时,我们需要在Eclipse里运行项目才可以打开游戏,显然这种方式并不符合普通用户。为此,我们来看如何把项目打包成jar包后,再生成exe可执行文件,这样的话,用户只要双击运行该exe可执行文件,就可以打开游戏。

  在此,介绍一款将jar文件生成exe文件时用到的第三方软件:exe4j(下载地址:http://www.ej-technologies.com/download/exe4j/files):

  至于接下来应该怎样把jar包生成exe文件,有一篇博客《将java项目打包成jar包并生成可独立执行的exe文件》中已经介绍得很详细,建议读者参考。

  现在,一个完整的exe版单机赛跑游戏就制作好了。

最新文章

  1. javascript性能优化:创建javascript无阻塞脚本
  2. Bug库
  3. msql,触发器无事物回滚,插入之前满足条件再插入
  4. JavaScript语言精粹笔记
  5. js获取url参数值(HTML之间传值)
  6. JS中用execCommand(&quot;SaveAs&quot;)保存页面兼容性问题解决方案
  7. 类名.class与类名.this详解
  8. [Linux发行版] 常见Linux系统下载
  9. js获取计算的样式(非行间样式)
  10. 基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)
  11. UWP: ListView 中与滚动有关的两个需求的实现
  12. Eviews 9.0新功能——估计方法(ARDL、面板自回归、门限回归)
  13. X1226 和 AT24C16 地址冲突问题
  14. 扩展方法、委托和Lambda
  15. Android手机有的不显示Toast
  16. spring boot 在不同环境下读取不同配置文件的一种方式
  17. 02-body标签中相关标签
  18. 前端ajax传数据成功发送,但后端接收不到
  19. NodeJS设置Header解决跨域问题
  20. 关于dojo自定义类

热门文章

  1. get和post请求及函数调用模式
  2. CYQ.Data 正式支持 DotNET Core 版本发布
  3. devdependencies与dependencies的区别
  4. Android Studio问题汇总
  5. 深入理解javascript函数进阶系列第一篇——高阶函数
  6. OAuth 2.0(网转)
  7. 代码管理 ,git 命令整理
  8. NGUI_概述
  9. JAVA基础-----Maven项目的搭建
  10. Socket TCP Server一个端口可以有多少个长连接?受到什么影响?linux最大文件句柄数量总结