Java线程 - 如何在所有线程中使用相同的TimerTask实例

时间:2015-01-04 12:04:52

标签: java multithreading time client server

我的(希望是最后一个)关于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();
        }
    }


}




}

2 个答案:

答案 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 Sc​​heduledExecutorService timer =        Executors.newSingleThreadScheduledExecutor();

然后是这样的:

timer.scheduleAtFixedRate(
         () -> doSomething(),0,1,TimeUnit.SECONDS);