- 效果图
- 场景描述:
多台传感器连接至服务端时,保存每台传感器最初的登录顺序和 socket 信息,然后根据登录顺序进行页面排序,当设备掉线或者主动断开时移除 socket 连接信息
- 代码设计
1. 创建一个 GlobalCommonUtil 的工具类 存放全局静态集合
//存放设备连接信息 eg: mac 登录状态 初始登录顺序等
public static List<TcpObject> list = new LinkedList<TcpObject>();
//存放设备初始登录顺序(累计排序)
public static List<FileObject> fileInfo = new LinkedList<FileObject>();
//存放 socket 连接对象,mac 为key 此处为线程安全的 map 集合
public static Map<String,Socket> map = new ConcurrentHashMap<String, Socket>();
2. 创建线程通信类 SocketThread,初始化 ServerSocket 和 Socket 对象
public class SocketThread extends Thread{
ServerSocket server;
Socket client;
public SocketThread(Socket socket){
this.client = socket;
}
@Override
public void run() {
try {
reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
// 读取reader中的信息...
} catch (Exception e) {
//捕获socket强制断开异常信息,移除socket
}
}
}
3. 创建线程启动类
public class TcpServer {
public static void main(String[] args) {
start();
}
public void start() {
try {
//记录链接过的客户端个数
//1、创建一个服务器端Socket,即ServerSocket,绑定指定的端口,进行监听
ServerSocket serverSocket = new ServerSocket(9000);
log.info("服务器即将启动,等待客户端连接");
//2、循环监听等待客户端的连接
while(true){
//调用accept方法 等待客户端的连接
Socket socket = serverSocket.accept();
if(!GlobalCommonUtil.isStart) {
//创建一个新的线程
SocketThread serverThread = new SocketThread(socket);
//启动线程
serverThread.start();
GlobalCommonUtil.count++;
log.info("连接过的客户端数量为:" + GlobalCommonUtil.count);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
测试环境中,由于真实场景和真实设备均没有见过,也无法考虑设备车间是怎么回事,只能老套路。用tcp连接工具模拟登录,登录,数据收发时均无问题,代码运行良好,开发测试都 ok ,然后交付了,再然后>跪了< 。。。
问题一:
全局静态资源的并发操作引起的线程不安全:
for(TcpObject tcpObject : GlobalCommonUtil.list) {
if(tcpObject.getMac().equals(mac)) {
GlobalCommonUtil.list.remove(tcpObject);
break;
}
}
GlobalCommonUtil.count--;
log.info("远程客户端已关闭连接!");
但客户端异常断开时,GlobalCommonUtil.list 的 remove 其他线程的读取很容易就会 java.lang.NullPointerException ,多线程之间共享变量,从而导致的线程不安全问题,如果我们让每个线程依次去读写这个变量,这样应该可以避免不安全问题了
-
加 synchronized 锁
分类 具体分类 被锁的对象 伪代码 方法 实例方法 类的实例对象 // 实例方法 锁住的是该类的实例对象
public synchronized void method(){
// action ...
}
静态方法 类对象 // 静态方法,锁住的是类对象
public static synchronized void method(){
// action ...
}
代码块 实例对象 类的实例对象 // 同步代码块,锁住的是该类的实例对象
synchronized (this){
// action ...
}
class对象 类对象 // 同步代码块,锁住的是该类的类对象
synchronized (Dermo.class){
// action ...
}
任意实例对象Object 实例对象 // 同步代码块,锁住的是配置的实例对象
//String 对象作为锁
String lock = “”
synchronized (lock){
// action ...
}
注:如果锁的是类的实例对象的话,每次 new 的操作都是创建一个新的对象,就出现 synchronized 锁不住对象的现象,如果锁的是类对象的话,无论new多少个实例对象,他们仍然会被锁住,即可保证线程之间的同步关系,synchronized 底层原理是使用了对象持有的监视器(monitor),但是同步代码块和同步方法的原理存在一点差异:
- 同步代码块使用的 monitorenter 和 monitorexit 指令实现的
- 同步方法是由方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZRED 标识隐式实现,实际上还是调用了 monitorenter 和 monitorexit 指令