Quelle est la différence entre un thread et un processus ? Réponse : Un processus est un environnement d'exécution indépendant, qui peut être considéré comme un programme ou une application. Un thread est une tâche exécutée dans un processus. Les threads sont un sous-ensemble de processus. Un processus peut avoir plusieurs threads, et chaque thread exécute différentes tâches en parallèle. Différents processus utilisent différents espaces mémoire et tous les threads partagent le même espace mémoire.
1) Thread : l'unité d'exécution responsable de l'exécution du programme dans le processus
Le thread lui-même s'appuie sur le programme pour s'exécuter
Un thread est un programme Le flux de contrôle séquentiel ne peut utiliser que les ressources et l'environnement attribués au programme
2) Processus : le programme en cours d'exécution
Un processus contient au moins un thread
3) Single thread : Il n'y a qu'un seul thread dans le programme. En fait, la méthode main est un thread principal
4) Multi-threading : exécuter plusieurs tâches dans un seul programme
Le but. est de mieux utiliser les ressources du processeur
1 Hériter de la classe Thread : définie dans le package java.lang. Méthode run()
class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主动创建的第"+num+"个线程"); } }
Après avoir créé votre propre classe de thread, vous pouvez créer un objet thread, puis démarrer le thread via la méthode start(). Notez que la méthode run() n'est pas appelée pour démarrer le thread. La méthode run définit uniquement les tâches qui doivent être effectuées. Si la méthode run est appelée, cela équivaut à exécuter la méthode run dans le thread principal. Aucune différence avec les appels de méthode ordinaires. Pour le moment, il n'y a pas de Un nouveau thread sera créé pour effectuer les tâches définies.
public class Test { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); } }class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主动创建的第"+num+"个线程"); } }
Dans le code ci-dessus, en appelant la méthode start(), un nouveau thread sera créé. Afin de distinguer la différence entre l'appel de méthode start() et l'appel de méthode run(), veuillez regarder l'exemple suivant :
public class Test { public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); MyThread thread1 = new MyThread("thread1"); thread1.start(); MyThread thread2 = new MyThread("thread2"); thread2.run(); } }class MyThread extends Thread{ private String name; public MyThread(String name){ this.name = name; } @Override public void run() { System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId()); } }
Résultat de l'exécution :
À partir du résultat de sortie, le Les conclusions suivantes peuvent être tirées :
1) Les ID de thread de thread1 et de thread2 sont différents, et les ID de thread2 et du thread principal sont les mêmes. Cela signifie que l'appel de la méthode run ne crée pas de nouveau thread. , mais exécute la méthode run directement dans le thread principal, ce qui est la même que la méthode ordinaire. Il n'y a aucune différence dans l'appel
2) Bien que la méthode start du thread1 soit appelée avant la méthode run du thread2. , les informations relatives à l'appel de la méthode run du thread2 sont affichées en premier, indiquant que le processus de création d'un nouveau thread ne bloquera pas l'exécution ultérieure du thread principal.
2. Implémentez l'interface Runnable
En plus d'hériter de la classe Thread lors de la création d'un thread en Java, vous pouvez également implémenter des fonctions similaires en implémentant l'interface Runnable. L'implémentation de l'interface Runnable doit remplacer sa méthode run.
Ce qui suit est un exemple :
public class Test { public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); } } class MyRunnable implements Runnable{ public MyRunnable() { } @Override public void run() { System.out.println("子线程ID:"+Thread.currentThread().getId()); } }
La signification chinoise de Runnable est "tâche". Comme son nom l'indique, en implémentant l'interface Runnable, nous définissons une sous-tâche puis la transmettons à Fil pour l'exécution. Notez que cette méthode doit utiliser Runnable comme paramètre de la classe Thread, puis utiliser la méthode start de Thread pour créer un nouveau thread afin d'effectuer la sous-tâche. Si vous appelez la méthode run de Runnable, aucun nouveau thread ne sera créé. Cet appel de méthode ordinaire ne fait aucune différence.
En fait, si vous regardez le code source d'implémentation de la classe Thread, vous constaterez que la classe Thread implémente l'interface Runnable.
En Java, ces deux méthodes peuvent être utilisées pour créer des threads pour effectuer des sous-tâches. La méthode à choisir dépend de vos propres besoins. Hériter directement de la classe Thread peut sembler plus simple que d'implémenter l'interface Runnable, mais comme Java n'autorise qu'un seul héritage, si une classe personnalisée doit hériter d'autres classes, elle ne peut choisir d'implémenter que l'interface Runnable.
3. Utilisez ExecutorService, Callable et Future pour implémenter le multi-threading avec les résultats renvoyés
Vous en apprendrez plus sur le multi-threading plus tard. Pour l'instant, sachons ceci. méthode.
Les objets ExecutorService, Callable et Future appartiennent en fait à des classes fonctionnelles dans le framework Executor. Si vous souhaitez en savoir plus sur le framework Executor, vous pouvez visiter http://www.javaeye.com/topic/366591, où le framework est expliqué en détail. Le thread qui renvoie le résultat est une nouvelle fonctionnalité introduite dans le JDK1.5. Il est en effet très pratique. Avec cette fonctionnalité, je n'ai plus besoin de me donner beaucoup de mal pour obtenir la valeur de retour, et même si elle est implémentée, il peut être plein de lacunes.
Les tâches qui peuvent renvoyer une valeur doivent implémenter l'interface Callable. De même, les tâches qui ne renvoient pas de valeur doivent implémenter l'interface Runnable. Après avoir exécuté la tâche Callable, vous pouvez obtenir un objet Future. Call get sur l'objet pour obtenir l'objet renvoyé par la tâche Callable. Combiné avec l'interface de pool de threads ExecutorService, vous pouvez implémenter le légendaire multi-threading qui renvoie les résultats. Ce qui suit fournit un exemple de test multithread complet avec les résultats renvoyés. Il a été vérifié sous JDK1.5 et peut être utilisé directement. Le code est le suivant :
/** * 有返回值的线程 */ @SuppressWarnings("unchecked") public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("----程序开始运行----"); Date date1 = new Date(); int taskSize = 5; // 创建一个线程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 创建多个有返回值的任务 List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new MyCallable(i + " "); // 执行任务并获取Future对象 Future f = pool.submit(c); // System.out.println(">>>" + f.get().toString()); list.add(f); } // 关闭线程池 pool.shutdown(); // 获取所有并发任务的运行结果 for (Future f : list) { // 从Future对象上获取任务的返回值,并输出到控制台 System.out.println(">>>" + f.get().toString()); } Date date2 = new Date(); System.out.println("----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】"); } } class MyCallable implements Callable<Object> { private String taskNum; MyCallable(String taskNum) { this.taskNum = taskNum; } public Object call() throws Exception { System.out.println(">>>" + taskNum + "任务启动"); Date dateTmp1 = new Date(); Thread.sleep(1000); Date dateTmp2 = new Date(); long time = dateTmp2.getTime() - dateTmp1.getTime(); System.out.println(">>>" + taskNum + "任务终止"); return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】"; } }
Description du code :
La classe Executors dans le code ci-dessus fournit une série de méthodes d'usine pour créer des pools de threads, et les pools de threads renvoyés implémentent tous l'interface ExecutorService.
public static ExecutorService newFixedThreadPool(int nThreads)
Créez un pool de threads avec un nombre fixe de threads.
public static ExecutorService newCachedThreadPool()
Créez un pool de threads pouvant être mis en cache, l'appel d'execute réutilisera le thread précédemment construit (si le thread est disponible). Si aucun thread existant n'est disponible, un nouveau thread est créé et ajouté au pool. Terminez et supprimez du cache les threads qui n'ont pas été utilisés depuis 60 secondes.
public static ExecutorService newSingleThreadExecutor()
Créez un exécuteur à thread unique.
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
Créez un pool de threads qui prend en charge l'exécution de tâches planifiées et périodiques, qui peut être utilisé pour remplacer la classe Timer dans la plupart des cas.
ExecutoreService fournit la méthode submit(), en passant un Callable ou Runnable et en renvoyant un Future. Si le pool de threads d'arrière-plan de l'Executor n'a pas terminé le calcul de Callable, cet appel renvoie la méthode get() de l'objet Future, qui se bloquera jusqu'à ce que le calcul soit terminé.
Avant d'apprendre formellement les méthodes spécifiques de la classe Thread, comprenons d'abord le statut du fil de discussion. Cela nous aidera à comprendre la classe Thread plus tard. méthode.
1) Créer un (nouvel) état : un objet multithread est prêt
2) État prêt (exécutable) : la méthode start() est appelée, en attendant que le CPU planifie
3) État d'exécution (en cours d'exécution) : exécute la méthode run()
4) état bloqué (bloqué) : arrête temporairement l'exécution et peut donner des ressources à d'autres threads pour une utilisation
5) état de fin (mort) : le thread est détruit
quand Lorsqu'un nouveau thread doit être démarré pour effectuer une certaine sous-tâche, un thread est créé. Cependant, une fois le thread créé, il n'entrera pas immédiatement dans l'état prêt, car son exécution nécessite certaines conditions (telles que des ressources mémoire. Dans le billet de blog précédent sur la division de la zone mémoire JVM, nous savons que le compteur du programme, La pile Java et la pile de méthodes locales sont toutes privées du thread, donc une certaine quantité d'espace mémoire doit être allouée au thread). Ce n'est que lorsque toutes les conditions requises pour que le thread s'exécute soient remplies, qu'il entrera dans le mode prêt. État.
Lorsque le thread entre dans l'état prêt, cela ne signifie pas que le temps d'exécution du CPU peut être obtenu immédiatement. Peut-être que le CPU exécute autre chose à ce moment-là, il doit donc attendre. Lorsque le temps d'exécution du CPU est obtenu, le thread entre effectivement dans l'état d'exécution.
Pendant que le thread est en cours d'exécution, il peut y avoir plusieurs raisons pour lesquelles le thread actuel ne continue pas à s'exécuter, telles que le fait que l'utilisateur laisse activement le thread dormir (puis le réexécute après avoir dormi pendant un certain temps) , l'utilisateur laisse activement le thread attendre, ou Être bloqué par un bloc de synchronisation correspond à plusieurs états : temps d'attente (en veille ou en attente d'un certain événement), en attente (en attente d'être réveillé) et bloqué (bloqué).
Lorsqu'en raison d'une interruption soudaine ou de l'exécution d'une sous-tâche, le thread sera supprimé.
L'image suivante décrit l'état d'un fil de la création à la mort :
Dans certains tutoriels, les états bloqué, en attente et en attente sont collectivement appelés états de blocage. tant que Mais ici je veux lier le statut du thread avec l'appel de méthode en Java, je sépare donc les deux états d'attente et de temps d'attente.
Remarque : La différence entre sleep et wait :
sleep est une méthode de la classe Thread, wait est une méthode définie dans la classe Object. ne provoque pas de changements dans le comportement du verrou. Si le thread actuel possède le verrou, Thread.sleep ne laissera pas le thread libérer le verrou
Thread.sleep et Object.wait suspendront tous deux le thread actuel. temps aux autres threads . La différence est qu'après avoir appelé wait, les autres threads doivent exécuter notify/notifyAll pour récupérer le temps d'exécution du CPU
Changement de contexte
虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。
1.静态方法
currentThread()方法
currentThread()方法可以返回代码段正在被哪个线程调用的信息。
public class Run1{ public static void main(String[] args){ System.out.println(Thread.currentThread().getName()); } }
2.sleep()方法
方法sleep()的作用是在指定的毫秒数内让当前“正在执行的线程”休眠(暂停执行)。这个“正在执行的线程”是指this.currentThread()返回的线程。
sleep方法有两个重载版本:
sleep(long millis) //参数为毫秒
sleep(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
sleep相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。
但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。看下面这个例子就清楚了:
public class Test { private int i = 10; private Object object = new Object(); public static void main(String[] args) throws IOException { Test test = new Test(); MyThread thread1 = test.new MyThread(); MyThread thread2 = test.new MyThread(); thread1.start(); thread2.start(); } class MyThread extends Thread{ @Override public void run() { synchronized (object) { i++; System.out.println("i:"+i); try { System.out.println("线程"+Thread.currentThread().getName()+"进入睡眠状态"); Thread.currentThread().sleep(10000); } catch (InterruptedException e) { // TODO: handle exception } System.out.println("线程"+Thread.currentThread().getName()+"睡眠结束"); i++; System.out.println("i:"+i); } } } }
从上面输出结果可以看出,当Thread-0进入睡眠状态之后,Thread-1并没有去执行具体的任务。只有当Thread-0执行完之后,此时Thread-0释放了对象锁,Thread-1才开始执行。
注意,如果调用了sleep方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。
3.yield()方法
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
public class MyThread extends Thread{ @Override public void run() { long beginTime=System.currentTimeMillis(); int count=0; for (int i=0;i<50000000;i++){ count=count+(i+1); //Thread.yield(); } long endTime=System.currentTimeMillis(); System.out.println("用时:"+(endTime-beginTime)+" 毫秒!"); } }public class Run { public static void main(String[] args) { MyThread t= new MyThread(); t.start(); } }
执行结果:
用时:3 毫秒!
如果将 //Thread.yield();的注释去掉,执行结果如下:
用时:16080 毫秒!
对象方法
4.start()方法
start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
5.run()方法
run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
5.getId()
getId()的作用是取得线程的唯一标识
代码:
public class Test { public static void main(String[] args) { Thread t= Thread.currentThread(); System.out.println(t.getName()+" "+t.getId()); } }
输出:
main 1
6.isAlive()方法
方法isAlive()的功能是判断当前线程是否处于活动状态
代码:
public class MyThread extends Thread{ @Override public void run() { System.out.println("run="+this.isAlive()); } }public class RunTest { public static void main(String[] args) throws InterruptedException { MyThread myThread=new MyThread(); System.out.println("begin =="+myThread.isAlive()); myThread.start(); System.out.println("end =="+myThread.isAlive()); } }
程序运行结果:
begin ==falserun=trueend ==false
方法isAlive()的作用是测试线程是否偶处于活动状态。什么是活动状态呢?活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备开始运行的状态,就认为线程是“存活”的。
有个需要注意的地方
System.out.println(“end ==”+myThread.isAlive());
虽然上面的实例中打印的值是true,但此值是不确定的。打印true值是因为myThread线程还未执行完毕,所以输出true。如果代码改成下面这样,加了个sleep休眠:
public static void main(String[] args) throws InterruptedException { MyThread myThread=new MyThread(); System.out.println("begin =="+myThread.isAlive()); myThread.start(); Thread.sleep(1000); System.out.println("end =="+myThread.isAlive()); }
则上述代码运行的结果输出为false,因为mythread对象已经在1秒之内执行完毕。
7.join()方法
在很多情况下,主线程创建并启动了线程,如果子线程中药进行大量耗时运算,主线程往往将早于子线程结束之前结束。这时,如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到join()方法了。方法join()的作用是等待线程对象销毁。
public class Thread4 extends Thread{ public Thread4(String name) { super(name); } public void run() { for (int i = 0; i < 5; i++) { System.out.println(getName() + " " + i); } } public static void main(String[] args) throws InterruptedException { // 启动子进程 new Thread4("new thread").start(); for (int i = 0; i < 10; i++) { if (i == 5) { Thread4 th = new Thread4("joined thread"); th.start(); th.join(); } System.out.println(Thread.currentThread().getName() + " " + i); } } }
执行结果:
main 0main 1main 2main 3main 4new thread 0new thread 1new thread 2new thread 3new thread 4joined thread 0joined thread 1joined thread 2joined thread 3joined thread 4main 5main 6main 7main 8main 9
由上可以看出main主线程等待joined thread线程先执行完了才结束的。如果把th.join()这行注释掉,运行结果如下:
main 0main 1main 2main 3main 4main 5main 6main 7main 8main 9new thread 0new thread 1new thread 2new thread 3new thread 4joined thread 0joined thread 1joined thread 2joined thread 3joined thread 4
8.其他方法
getName和setName
用来得到或者设置线程名称。
getPriority和setPriority
用来获取和设置线程优先级。
setDaemon和isDaemon
用来设置线程是否成为守护线程和判断线程是否是守护线程。
守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。
在上面已经说到了Thread类中的大部分方法,那么Thread类中的方法调用到底会引起线程状态发生怎样的变化呢?下面一幅图就是在上面的图上进行改进而来的:
停止线程是在多线程开发时很重要的技术点,掌握此技术可以对线程的停止进行有效的处理。
停止一个线程可以使用Thread.stop()方法,但最好不用它。该方法是不安全的,已被弃用。
在Java中有以下3种方法可以终止正在运行的线程:
1.使用退出标志,使线程正常退出,也就是当run方法完成后线程终止
使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resume一样,都是作废过期的方法,使用他们可能产生不可预料的结果。
2.使用interrupt方法中断线程,但这个不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。
3.暂停线程
interrupt()方法
在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
设置线程优先级有助于帮“线程规划器”确定在下一次选择哪一个线程来优先执行。
设置线程的优先级使用setPriority()方法,此方法在JDK的源码如下:
public final void setPriority(int newPriority) { ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) != null) { if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriority0(priority = newPriority); } }
在Java中,线程的优先级分为1~10这10个等级,如果小于1或大于10,则JDK抛出异常throw new IllegalArgumentException()。
JDK中使用3个常量来预置定义优先级的值,代码如下:
public final static int MIN_PRIORITY = 1;public final static int NORM_PRIORITY = 5;public final static int MAX_PRIORITY = 10;
1.线程优先级特性:
1)继承性
比如A线程启动B线程,则B线程的优先级与A是一样的。
2)规则性
高优先级的线程总是大部分先执行完,但不代表高优先级线程全部先执行完。
3)随机性
优先级较高的线程不一定每一次都先执行完。
在Java线程中有两种线程,一种是User Thread(用户线程),另一种是Daemon Thread(守护线程)。
Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。
守护线程并非虚拟机内部可以提供,用户也可以自行的设定守护线程,方法:public final void setDaemon(boolean on) ;但是有几点需要注意:
thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。 (备注:这点与守护进程有着明显的区别,守护进程是创建后,让进程摆脱原会话的控制+让进程摆脱原进程组的控制+让进程摆脱原控制终端的控制;所以说寄托于虚拟机的语言机制跟系统级语言有着本质上面的区别)
在Daemon线程中产生的新线程也是Daemon的。 (这一点又是有着本质的区别了:守护进程fork()出来的子进程不再是守护进程,尽管它把父进程的进程相关信息复制过去了,但是子进程的进程的父进程不是init进程,所谓的守护进程本质上说就是“父进程挂掉,init收养,然后文件0,1,2都是/dev/null,当前目录到/”)
不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。因为在Daemon Thread还没来的及进行操作时,虚拟机可能已经退出了。
1.同步代码块
在代码块上加上”synchronized”关键字,则此代码块就称为同步代码块
同步代码块格式
synchronisé(objet synchronisé){
Blocs de code qui doivent être synchronisés ;
}
2. Méthodes de synchronisation
En plus des blocs de code qui peuvent être synchronisés, les méthodes peuvent également être synchronisées
Format de synchronisation des méthodes
1
nom de la méthode void synchronisée (){}
Articles associés :
Instance Java - blocage du fil de discussion
Vidéos associées :
Tutoriel vidéo sur les applications avancées de la bibliothèque multithreading et simultanée Java
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!