我的(希望是最后一个)关于Java中的定时事件和线程的问题。我有一个服务器应用程序,它向所有客户端发送有关当前服务器时间的信息。我为客户添加了更改服务器时间的可能性。但是,它当前实现的方式,每个客户端连接都在一个单独的线程上运行。每个线程都有自己的ClockTask实例。因此,即使客户端修改了时间,它也只会在此特定线程的ClockTask实例中进行修改。任何其他客户仍将拥有自己的旧时光。我的代码:
服务器 - 每次传入的客户端连接时,都会启动一个新线程。
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = null;
boolean listeningSocket = true;
try {
serverSocket = new ServerSocket(11111);
} catch (IOException e) {
System.err.println("Could not listen on port: 11111");
}
while(listeningSocket){
System.out.println("Waiting for a client to connect...");
Socket clientSocket = serverSocket.accept();
System.out.println("Client connected!");
ConnectThread ct = new ConnectThread(clientSocket);
ct.start();
}
System.out.println("closed");
serverSocket.close();
}
}
ConnectThread - 每个连接线程都有自己的ClockTask(实际上是一个定时器,每秒更新一次):
public class ConnectThread extends Thread{
public static final int INTERVAL = 1000;
static ClockTask ctask;
private Socket socket = null;
public ConnectThread(Socket socket) {
super("ConnectThread");
this.socket = socket;
if(this.ctask == null)
{
synchronized(ClockTask.class)
{
if(this.ctask == null)
{
this.ctask = new ClockTask();
}
}
}
}
@Override
public void run(){
ObjectOutputStream serverOutputStream = null;
ObjectInputStream serverInputStream = null;
try {
serverOutputStream = new ObjectOutputStream(socket.getOutputStream());
serverInputStream = new ObjectInputStream(socket.getInputStream());
Timer timer = new Timer();
timer.schedule(ctask, 0, INTERVAL);
while(true)
{
Thread.sleep(INTERVAL);
System.out.println("mark");
System.out.println(ctask.getTime());
serverOutputStream.writeUTF(ctask.getTime());
serverOutputStream.flush();
String ok = serverInputStream.readUTF();
if(ok.equals("ok"))
{
String newTime = serverInputStream.readUTF();
ctask.setCalendarTime(newTime);
}
}
} catch (IOException | InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
try {
serverOutputStream.close();
serverInputStream.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
时钟任务保存时间信息:
public class ClockTask extends TimerTask {
private Calendar calendar;
private String time;
public String getTime() {
return time;
}
public void setTime(String time) {
this.time = time;
}
public ClockTask()
{
this.calendar = Calendar.getInstance(new Locale("us", "US"));
}
@Override
public void run() {
calendar.add(Calendar.SECOND, 1);
DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
this.time = dateFormat.format(calendar.getTime());
//System.out.println(time);
}
public void setCalendarTime(String newTime)
{
DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
try {
Date t = dateFormat.parse(newTime);
calendar.setTime(t);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
现在,我想到的第一个明显的方法是在Server类中声明ClockTask,然后将对象引用传递给任何新的ConnectThread,然后修改它。但是这没有用,我得到了很多数据读取错误,这也是一个问题,它发生在静态主方法中,所以我只能以静态方式访问它。我唯一知道的是,无法在每个新的ConnectThread实例中创建它。它必须是一个对象,存在于外面的某个地方。有什么想法吗?
EDIT 客户端:
public class Client {
private static final String REQUEST_TIME_CHANGE = "ok";
public static void main(String[] arg) {
Socket socketConnection = null;
ObjectOutputStream clientOutputStream = null;
ObjectInputStream clientInputStream = null;
ClockGUI gui = new ClockGUI();
gui.setVisible(true);
try {
socketConnection = new Socket("127.0.0.1", 11111);
clientOutputStream = new ObjectOutputStream(
socketConnection.getOutputStream());
clientInputStream = new ObjectInputStream(
socketConnection.getInputStream());
while(true){
System.out.println("before\n");
String date = clientInputStream.readUTF();
System.out.println(date);
gui.cp.setDate(date);
gui.repaint();
if(gui.isTimeChanged())
{
clientOutputStream.writeUTF(REQUEST_TIME_CHANGE);
clientOutputStream.flush();
clientOutputStream.writeUTF(gui.getTime());
clientOutputStream.flush();
gui.setTimeChanged(false);
}
else{
clientOutputStream.writeUTF("");
clientOutputStream.flush();
}
}
} catch (Exception e) {
System.out.println("The following exception has occured and was caught:");
e.printStackTrace();
}
finally{
try {
clientOutputStream.close();
clientInputStream.close();
socketConnection.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
答案 0 :(得分:0)
使变量成为静态,因此您只处理它的一个实例。
private static ClockTask ctask;
使用双重检查锁定以确保创建和维护单个ctask实例。
if(this.ctask == null)
{
synchronized(ClockTask.class)
{
if(this.ctask == null)
{
this.ctask = new ClockTask();
}
}
}
现在,您可以跨多个线程更改相同的Calendar
变量。当多个线程试图改变时间时,你当然必须处理并发问题。
此外,如果您要改变多方之间共享的状态,您也可以在另一个类中进行。 cTask
可以是帮助您改变该状态的引用,并且状态本身(日历时间)可以由另一个类表示。这样,您可以启动多个cTask
实例,这些实例会尝试锁定同一个日历对象以改变其状态。这将是一种更好的方式来实现你的目标。
答案 1 :(得分:0)
您可以使用CDI注释@Singleton
注释您的班级另一种选择是为计时器创建一个单独的线程:
static ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor();
然后是这样的:
timer.scheduleAtFixedRate(
() -> doSomething(),0,1,TimeUnit.SECONDS);