Maison  >  Article  >  Java  >  Multithreading Java : utilisation de la classe Thread.

Multithreading Java : utilisation de la classe Thread.

王林
王林avant
2023-04-25 14:52:071299parcourir

    Utilisation de base de la classe Thread

    1 Créez une sous-classe, héritez de Thread et remplacez la méthode run. :

    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("hello thread");
        }
    }
    public class Demo1 {
        public static void main(String[] args) {
            // 最基本的创建线程的办法.
            Thread t = new MyThread();
            //调用了start方法才是真正的在系统中创建了线程,执行run方法
            t.start();
        }
    }

    2 Créez une classe, implémentez l'interface Runnable puis créez une instance Runnable et transmettez-la à Thread

    class MyRunnable implements Runnable{
        @Override
        public void run() {
            System.out.println("hello");
        }
    }
    public class Demo3 {
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable());
            t.start();
        }
    }
    #🎜. 🎜## 🎜🎜#3. Classe interne anonyme

    Créez une classe interne anonyme, héritez de la classe Thread, remplacez la méthode run, puis créez une instance de la classe interne anonyme

    # 🎜🎜#
    public class Demo4 {
        public static void main(String[] args) {
            Thread t = new Thread(){
                @Override
                public void run() {
                    System.out.println("hello");
                }
            };
            t.start();
        }
    }

    new Runnable, pour cette classe interne anonyme créée, et la nouvelle instance Runnable est transmise au constructeur Thread
    public class Demo5 {
        public static void main(String[] args) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("hello");
                }
            });
            t.start();
        }
    }

    4. expression lambda lambda remplace Runnable 🎜#Si le fil d'arrière-plan du fil d'arrière-plan n'affecte pas la sortie du processus, ce n'est pas le fil d'arrière-plan qui affectera la sortie du processus

    2.isAlive();

    Qu'il soit vivant pendant l'appel Il n'y a pas de thread correspondant dans le système avant le démarrage. Le thread est détruit après l'exécution de la méthode run. L'objet t peut toujours exister

    Le. différence entre run et start

    run est simplement une méthode ordinaire qui décrit le contenu de la tâche start est une méthode spéciale, un fil sera créé en interne dans le système
    .

    Interrompre le thread
    La clé pour arrêter le thread est de laisser la méthode d'exécution correspondante terminer son exécution. Pour le thread principal, la méthode principale ne sera pas terminée. jusqu'à ce que la méthode principale soit exécutée. Cet indicateur peut affecter la fin de ce thread, mais plusieurs threads partagent ici un espace virtuel, donc le isQuit modifié par le thread principal et le isQuit jugé par le thread t ont la même valeur

    .

    public class Demo6 {
        public static void main(String[] args) {
            Thread t = new Thread(() ->{
                System.out.println("hello");
            });
            t.start();
        }
    }
    #🎜🎜 #2 Utilisez un indicateur intégré dans Thread pour déterminer
    Thread.interruted() Il s'agit d'une méthode statique Thread.currentThread().isInterrupted. () Il s'agit d'une méthode d'instance, Parmi elles, currentThread peut obtenir l'instance du thread actuel .interrupt() peut produire deux situations : # 🎜🎜#

    1) Si le thread t est prêt, définissez l'indicateur du thread sur true
    2) Si le thread t est dans un état bloqué (veille) , une InterruptExeception sera déclenchée

    # 🎜🎜#

    Thread en attente

    L'ordre de planification entre plusieurs threads est incertain. Parfois, nous devons contrôler l'ordre entre les threads. .Le thread en attente est une sorte de contrôle. La séquence d'exécution du thread signifie que le thread en attente ici contrôle uniquement l'ordre dans lequel les threads se terminent.
    Quel thread rejoint, quel thread bloquera et attendra que le thread correspondant termine son exécution.

    t.join();
    Le thread qui appelle cette méthode est le thread principal, et cette méthode est appelée pour l'objet t Le temps est de laisser le principal attendre t.

    Le code s'arrête lorsqu'il atteint la ligne de jointure, laissant t se terminer d'abord, puis main continue.

    t.join(10000);

    join fournit une autre version avec un paramètre, le paramètre attend Après 10 secondes , rejoindre reviendra directement et n'attendra plus

    Thread.currentThread()

    peut obtenir le. thread actuel Dans l'application, quel thread appelle currentThread, l'instance de quel thread est obtenue. Comparez cela comme suit :
    Pour ce code, les threads sont créés en héritant de la méthode Thread. À ce stade, ce que vous obtenez directement via cela dans la méthode run est l'instance Thread actuelle

    public class Demo10 {
        // 通过这个变量来控制线程是否结束.
        private static boolean isQuit = false;
        public static void main(String[] args) {
            Thread t = new Thread(() -> {
                while (!isQuit) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
    
            // 就可以在 main 线程中通过修改 isQuit 的值, 来影响到线程是否退出
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // main 线程在 5s 之后, 修改 isQuit 的状态.
            isQuit = true;
        }
    }

    Cependant, cela ne pointe pas ici vers le type Thread, mais vers Runnable, qui est juste une tâche simple n'a pas d'attribut name. Pour obtenir le nom du fil, vous ne pouvez obtenir le nom du fil que via Thread.currentThread()

    public class Demo7 {
        public static void main(String[] args)  {
            Thread t = new Thread(() -> {
                while(!Thread.currentThread().isInterrupted()){
                    System.out.println("hello");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        // 当触发异常之后, 立即就退出循环~
                        System.out.println("这是收尾工作");
                        break;
                    }
                }
            });
            t.start();
            try{
                Thread.sleep(5000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            // 在主线程中, 调用 interrupt 方法, 来中断这个线程.
            // t.interrupt 的意思就是让 t 线程被中断!!
            t.interrupt();
        }
    }

    Process status.

    Pour les couches système :


    ready

    blocking

    #🎜 🎜#
    La classe Thread en Java est encore affinée :

    NEW :
    Thread L'objet a été créé mais start n'a pas encore été appelé


    TERMINATED:# 🎜🎜#Le thread dans le système d'exploitation a été exécuté et détruit, mais l'objet Thread est toujours là Statut obtenu

    RUNNABLE :

    État prêt, le thread dans cet état est dans la file d'attente prêt et peut être programmé sur le CPU à tout moment. entrera dans cet état lorsque sleep sera appelé, rejoignez (délai d'expiration) BLOCKED : le thread actuel attend le verrouillage

    • WAITING :#🎜 🎜#Le fil de discussion actuel attend de se réveiller

    • # 🎜🎜#Schéma de transition d'état :

    线程安全问题

    定义:操作系统中线程调度是随机的,导致程序的执行可能会出现一些bug。如果因为调度随机性引入了bug线程就是不安全的,反之则是安全的。
    解决方法:加锁,给方法直接加上synchronized关键字,此时进入方法就会自动加锁,离开方法就会自动解锁。当一个线程加锁成功的时候,其他线程尝试加锁就会触发阻塞等待,阻塞会一直持续到占用锁的线程把锁释放为止。

    synchronized public void increase() {
            count++;
    }

    线程不安全产生的原因:

    • 1.线程是抢占式执行,线程间的调度充满随机性。

    • 2.多个线程对同一个变量进行修改操作

    • 3.针对变量的操作不是原子的

    • 4.内存可见性也会影响线程安全(针对同一个变量t1线程循环进行多次读操作,t2线程少次修改操作,t1就不会从内存读数据了而是从寄存器里读)

    • 5.指令重排序,也是编译器优化的一种操作,保证逻辑不变的情况下调整顺序,解决方法synchronized。

    内存可见性解决方法:

    • 1.使用synchronized关键字 使用synchronized不光能保证指令的原子性,同时也能保证内存的可见性。被synchronized包裹起来的代码编译器就不会从寄存器里读。

    • 2.使用volatile关键字 能够保证内存可见性,禁止编译器作出上述优化,编译器每次执行判定相等都会重新从内存读取。

    synchronized用法

    在java中每个类都是继承自Object,每个new出来的实例里面一方面包含自己安排的属性,另一方面包含了“对象头”即对象的一些元数据。加锁操作就是在这个对象头里面设置一个标志位。

    1.直接修饰普通的方法

    使用synchronized的时候本质上是对某个“对象”进行加锁,此时的锁对象就是this。加锁操作就是在设置this的对象头的标志位,当两个线程同时尝试对同一个对象加锁的时候才有竞争,如果是两个线程在针对两个不同对象加锁就没有竞争。

    class Counter{
    	public int count;
    	synchronized public void increase(){
    	count++;
    	}
    }

    2.修饰一个代码块

    需要显示制定针对那个对象加锁(java中的任意对象都可以作为锁对象)

    public void increase(){
        synchronized(this){
        count++;
        }
    }

    3.修饰一个静态方法

    相当于针对当前类的类对象加锁,类对象就是运行程序的时候。class文件被加载到JVM内存中的模样。

    synchronized public static void func(){
    }

    或者

    public static void func(){
        synchronized(Counter.class){
    
        }
    }

    监视器锁monitor lock

    可重入锁就是同一个线程针对同一个锁,连续加锁两次,如果出现死锁就是不可重入锁,如果不会死锁就是可重入的。因此就把synchronized实现为可重入锁,下面的例子里啊连续加锁操作不会导致死锁。可重入锁内部会记录所被哪个线程占用也会记录加锁次数,因此后续再加锁就不是真的加锁而是单纯地把技术给自增。

    synchronized public void increase(){
        synchronized(this){
            count++;
        }
    }

    死锁的其他场景

    • 1.一个线程一把锁

    • 2.两个线程两把锁

    • 3.N个线程M把锁(哲学家就餐问题,解决方法:先拿编号小的筷子)

    死锁的四个必要条件(前三个都是锁本身的特点)

    • 1.互斥使用,一个锁被另一个线程占用后其他线程就用不了(锁的本质,保证原子性)

    • 2.不可抢占,一个锁被一个线程占用后其他线程不可把这个锁给挖走

    • 3.请求和保持,当一个线程占据了多把锁之后,除非显示的释放否则这些锁中都是该线程持有的

    • 4.环路等待,等待关系成环(解决:遵循固定的顺序加锁就不会出现环路等待)

    java线程类:

    • 不安全的:ArrayList,LinkedList,HashMap,TreeMap,HashSet,TreeSet,StringBuilder

    • 安全的:Vector,HashTable,ConcurrentHashMap,StringBuffer,String

    volatile

    禁止编译器优化保证内存可见性,产生原因:计算机想执行一些计算就需要把内存的数据读到CPU寄存器中,然后再从寄存器中计算写回到内存中,因为CPU访问寄存器的速度比访问内存快很多,当CPU连续多次访问内存结果都一样,CPU就会选择访问寄存器。

    JMM(Java Memory Model)Java内存模型

    就是把硬件结构在java中用专业的术语又重新抽象封装了一遍。

    工作内存(work memory)其实指的不是内存,而是CPU寄存器。
    主内存(main memeory)这才是主内存。
    原因:java作为一个跨平台编程语言要把硬件细节封装起来,假设某个计算机没有CPU或者内存同样可以套到上述模型中。

    寄存器,缓存和内存之间的关系

    CPU从内存取数据太慢,因此把数据直接放到寄存器里来读,但寄存器空间太紧张于是又搞了一个存储空间,比寄存器大比内存小速度比寄存器慢比内存快称为缓存。寄存器和缓存统称为工作内存。

    寄存器,缓存和内存之间的关系图

    Multithreading Java : utilisation de la classe Thread.

    • 存储空间:CPU

    • 速度:CPU>L1>L2>L3>内存

    • 成本:CPU>L1>L2>L3>内存

    volatile和synchronized的区别

    • volatile只是保证可见性不保证原子性,只是处理一个线程读和一个线程写的过程。

    • synchronized都能处理

    wait和notify

    等待和通知处理线程调度随机性问题的,join也是一种控制顺序的方式更倾向于控制线程结束。wait和notify都是Object对象的方法,调用wait方法的线程就会陷入阻塞,阻塞到有线程通过notify来通知。

    public class Demo9 {
        public static void main(String[] args) throws InterruptedException {
            Object object = new Object();
            System.out.println("wait前");
            object.wait();
            System.out.println("wait后");
        }
    }

    wait内部会做三件事;

    • 1.先释放锁

    • 2.等待其他线程的通知

    • 3.收到通知后重新获得锁并继续往下执行

    因此想用wait/notify就得搭配synchronized

    public class Demo9 {
        public static void main(String[] args) throws InterruptedException {
            Object object = new Object();
            synchronized (object){
                System.out.println("wait前");
                object.wait();
                System.out.println("wait后");
            }
        }
    }

    注意:wait notify都是针对同一对象来操作的,例如现在有一个对象o,有10个线程都调用了o.wait,此时10个线程都是阻塞状态。如果调用了o.notify就会把10个线程中的一个线程唤醒。而notifyAll就会把所有10个线程全都给唤醒,此时就会竞争锁。

    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!

    Déclaration:
    Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer