Java网络编程

Processus et threads : faire plusieurs choses en même temps[进程和线程:同时做很多事情]

La quasi-totalité [几乎所有的] des systèmes informatiques que vous manipulez savent faire plusieurs choses en même temps.

Vous disposez sur votre ordinateur d’une multitude de programmes : terminal, navigateur Firefox, IDE Eclipse, interprète Java, etc. Tous ces programmes ne s’exécutent pas tant que [只要] vous ne les lancez pas.

Un programme qui s’exécute sur une machine est un processus. Si vous ouvrez plusieurs terminaux, alors vous aurez plusieurs processus correspondant au programme Terminal. Si vous lancez plusieurs fois le même programme Java, vous aurez alors plusieurs processus. Chaque processus dispose ensuite de sa propre mémoire, et par défaut il est impossible pour un processus de lire ou d’écrire dans la mémoire d’un autre processus.

Par ailleurs, les ordinateurs modernes proposent maintenant des architectures de processeurs multi-cœurs. Dans le cadre de ce cours, vous pouvez assimiler ces cœurs à des processeurs à part entière, et considérer que votre ordinateur peut exécuter en parallèle un processus par processeurs.
????? 另外,现代计算机现在提供多核处理器体系结构。 我们可以将这些内核同化为完整的处理器,并认为计算机可以由处理器并行运行一个进程。

在终端上,ps命令可以看到计算机当前的进程以及使用的处理器。

Chaque processus peut également faire plusieurs choses en même temps avec des threads [线程] ou fils d’exécution, aussi appelés lightweight processes[轻量级进程].

On parle de concurrence parce que tous les processus et tous les threads s’exécutent “en même temps”. Quand il y a plus de processus et de threads que de processeurs, la concurrence est réalisée en par time-slicing [时间切片]: chaque processeur bascule d’un thread à un autre au cours du temps.

[picture:java_concurrence.jpg]

时间切片主要由操作系统处理,但也可以由用于开发程序的编程语言处理,例如java。

综上,我们可以认为,进程是某一个计算机内我们想要进行的任务,而线程是该任务的分解。

先说几个结论: [作者:彭志明 链接:https://www.jianshu.com/p/c6f39e36b817 来源:简书]
(1)进程是资源的分配和调度的一个独立单元,而线程是CPU调度的基本单元

(2)同一个进程中可以包括多个线程,并且线程共享整个进程的资源(寄存器、堆栈、上下文),一个进程至少包括一个线程。

(3)进程结束后它拥有的所有线程都将销毁,而线程的结束不会影响同个进程中的其他线程的结束

(4)线程是轻量级的进程,它的创建和销毁所需要的时间比进程小很多,所有操作系统中的执行功能都是创建线程去完成的

(5)线程中执行时一般都要进行同步和互斥,因为他们共享同一进程的所有资源

由于CPU的处理速度很快,对于RAM等的运算都是按照线性顺序进行,即轮流执行任务。因此上述的时间切片的概念其实就是CPU给不同进程乃至于线程分配的计算时间。

Threads Java

Tout programme Java comporte au moins un thread : celui qui exécute la méthode main().每个java程序都至少有一个线程,用来运行main函数. À ce thread s’ajoute généralement des threads implicites [隐式线程] supplémentaires, en particulier le ramasse-miettes (Garbage Collector)[垃圾收集器]. C’est la machine virtuelle [虚拟机] Java (JVM) qui gère l’exécution des threads, et en particulier à quel moment ils sont exécutés (scheduling).

L’utilisation de threads peut entrainer l’apparition de problèmes de synchronisation[同步] : la programmation est alors assez délicate.

Pour créer explicitement un nouveau thread Java dédié à vos propres instructions, vous avez deux possibilités :

  1. Écrire une sous-classe de java.lang.Thread
  2. Écrire une classe qui réalise l’interface java.lang.Runnable
public class RepeaterThread implements Runnable {
    final static public int NB_REPEATS = 100;

    private String message;

    public RepeaterThread(String message) {
        this.message = message;
    }

    public void run() {
        for(int i = 0; i < NB_REPEATS; i++) {
            System.out.println(message);
        }
    }
}

public class TwoThreads {
    public static void main(String[] args) {
        System.out.println("Main start");

        // start second thread
        Thread secondThread = new Thread(new RepeaterThread("      second"));
        secondThread.start();

        // remainder of the main thread execution
        for(int i = 0; i < RepeaterThread.NB_REPEATS; i++) {
            System.out.println("first ");
        }
    }
}

java线程就是运行在JVM中的子任务,因此JVM拥有对线程的调度机制,实际上采用的是一种叫做线程组ThreadGroup的方式,按照特定功能对线程进行集中式分组管理。java中每个线程都属于某一个线程组,器默认线程组为main,在main的线程组中同样也可以创建其他名称的线程组,但是已经加入某线程组的线程后期无法变更位置。

java线程的状态控制:

  1. 新建(new):当利用new关键字创建线程对象实例后,它仅仅作为一个对象实例存在,JVM没有为其分配CPU时间片等线程运行资源; 

  2. 就绪(ready):在处于创建状态的线程中调用start方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其它系统资源,只等JVM的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会。 

  3. 运行(running):线程获得了CPU 时间片(timeslice) ,执行程序代码

  4. 挂起(suspend):因为某种原因放弃了CPU 使用权,也即让出了CPU时间片,暂时停止运行。这时,线程将释放占用的所有资源,由JVM调度转入临时存储空间,直至应用程序恢复线程运行。

例如:运行(running)的线程执行Object.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。

  1. 睡眠(sleep):在线程运行过程中可以调用sleep方法并在方法参数中指定线程的睡眠时间将线程状态转换为睡眠状态。这时,该线程在不释放占用资源的情况下停止运行指定的睡眠时间。时间到达后,线程重新由JVM线程调度器进行调度和管理。 例如:运行(running)的线程执行Thread.sleep(long ms)或Thread.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。

  2. 终止(end):当线程体运行结束后,由JVM收回线程占用的资源时的状态。 

[待补充:守护线程和非守护线程]

Favoriser l’entrelacement des exécutions de threads

Les méthodes Thread.yield() et Thread.sleep(long) laissent à la machine virtuelle la possibilité d’exécuter un autre thread que le thread en cours d’exécution. Cela favorise un changement plus rapide du thread en cours d’exécution, et donc un entrelacement plus rapide :

public void run() {
        for(int i = 0; i < NB_REPEATS; i++) {
            System.out.println(message);
            Thread.yield();
        }
    }

或者

  public void run() {
        for(int i = 0; i < NB_REPEATS; i++) {
            System.out.println(message);
            //Thread.yield();
            try {
				Thread.sleep(i);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
        }
    }

Boucle de service TCP pour clients parallèles

本节的目的是建立一个完整的服务循环,即建立多个客户端和服务器的并行交互,同时在每个并行的客户端的链接中启动线程。

[picture:java_boucle_complete]

我的做法是,在之前建立的多个客户端依次进行访问服务器的程序中进行修改[具体参看之前的java_Bloc],首先在server的接收循环中不断新建线程,每个新建的线程对应一个service,其实也就意味着对应着每个一client,因为service和client是一一对应的。其中注意,需要将service修改为runnable的。另外,为了能看出在连接不同客户端后请求提交的并行特点(即交替进行),最好在client的main线程中不同请求之间添加Thread.sleep(3000)的代码,为了延迟请求提交使结果明显。

Client interactif 这一步是进一步修改client,用实际输入来代替时间延时。

String clientInput = null;
        Scanner inputScanner = new Scanner(System.in);
        do {
            System.out.print("Type your request (bye to exit): ");
            clientInput = inputScanner.nextLine();
            String response = client.requestEcho(clientInput);
            System.out.println("Sent: " + clientInput + ", received: " + response);
        } while(!clientInput.equalsIgnoreCase("bye"));
        inputScanner.close();

Terminaison d’un serveur pour clients parallèles**

Arrêt manuel 最常见的方式就是手动停止服务器。但是在这种情况下,如果仍有客户端与服务器保持链接状态的话,当手动终止服务器后,该客户端再次发出请求后会出现java.net.SocketException异常。
因此,我们解决此类问题的方式有两种,第一种是在手动关闭服务器前确认所有的客户端均已断连,第二种是对客户端进行编程以捕获异常、明确显示出服务器停止的消息。

对于第二种方法,我们仍然需要使用到的函数是try{...}catch(SocketException){...}

Utilisation d’une requête d’arrêt

Il est difficile d’arrêter proprement un serveur pour client parallèles avec une requête d’arrêt. Au moment où cette requête est reçue, plusieurs threads s’exécutent en parallèle :

  • le thread de la boucle d’acceptation de nouveaux clients
  • un thread par boucle de service de chaque client connecté

Il faut alors décider de la stratégie à adopter, par exemple :

  1. Arrêt immédiat du serveur avec un appel à System.exit(). De nombreuses connections seront interrompues avec des exceptions “Connection reset” dans les clients.

  2. Arrêt du serveur par simple mise à jour de l’attribut serverRunning et fermeture de la socket d’acceptation de connexion, pour sortir de l’appel bloquant à accept(). Dans ce cas, les threads correspondant aux clients déjà connectés poursuivront leur exécution jusqu’à la déconnexion de chacun de ces clients, et ensuite seulement le serveur s’arrêtera réellement.

一方面,需要通过“halt”请求完成服务器的退出,同时不影响客户端的继续连接(System.exit());另外,对于客户端的提前手动退出需要捕获异常输出消息。 这一个与之前做过的一个类似的练习不同,当初那个练习要求能够在“halt”请求发出后,既切断连接,也终止server的服务,现在看看当初的方法还是有些笨拙。