Java:Threads,如何让他们都做点什么

时间:2010-12-06 03:24:58

标签: java multithreading locking

我正在尝试用Java实现相互通信的节点。我这样做是为每个想要与服务器通信的节点创建一个新线程。

当给定数量的节点(即已创建多个线程)连接到服务器时,我希望每个线程在添加到“sharedCounter”后执行下一位代码。

我认为我需要在共享变量上使用'locks',以及signalAll()或notifyAll()之类的东西来获取所有线程,但我似乎无法清楚地知道它是如何工作的实施它。


任何解释这些Java概念的帮助都将不胜感激:D


下面是我的代码的结构:

import java.net.*;
import java.io.*;

public class Node {

    public static void main(String[] args) {
    ...
    // Chooses server or client launchers depend on parameters.
    ...
    }
}

class sharedResource {
    private int sharedCounter;

    public sharedResource(int i) {
        sharedCounter = i;
    }

    public synchronized void incSharedCounter() {
        sharedCounter--;
        if (sharedCounter == 0)
            // Get all threads to do something
    }
}

class Server {
    ...
        for (int i = 0; i < numberOfThreads; i++) {
            new serverThread(serverSocket.accept()).start();
        }
    ...
        sharedResource threadCount = new sharedResource(numberOfThreads);
    ...
}

class serverThread extends Thread {
...
//some code
Server.threadCount.incSharedCounter();
// Some more code to run when sharedCounte == 0
...
}

class Client {
...
}

3 个答案:

答案 0 :(得分:3)

//获取所有线程

线程(或者说Runnables,你应该实现而不是扩展Thread)有一个run方法,它包含了它们应该执行的代码。

一旦你调用Thread #start(后来调用Runnable#run),线程就会开始这样做。

由于您似乎不熟悉Java中的多线程,因此我建议您阅读已在Java5中引入的并发实用程序包的简介,以便更轻松地实现并发操作。

具体而言,您似乎正在寻找的是一种“暂停”操作直到满足条件的方法(在您的情况下,计数器已达到零)。为此,您应该查看CountDownLatch

答案 1 :(得分:1)

事实上,这个主题很广泛,但我会尝试解释基础知识。可以从各种blogs和文章中阅读更多详细信息。其中一个是Java trail

最好将每个线程看作是在比赛中彼此并排的跑步者(自然人)。每个跑步者可以在跑步时执行任何任务。例如,在比赛的特定时刻从桌子上取一杯水。在物理上,他们不能同时从同一个杯子喝水,但在虚拟世界中,它是可能的(这是画线的地方)。

例如,再拿两个跑步者;他们每个人都必须在一个轨道上来回奔跑,并在每一端按下按钮(由跑步者共享)1000,000次,按钮只是每次递增一个计数器。当他们完成运行时,计数器的价值是多少?在物理世界中,它将是2'000'000,因为跑步者不能同时按下按钮,他们会等待第一个先离开...这是除非他们为此而战...嗯,这正是两个线程会做的事情。请考虑以下代码:

public class ThreadTest extends Thread {
    static public final int TOTAL_INC = 1000000;

    static public int counter = 0;

    @Override
    public void run() {
        for (int i=0; i<TOTAL_INC; i++) {
            counter++;
        }
        System.out.println("Thread stopped incrementing counter " + TOTAL_INC + " times");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new ThreadTest();
        Thread t2 = new ThreadTest();

        t1.start();
        t2.start();

        t1.join();  // wait for each thread to stop on their own...
        t2.join();  // 

        System.out.println("Final counter is : " + counter + " which should be equal to " + TOTAL_INC * 2);
    }
}

输出可能类似于

Thread stopped incrementing counter 1000000 times
Thread stopped incrementing counter 1000000 times
Final counter is : 1143470 which should be equal to 2000000

偶尔,两个线程只会增加相同的值两次;这被称为竞争条件。

同步run方法不起作用,您必须使用一些锁定机制来防止这种情况发生。请考虑run方法中的以下更改:

static private Object lock = new Object();
@Override
public void run() {
    for (int i=0; i<TOTAL_INC; i++) {
        synchronized(lock) {
            counter++;
        }
    }
    System.out.println("Thread stopped incrementing counter " + TOTAL_INC + " times");
}

现在预期的输出是

...
Final counter is : 2000000 which should be equal to 2000000

我们已将计数器与共享对象同步。这就像在只有一个跑步者可以一次访问按钮之前放置队列一行。

注意:此锁定机制称为mutex。如果一次可以通过 n 线程访问资源,您可以考虑使用semaphore

多线程也与死锁有关。 deadlock是两个线程相互等待另一个线程以释放某些同步资源以继续。例如:

  • 线程1启动
  • 线程2开始
  • 线程1获取synchronized object1
  • 线程2获取synchronized object2
  • 线程2需要获取object2以继续(由线程1锁定)
  • 线程1需要获取object1以继续(由线程2锁定)
  • 程序挂起死锁

虽然有很多方法可以防止这种情况发生(这取决于你的线程在做什么,以及它们是如何实现的......)你应该特别阅读它。

注意:方法waitnotifynotifyAll只能在同步对象时调用。例如:

static public final int TOTAL_INC = 10;
static private int counter = 0;
static private Object lock = new Object();

static class Thread1 extends Thread {
    @Override
    public void run() {
        synchronized (lock) {
            for (int i=0; i<TOTAL_INC; i++) {
                try {
                    lock.wait();
                    counter++;
                    lock.notify();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

static class Thread2 extends Thread {
    @Override
    public void run() {
        synchronized (lock) {
            for (int i=0; i<TOTAL_INC; i++) {
                try {
                    lock.notify();
                    counter--;
                    lock.wait();
                } catch (InterruptedException e) {
                    /* ignored */
                }
            }
        }
    }
}

请注意,两个线程都在synchronized块中运行for...loop块。 (两个线程结束时counter == 0的结果。)这可以实现,因为它们“让对方”通过资源的waitnotify方法访问同步资源。不使用这两种方法,两个线程都只是顺序运行而不是同时运行(或更准确地说,交替运行)。

我希望这能够揭示线程(在Java中)。

** 更新 **

以下是使用CountDownLatch之前Thilo类建议的{{3}}类,对上述所有内容的概念进行了一些证明:

static class Server {
    static public final int NODE_COUNT = 5;

    private List<RunnableNode> nodes;
    private CountDownLatch startSignal; 
    private Object lock = new Object();


    public Server() {
        nodes = Collections.synchronizedList(new ArrayList<RunnableNode>());
        startSignal = new CountDownLatch(Server.NODE_COUNT);
    }

    public Object getLock() {
        return lock;
    }

    public synchronized void connect(RunnableNode node) {
        if (startSignal.getCount() > 0) {
            startSignal.countDown();
            nodes.add(node);
            System.out.println("Received connection from node " + node.getId() + " (" + startSignal.getCount() + " remaining...)");
        } else {
            System.out.println("Client overflow! Refusing connection from node " + node.getId());
            throw new IllegalStateException("Too many nodes connected");
        }
    }

    public void shutdown() {
        for (RunnableNode node : nodes) {
            node.shutdown();
        }
    }

    public void awaitAllConnections() {
        try {
            startSignal.await();
            synchronized (lock) {
                lock.notifyAll();  // awake all nodes
            }
        } catch (InterruptedException e) {
            /* ignore */
            shutdown();  // properly close any connected node now
        }
    }
}


static class RunnableNode implements Runnable {
    private Server server;
    private int id;
    private boolean working;

    public RunnableNode(int id, Server server) {
        this.id = id;
        this.server = server;
        this.working = true;
    }
    public int getId() {
        return id;
    }
    public void run() {
        try {
            Thread.sleep((long) (Math.random() * 5) * 1000); // just wait randomly from 0 to 5 seconds....

            synchronized (server.getLock()) {
                server.connect(this);
                server.getLock().wait();
            }

            if (!Thread.currentThread().isAlive()) {
                throw new InterruptedException();
            } else {
                System.out.println("Node " + id + " started successfully!");

                while (working) {
                    Thread.yield();
                }

            }
        } catch (InterruptedException e1) {
            System.out.print("Ooop! ...");
        } catch (IllegalStateException e2) {
            System.out.print("Awwww! Too late! ...");
        }

        System.out.println("Node " + id + " is shutting down");
    }
    public void shutdown() {
        working = false; // shutdown node here...
    }
}


static public void main(String...args) throws InterruptedException {
    Server server = new Server();

    for (int i=0; i<Server.NODE_COUNT + 4; i++) {  // create 4 more nodes than needed...
        new Thread(new RunnableNode(i, server)).start();
    }
    server.awaitAllConnections();

    System.out.println("All connection received! Server started!");

    Thread.sleep(6000); 
    server.shutdown();
}

答案 2 :(得分:0)

这是一个广泛的主题。您可以尝试通过official guides阅读Java中的并发(即线程,或多或少)。这不是切割和干燥的解决方案;你必须设计一些东西。