前言:
最近项目中需要用到websocket进行通信,boss想要在接受消息时增加json串的匹配验证,需要判断收到的消息的“{”“}”进行匹配,使用json传递消息,并经过Base64加密处理。
因为是多线程处理的websocket消息,因此存放匹配的字符时,使用的StringBuffer.
实例
项目代码类MessageServer继承WebSocketServer,实现onMessage(WebSocket conn, String message)方法。
声明全局变量
private AtomicInteger count= new AtomicInteger(0); //记录字符串中"{""}"个数,左加右减
StringBuffer msg = new StringBuffer(); //存放匹配的字符
StringBuffer websocketId=new StringBuffer(); //存放上次消息处理的websocket标识
具体实现
//前一次解析结果部为完整json,且此次消息不为同一websocket通道传来的消息
if (!msg.toString().isEmpty() && !websocketId.toString().equals(conn.toString())) {
//释放临时存放msg,忽略上次不完整json
msg.delete(0, msg.length());
count=new AtomicInteger(0);
}
msg为内存中存放的已解析过的json消息,若msg不为空,表明上次解析的json串不完整,若上次保存websocketId与此次最新websocket标识不一致,则是两个不同websocket通道传来消息,不完整json串忽略。释放msg,清楚计数器count。
解析过程
websocketId.delete(0, websocketId.length());
websocketId.append(conn.toString());
for (int i=0; i < tmpMsg.length();i++) {
c=tmpMsg.charAt(i);
msg.append(String.valueOf(c));
if (c == '{') {
count.getAndIncrement();
} else if (c == '}') {
count.getAndDecrement();
//如果count=0,表示收到的msg为完整json串,可进行下一步操作。否则,需暂时保存此串,待收到下一个字符串进行拼接
if (count.get()==0) {
//括号配对后再次解析json格式
try {
//成功解析的,进行下一步操作。
//构建WebsocketMessage
WebsocketMessage websocketMessage =
new WebsocketMessage(conn.toString(), msg.toString(), conn.getRemoteSocketAddress().getAddress().getHostAddress());
msgQueue.put(websocketMessage);
//释放临时存放msg
msg.delete(0, msg.length());
} else if (count.get() < 0) {
//收到的json只包含后半部分,以后无法匹配出正确完整json,丢弃
Log4jUtil.info("-----------------------"+websocketId.toString()+" onMessage(): received msg error------------------------" + msg.toString());
msg.delete(0, msg.length());
count=new AtomicInteger(0);
return;
} else {
//收到的json为前半部分,等待下次匹配,此处只输出log
if (i == tmpMsg.length()-1) {
Log4jUtil.info("-----------------------"+websocketId.toString()+" onMessage(): half msg------------------------" + msg.toString());
}
}
} else {
if (i == tmpMsg.length()-1) {
//需解析字符串最后一个字符且不为“}”
if (count.get() > 0) {
//收到的json为前半部分,等待下次匹配,此处只输出log
Log4jUtil.info("-----------------------"+websocketId.toString()+" onMessage(): half msg------------------------" + msg.toString());
} else {
//收到json串有误,忽略
Log4jUtil.info("-----------------------"+websocketId.toString()+" onMessage(): received msg error------------------------" + msg.toString());
msg.delete(0, msg.length());
count=new AtomicInteger(0);
}
}
}
}
线程安全变量
String、StringBuilder、StringBuffer比较
1.执行速度 StringBuilder > StringBuffer > String
原因:
String:字符串常量
StringBuilder:字符串变量
StringBuffer:字符串变量
String是字符串常量解释:
String中使用字符数组保存字符串,如下修饰符final表明不可更改。
private final char value[];
1 String s = "abcd";
2 s = s+1;
上述代码,JVM解析过程:首先创建对象s,赋值abcd。然后再创建新的对象s,执行第二行代码。并没有改变第一个对象。
每当用Strng操作字符串时,实际上是不断创建新的对象,原来的对象变为垃圾被GC回收,效率低。
StringBuilder与StringBuffer都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,如下就是,可知这两种对象都是可变的。
char[] value;
线程安全
String: 对象不可变,常量,线程安全。
StringBuffer:线程安全,对方法加了同步锁或调用的方法加了同步锁。
1 public synchronized StringBuffer reverse() {
2 super.reverse();
3 return this;
4 }
5
6 public int indexOf(String str) {
7 return indexOf(str, 0); //存在 public synchronized int indexOf(String str, int fromIndex) 方法
8 }
StringBuffer 上的主要操作是 append 和 insert 方法,可重载这些方法,以接受任意类型的数据。每个方法都能有效地将给定的数据转换成字符串,然后将该字符串的字符追加或插入到字符串缓冲区中。append 方法始终将这些字符添加到缓冲区的末端;而 insert 方法则在指定的点添加字符。
例如,如果 z 引用一个当前内容是“start”的字符串缓冲区对象,则此方法调用 z.append(“le”) 会使字符串缓冲区包含“startle”,而 z.insert(4, “le”) 将更改字符串缓冲区,使之包含“starlet”。
StringBuilder:非线程安全。方法未加同步锁。
在多线程环境下,需要使用StringBuffer保证线程安全。单线程环境下,可使用StringBuilder,速度快。
使用的总结: 1.如果要操作少量的数据用 String
2.单线程环境操作大量数据 StringBuilder
3.多线程环境操作大量数据 StringBuffer
AtomicInteger
AutomicInteger,一个提供原子操作的Integer的类。
基本工作原理是使用了同步synchronized的方法实现了对一个long, integer, 对象的增、减、赋值(更新)操作. 比如对于++运算符AtomicInteger可以将它持有的integer 能够atomic 地递增。在需要访问两个或两个以上 atomic变量的程序代码(或者是对单一的atomic变量执行两个或两个以上的操作)通常都需要被synchronize以便两者的操作能够被当作是一个atomic的单元。
在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则通过一种线程安全的加减操作接口。
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile int value;
这里, unsafe是java提供的获得对对象内存地址访问的类,注释已经清楚的写出了,它的作用就是在更新操作时提供“比较并替换”的作用。实际上就是AtomicInteger中的一个工具。
valueOffset是用来记录value本身在内存的便宜地址的,这个记录,也主要是为了在更新操作在内存中找到value的位置,方便比较。使用volatile将使得VM优化失去作用,导致效率较低,所以要在必要的时候使用,因此AtomicInteger类不要随意使用。
注意:value是用来存储整数的时间变量,这里被声明为volatile,就是为了保证在更新操作时,当前线程可以拿到value最新的值(并发环境下,value可能已经被其他线程更新了)
自增
/**
* Atomically increments by one the current value.
*
* @return the updated value
*/
public final int incrementAndGet() {
for (;;) {
//这里可以拿到value的最新值
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final boolean compareAndSet(int expect, int update) {
//使用unsafe的native方法,实现高效的硬件级别CAS
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}