暑假小项目:用网页前后台来实现生产者消费者,前台用于显示,后台用于实现算法。
其中涵盖的技术有 jquery、nodejs、java、mysql
话不多说,直接上学习过程。
step one 生产者消费者模型
并发编程把程序划分为多个独立的子任务,通过多线程来驱动。每个线程有创建、就绪、运行、阻塞、死亡五种状态。wait()方法使线程等待,进入阻塞状态。notify()方法唤醒相应的一个线程,使之进入运行状态。notifyall()方法唤醒所有在等待运行的线程。synchronized是同步关键词,当该词修饰的线程在运行时,不允许其他线程运行。thread()类和runnable()接口都是多线程编程的主要方法,主要区别在于在thread()类中类只能继承一个父类,而在runnable()接口中一个类可以继承多个接口。
而生产者消费者模型实质就是生产者生产商品,并存入缓存仓库,消费者从缓存仓库中消费商品,因此每次生产商品时需要创建一个生产线程,消费时创建一个消费线程,再根据缓存仓库的容量,对生产和消费行为进行一定的约束。
step two 结合数据库mysql
网站运行的持久化需要数据库支持,这里运用了关系型数据库mysql。创建过程不再叙述,数据库主键为turn,表明线程的执行顺序,并且修饰了auto_increment自增关键词(因此在清空数据库数据时应该使用truncate而不是delete)。id表示每个线程的属性(为了弄清楚线程运行的不确定性,笔者给每个线程增加了id属性)。num表示该线程生产或消费商品的数量。sum表示执行完该线程后仓库商品的剩余量。效果如下图:
接下来贴出算法代码,简洁起见,只贴出生产者代码。
class Godown {
public static final int max_size = 100; //最大库存量
public int curnum; //当前库存量
Godown() {
}
Godown(int curnum) {
Connection conn = null;
try {
Class.forName("org.gjt.mm.mysql.Driver");
} catch (ClassNotFoundException e) {
System.out.println("无法加载驱动");
e.printStackTrace();
}
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/procons?characterEncoding=utf8&useSSL=false","root","123456");
} catch (SQLException e) {
System.out.println("无法连接数据库");
e.printStackTrace();
}
try {
Statement stmt = conn.createStatement();
stmt.executeUpdate("insert into storage (id,num,sum) values (0,0," + curnum + ")");
stmt.close();
conn.close();
} catch (SQLException e) {
System.out.println("无法插入初始化数据");
e.printStackTrace();
}
System.out.println(0 + ".目前库存为:" + curnum);
this.curnum = curnum;
}
public synchronized void produce(int id, int neednum) {
//测试是否需要生产
while (neednum + curnum > max_size) {
System.out.println("要生产的产品数量" + neednum + "超过剩余库存量" + (max_size - curnum) + ",暂时不能执行生产任务!");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//满足生产条件,则进行生产,这里简单的更改当前库存量
curnum += neednum;
Connection conn = null;
try {
Class.forName("org.gjt.mm.mysql.Driver");
} catch (ClassNotFoundException e) {
System.out.println("无法加载驱动");
e.printStackTrace();
}
try {
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/procons?characterEncoding=utf8&useSSL=false","root","123456");
} catch (SQLException e) {
System.out.println("无法连接数据库");
e.printStackTrace();
}
try {
Statement stmt = conn.createStatement();
stmt.executeUpdate("insert into storage(id,num,sum) values("+ id +","+ neednum +","+ curnum +")");
stmt.close();
conn.close();
} catch (SQLException e) {
System.out.println("无法插入生产数据");
}
System.out.println(id + ".已经生产了" + neednum + "个产品,现仓储量为" + curnum);
//唤醒在此对象监视器上等待的所有线程
notifyAll();
}
//消费
public synchronized void consume(int id, int neednum) {……}
}
class Producer extends Thread {
private int id;
private int neednum; //生产产品的数量
private Godown godown; //仓库
Producer(int id, int neednum, Godown godown) {
this.id = id;
this.neednum = neednum;
this.godown = godown;
}
public void run() {
//生产指定数量的产品
godown.produce(id,neednum);
}
}
step three 写html页面
页面写的比较粗劣,能看就行…
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<script src="jquery-3.1.0.js" type="text/javascript"></script>
<script src="jquery.js" type="text/javascript"></script>
</head>
<body>
<p><input type="submit" id="sub" value="显示" /><input type="button" id="but" value="清空"></p>
<div id="a1"></div>
<div id="a2"></div>
<div id="a3"></div>
<div id="a4"></div>
</body>
</html>
step four 搭建nodejs服务器
搭建nodejs服务器是最后一步,我的想法是触发网页上click事件,通过使用mysql模块读取后台数据库的数据并显示到网页上。mysql模块比较简单,通过建立一个connection,使用connect()、query()、end()等方法便能使用。而页面与服务器的交互,得到学长的建议,我使用了websocket。websocket是html5的新协议,实现了浏览器和服务器的全双工通信。首先通过已封装好的握手协议,实现浏览器的请求和服务器的回应,涉及http协议。握手成功后有许多操作,比如我写在前端的jquery部分,有onopen、onerror、onmessage(接收服务器传过来的数据)、onclose, node端有on(接收浏览器数据并处理)。
如下为nodejs代码:
var express = require('express');
var mysql = require('mysql');
var ws = require('ws').Server;
var app = express();
var data = new Array();
app.use(express.static(__dirname + '/public'));
app.set('port', process.env.PORT || 3000);
//数据库连接
var connection = mysql.createConnection({
host:'localhost',
user:'root',
password:'123456'
});
var server = new ws({host:"127.0.0.1",port:3000});
server.on('connection',function(ws) {
console.log('new connection founded successfully');
//接收数据并处理
ws.on('message',function(e) {
if (e==1) {
connection.connect();
connection.query('use procons');
connection.query('select * from storage',function selectCb(err,results,fields){
if (err) {
throw err;
}
if (results) {
for (var i = 0; i < results.length; i++) {
data[i] = results[i].turn+' '+results[i].id+' '+results[i].num+' '+results[i].sum;
ws.send(data[i]);
}
}
});
connection.end();
}
/* if (e==2) {
connection.connect();
connection.query('use procons');
connection.query('truncate table storage',function(err,result){
if (err) {
throw err;
}
if (result) {
ws.send(null);
}
});
connection.end();
}
*/
});
});
console.log('websocket-server running...');
app.listen(app.get('port'), function(){
console.log( 'Express started on http://localhost:' + app.get('port') + '; press Ctrl-C to terminate.' );
});
以及jquery文件:
var bool;
var ws = new WebSocket('ws://127.0.0.1:3000');
ws.onopen = function() {
$("#a1").append("<p>连接建立</p>");
}
ws.onerror = function() {
$("#a3").append("<p>连接错误</p>");
}
//接收从服务器发来的数据
ws.onmessage = function(e) {
$("#a2").append('<p>'+e.data+'</p>');
}
ws.onclose = function() {
$("#a4").append("<p>连接关闭</p>");
}
$(function() {
$("#sub").click(function() {
$("#a2").append("<p>turn id num sum</p>");
bool = 1;
ws.send(bool);
});
$("#but").click(function() {
$("#a2").remove();
})
})
网页上的显示结果为:
至此,一个简单的前后端交互的生产者消费者模型就完成了。粗劣地完成后,笔者就卸甲归家了,还有很多细节和功能都有待完善,之后再改善了,不过这也是笔者第一次独立完成的小项目,意义非凡。