Cet article vous apporte des connaissances pertinentes sur java, qui présente principalement des questions liées au multi-threading, notamment ce que sont les programmes, les processus, les threads, les trois façons de créer des threads, le statut des threads, etc. contenu ci-dessous. J’espère qu’il sera utile à tout le monde.
Étude recommandée : "Tutoriel vidéo Java"
Programme (programme) : Il est écrit dans un certain langage pour compléter un spécifique tâche Un ensemble d'instructions est un morceau de code statique. (Le programme est statique)
Processus (processus) : C'est un processus d'exécution d'un programme, un programme en cours d'exécution. En tant qu'unité d'allocation de ressources, le processus sera dans. la mémoire pour chaque processus alloue différentes zones de mémoire. (Le processus est dynamique) C'est un processus dynamique. Le cycle de vie du processus : a son propre processus de création, d'existence et de mort
Actuellement, le système d'exploitation prend en charge plusieurs processus et peut exécuter plusieurs processus en même temps. en même temps. Distinguer par ID de processus
Thread (thread) : un chemin d'exécution dans un processus, qui est également l'unité de planification de base de CUP. Un processus se compose d'un ou plusieurs threads. , dont chacun accomplit des tâches différentes, exécutées par plusieurs threads en même temps, est appelé multithreading.
Composition du thread
Les composants de base de tout thread :
- Tranche de temps CPU : Le système d'exploitation (OS) alloue du temps d'exécution à chaque thread.
- Exécution de données : espace de tas (stockage des objets que les threads doivent utiliser, plusieurs threads peuvent partager des objets dans le tas) ; espace de pile (stockage des variables locales que les threads doivent utiliser, chaque thread a une pile indépendante)
Caractéristiques des threads
- Exécution préemptive des threads (haute efficacité, peut empêcher un seul thread de monopoliser le CPU pendant une longue période)
- Lorsqu'un CPU monocœur est exécuté, il est exécuté selon des tranches de temps, et uniquement une tranche de temps peut être exécutée par des Threads, car la tranche de temps est extrêmement courte, ce que nous pensons, c'est qu'ils se produisent "simultanément".
- Le processeur multicœur réalise véritablement l'exécution simultanée de plusieurs threads en une seule tranche de temps
- Dans un processeur monocœur, macroscopiquement, exécution simultanée, microscopiquement, exécution séquentielle
- Un processus est l'unité de base d'allocation des ressources dans le système d'exploitation, et les threads sont l'unité de planification de base du CPU
- Après l'exécution d'un programme, il y aura au moins un processus
- Un processus peut contenir plusieurs threads, mais il doit y avoir au moins un thread, sinon le processus n'a aucun sens
- L'adresse du segment de données ne peut pas être partagée entre les processus, mais elle peut être partagée entre les threads du même processus.
1. Hériter de la classe Thread
2. Remplacer la méthode run()
3 . Créez un objet Class enfant
4. Appelez la méthode start() (PS : n'appelez pas la méthode run(), cela équivaut à appeler la méthode objet normalement et démarre le thread)
Classe héritée
public class MyThread extends Thread { @Override public void run() { for (int i = 1; i " + i); } }}
Classe de test
public class TestThread { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); for (int i = 1; i "+i); } }}
Résultat
getId()
//Obtenir l'identifiant du fil, chaque fil a le sien idgetId()
//获取线程的id,每个线程都有自己的idgetName()
//获取线程名字Thread.currentThread()
//获取当前线程
代码
public class TestThread { public static void main(String[] args) { MyThread t=new MyThread(); t.start(); //只能在继承Thread类的情况下用 System.out.println("线程id:"+t.getId()); System.out.println("线程名字:"+t.getName()); //调用Thread类的静态方法获取当前线程(这里获取的是主线程) System.out.println("线程id:"+Thread.currentThread().getId()); System.out.println("线程名字:"+Thread.currentThread().getName()); }}
只能修改线程的名称,不能修改线程的id(id是由系统自动分配)
1、使用线程子类的构造方法赋值
2、调用线程对象的setName()
getName()
//Obtenir le nom du fil de discussionThread.currentThread()
//Obtenir le fil de discussion actuel
code
🎜public class MyThread extends Thread{ public MyThread() {}//无参构造器 public MyThread(String name) { super(name); } public void run() { for(int i=1;i🎜<img src="https://img.php.cn%20/upload/article/000/000/067/57e2f75a769022a530da131db2d02e06-6.png" alt="Insérer la description de l'image ici">🎜🎜Modifier le nom du fil🎜🎜🎜Uniquement le nom du le thread peut être modifié, mais l'identifiant du thread ne peut pas être modifié (l'identifiant est automatiquement attribué par le système)🎜 1. Utilisez la méthode constructeur de la sous-classe du thread pour attribuer la valeur🎜 2. Appelez le <code>setName() </code> méthode de l'objet thread🎜🎜🎜🎜Code🎜🎜<pre class="brush:php;toolbar:false">public class MyThread extends Thread{ public MyThread() {}//无参构造器 public MyThread(String name) { super(name); } public void run() { for(int i=1;i<pre class="brush:php;toolbar:false">public class TestThread { public static void main(String[] args) { MyThread t1=new MyThread("子线程1");//通过构造方法 MyThread t2=new MyThread(); t2.setName("子线程2"); System.out.println("线程t1的id:"+t1.getId()+" 名称:"+t1.getName()); System.out.println("线程t2的id:"+t2.getId()+" 名称:"+t2.getName()); }}
1.实现
Runnable
接口
2.实现run()
方法
3.创建实现类对象
4.创建线程类对象
5.调用start()
方法
实现接口
public class MyRunnable implements Runnable{//实现接口 @Override public void run() {//实现run方法 // TODO Auto-generated method stub for(int i=1;i<p><strong>测试类</strong></p><pre class="brush:php;toolbar:false">public class TestRunnable { public static void main(String[] args) { //1.创建MyRunnable对象,表示线程执行的功能 Runnable runnable=new MyRunnable(); //2.创建线程对象 Thread th=new Thread(runnable); //3.启动线程 th.start(); for(int i=1;i<p><img src="https://img.php.cn/upload/article/000/000/067/beb0b9e2f551ceb35ac7ab846edfec3d-8.png" alt="En savoir plus sur le mécanisme multithread Java"></p><h3>使用匿名内部类</h3><blockquote><p>如果一个线程方法我们只使用一次,那么就不必设置一个单独的类,就可以使用匿名内部类实现该功能</p></blockquote><pre class="brush:php;toolbar:false">public class TestRunnable { public static void main(String[] args) { Runnable runnable=new Runnable() { @Override public void run() { // TODO Auto-generated method stub for(int i=1;i<h2>2.3 实现Callable接口</h2><h3>Callable和Thread、Runnable比较</h3><blockquote> <p>对比继承<code>Thread</code>类和实现<code>Runnable</code>接口创建线程的方式发现,都需要有一个<code>run()</code>方法,但是这个run()方法有不足:</p> <ul> <li>没有返回值</li> <li>不能抛出异常</li> </ul> <p>基于上面的两个不足,在JDK1.5以后出现了第三种创建线程的方式:实现<code>Callable</code>接口</p> <p>实现<code>Callable</code>接口的好处:</p> <ul> <li>有返回值</li> <li>能抛出异常</li> </ul> <p>缺点:</p> <ul><li>创建线程比较麻烦</li></ul> </blockquote><blockquote> <p>1.实现<code>Callable</code>接口,可以不带泛型,如果不带泛型,那么call方法的返回值就是<code>Object</code>类型</p> <p>2.如果带泛型,那么call的返回值就是泛型对应的类型</p> <p>3.从call方法看到:方法有返回值,可以抛出异常</p> </blockquote><h3>具体实现</h3><p><strong>实现接口</strong></p><pre class="brush:php;toolbar:false">import java.util.Random;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class TestCallable implements Callable<integer>{ @Override public Integer call() throws Exception { // TODO Auto-generated method stub return new Random().nextInt(10); }}</integer>
测试类
import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class Test { public static void main(String[] args) throws InterruptedException, ExecutionException { TestCallable tc=new TestCallable(); FutureTask<integer> ft=new FutureTask(tc); //创建线程对象 Thread th=new Thread(ft); th.start(); //获取线程得到的返回值 Integer In=ft.get(); System.out.println(In); }}</integer>
休眠(当前线程主动休眠millis毫秒)
public static void sleep(long millis)
放弃(当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片)
public static void yield()
加入(当一个线程调用了join方法,这个线程就会先被执行,它执行结束以后才可以去执行其余的线程)
public final void join()
//必须先start(),在join(),才有效优先级(线程优先级为1–10,默认为5,优先级越高,表示获取CPU机会越多)
线程对象.setPriority()
守护线程
- 线程对象.setDaemon(true);设置为守护线程
- 线程有两类:用户线程(前台线程)、守护线程(后台线程)
- 如果程序中所有前台线程都执行完毕了,后台线程也会自动结束
- 垃圾回收器线程属于守护线程
public static void sleep(long millis)
当前线程主动休眠millis毫秒
子线程
public class SleepThread extends Thread{ @Override public void run() { for (int i = 1; i <p><mark>PS:sleep()的异常在run方法中是不能抛出的,只能用try–catch处理</mark><br><strong>测试类</strong></p><pre class="brush:php;toolbar:false">public class Test01 { public static void main(String[] args) { SleepThread sleepThread = new SleepThread(); sleepThread.start(); }}
结果:每次间隔100ms输出一次
public static void yield()
当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
子线程
public class YieldThread extends Thread{ @Override public void run() { for (int i=1;i<p><strong>测试类</strong></p><pre class="brush:php;toolbar:false">public class Test01 { public static void main(String[] args) { YieldThread yieldThread01 = new YieldThread(); YieldThread yieldThread02 = new YieldThread(); yieldThread01.start(); yieldThread02.start(); }}
结果:基本都会交替进行,也会有一个线程连输出
当一个线程调用了join方法,这个线程就会先被执行,它执行结束以后才可以去执行其余的线程,必须先start,再join才有效
子线程
public class JoinThread extends Thread{ @Override public void run() { for (int i=1;i<p><strong>测试类</strong></p><pre class="brush:php;toolbar:false">public class Test01 { public static void main(String[] args) throws InterruptedException { for (int i=1;i<p><strong>结果:当主线程打印到5的时候,这时候子线程加入进来,就先执行完子线程,在执行主线程</strong><br><img src="https://img.php.cn/upload/article/000/000/067/da3747d2bd3ee6a154b0a90e8cc9f0a7-14.png" alt="En savoir plus sur le mécanisme multithread Java"></p><h2>4.4 守护线程(setDaemon)</h2><blockquote><p>将子线程设置为主线程的伴随线程,主线程停止的时候,子线程也不要继续执行了<br> 注意:<strong>先设置,在启动</strong></p></blockquote><p><strong>子线程</strong></p><pre class="brush:php;toolbar:false">public class TestThread extends Thread{ @Override public void run() { for(int i=1;i<p><strong>测试类</strong></p><pre class="brush:php;toolbar:false">public class Test01 { public static void main(String[] args) throws InterruptedException { TestThread daemonThread = new TestThread(); daemonThread.setDaemon(true);//设置守护线程 daemonThread.start(); for (int i=1;i<p><strong>结果:当主线程结束时,子线程也跟着结束,并不会继续执行下去打印输出</strong><br><img src="https://img.php.cn/upload/article/000/000/067/da3747d2bd3ee6a154b0a90e8cc9f0a7-15.png" alt="En savoir plus sur le mécanisme multithread Java"></p><h2>4.5 线程优先级(setPriority)</h2><blockquote><p>线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多<br><img src="https://img.php.cn/upload/article/000/000/067/da3747d2bd3ee6a154b0a90e8cc9f0a7-16.png" alt="En savoir plus sur le mécanisme multithread Java"></p></blockquote><p><strong>子线程</strong></p><pre class="brush:php;toolbar:false">public class TestThread extends Thread{ @Override public void run() { for(int i=1;i<p><strong>测试</strong></p><pre class="brush:php;toolbar:false">public class Test01 { public static void main(String[] args) throws InterruptedException { TestThread th1 = new TestThread(); TestThread th2 = new TestThread(); TestThread th3 = new TestThread(); th1.setPriority(10);//设置线程1优先级10 th1.start(); th2.start();//线程2优先级默认不变,为5 th3.setPriority(1);//设置线程3优先级为1 th3.start(); }}
结果:优先级(th1>th2>th3)线程3应该在最后打印
需求:模拟三个窗口,每个窗口有100个人,同时抢10张票
使用继承Runnable接口的方法
public class BuyTicketRunnable implements Runnable{ private int ticketNum=10; @Override public void run() { for(int i=1;i<p><strong>测试方法</strong></p><pre class="brush:php;toolbar:false">public class BuyTicketTest { public static void main(String[] args) { // TODO Auto-generated method stub Runnable runnable=new BuyTicketRunnable(); Thread th1=new Thread(runnable,"窗口1"); Thread th2=new Thread(runnable,"窗口2"); Thread th3=new Thread(runnable,"窗口3"); th1.start(); th2.start(); th3.start(); }}
结果
我们发现,不同窗口会抢到同一张票!!!,这在实际情况是不允许的,这是因为多个线程,在争抢资源的过程中,导致共享的资源出现问题。一个线程还没执行完,另一个线程就参与进来了,开始争抢。(但窗口2抢到第10张票,还没来得及
ticketNum--
操作,时间片就用完了,随后被窗口三抢到CPU资源,此时的票数还是10,窗口三也抢到第十张票,也还没来得及ticketNum--
操作窗口三时间片由完了,窗口一抢到CPU资源,还是买到了第10张票)
多线程安全问题:
- 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致
- 临界资源:共享资源(同一对象),一次只能允许一个线程使用,才可以保证其正确性
- 原子操作:不可分割的多步操作,被视作一个整体,其顺序和步骤不可被打乱或缺省
synchronized
(同步监视器)
- 必须是引用数据类型,不能是基本数据类型
- 也可以创建一个专门的同步监视器,没有任何业务含义 (new Object)
- 一般使用共享资源做同步监视器即可
- 在同步代码块中不能改变同步监视器对象的引用
- 尽量不要String和包装类Integer做同步监视器,建议使用final修饰同步监视器
对卖票案例改进
public class BuyTicketRunnable implements Runnable{ static Object obj=new Object(); private int ticketNum=10; @Override public void run() { for(int i=1;i<blockquote><ul> <li>多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块,其他线程无法访问其中的任何一个代码块</li> <li>多个代码块使用了同一个同步监视器(锁),锁住一个代码块的同时,也锁住所有使用该锁的所有代码块, 但是没有锁住使用其他同步监视器的代码块,其他线程有机会访问其他同步监视器的代码块</li> </ul></blockquote><h2>5.3 同步方法</h2><blockquote> <p><code>synchronized</code>(同步方法)</p> <ul> <li>不要将run()定义为同步方法</li> <li>非静态同步方法的同步监视器是this;静态同步方法(static)的同步监视器是 类名.class 字节码信息对象</li> <li>同步代码块的效率要高于同步方法(原因:同步方法是将线程挡在了方法的外部,而同步代码块锁将线程挡在了代码块的外部,但是却是方法的内部)</li> <li>同步方法的锁是this,一旦锁住一个方法,就锁住了所有的同步方法;同步代码块只是锁住使用该同步监视器的代码块,而没有锁住使用其他监视器的代码块</li> </ul> </blockquote><p><strong>买票案例改进</strong></p><pre class="brush:php;toolbar:false">public class BuyTicketRunnable implements Runnable{ private int ticketNum=10; @Override public void run() { for(int i=1;i0) { System.out.println("在"+Thread.currentThread().getName()+"买到了第"+ticketNum+"张票!"); ticketNum--; } }}
Lock
锁:
- DK1.5后新增新一代的线程同步方式:Lock锁,与采用synchronized相比,lock可提供多种锁方案,更灵活
- synchronized是Java中的关键字,这个关键字的识别是靠JVM来识别完成的呀。是虚拟机级别的。
但是Lock锁是API级别的,提供了相应的接口和对应的实现类,这个方式更灵活,表现出来的性能优于之前的方式。
对买票案例改进
import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class BuyTicketRunnable implements Runnable{ private int ticketNum=10; Lock lock=new ReentrantLock();//接口=实现类 可以使用不同的实现类 @Override public void run() { for(int i=1;i0) { System.out.println("在"+Thread.currentThread().getName()+"买到了第"+ticketNum+"张票!"); ticketNum--; } }catch(Exception e) { e.printStackTrace(); }finally { //关闭锁:--->即使有异常,这个锁也可以得到释放 lock.unlock(); } } }}
Lock和synchronized的区别
- Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
*案例:男孩女孩一起去吃饭,但是桌子上只有两根筷子,如果两个人同时抢到一根筷子而不放弃,这样两个人都吃不上饭,这样就形成死锁了;必须要有一个人放弃争抢,等待另一个人用完,释放资源,这个人之后才会获得两根筷子,两个人才能都吃上饭 *
package 多线程;class Eat{ //代表两个筷子 public static Object o1=new Object(); public static Object o2=new Object(); public static void eat() { System.out.println("可以吃饭了"); }}class BoyThread extends Thread{ public void run() { synchronized (Eat.o1) { System.out.println("男孩拿到了第一根筷子!"); synchronized (Eat.o2) { System.out.println("男孩拿到了第二根筷子!"); Eat.eat(); } } }}class GirlThread extends Thread{ public void run() { synchronized (Eat.o2) { System.out.println("女孩拿到了第二根筷子!"); synchronized (Eat.o1) { System.out.println("女孩拿到了第一根筷子!"); Eat.eat(); } } }}public class MyLock { public static void main(String[] args) { BoyThread boy=new BoyThread(); GirlThread girl=new GirlThread(); boy.start(); girl.start(); }}
结果
解决办法
先让男孩拿到筷子,线程休眠一下,等待男孩用完筷子,在启动女孩线程
public class MyLock { public static void main(String[] args) { BoyThread boy=new BoyThread(); GirlThread girl=new GirlThread(); boy.start(); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } girl.start(); }}
在写程序中要避免这种死锁:减少同步资源的定义,避免嵌套同步
在Java对象中,有两种池
- 锁池(
synchronized
)- 等待池(
wait()
;notify()
;notifyAll()
)
如果一个线程调用了某个对象的wait方法,那么该线程进入到该对象的等待池中(并且已经将锁释放);
如果未来的某个时刻,另外一个线程调用了相同的对象notify方法或者notifyAll方法,那么该等待池中的线程就会被唤醒,然后进入到对象的锁池里面去获得该对象的锁;
如果获得锁成功后,那么该线程就会沿着wait方法之后的路径继续执行。注意:沿着wait方法之后执行
- wait():的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。直到其他线程调用此对象的
notify()
方法或notifyAll()
方法,当前线程被唤醒(进入就绪状态)- wait(long timeout):让当前线程处于“等待(阻塞)状态,直到其他线程调用此对象的
notify()
方法或notifyAll()
方法,或者超过指定的时间量”,当前线程被唤醒(进入就绪状态)
sleep和wait的区别:sleep进入阻塞状态没有释放锁,wait进入阻塞状态但是同时释放了锁
notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程
- notify()是唤醒单个线程
- notifyAll()是唤醒所有的线程
案例:
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费。
如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。
功能分解一:商品类
public class Product {//商品类 private String name;//名字 private String brand;//品牌 boolean flag = false;//设置标记,false表示商品没有,等待生产者生产 public synchronized void setProduct(String name, String brand) {//生产商品,同步方法,锁住的是this if (flag == true) {//如果flag为true,代表有商品,不生产,等待消费者消费 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //生产商品 this.setName(name); this.setBrand(brand); System.out.println("生产者生产了" +this.getBrand() +this.getName()); //生产完,设置标志 flag = true; //唤醒消费线程 notify(); } public synchronized void getProduct() { if (flag == false) {//如果是false,则没有商品,等待生产者生产 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //如果有商品,消费 System.out.println("消费者消费了" + this.getBrand() +this.getName()); //设置标志 flag = false; //唤醒线程 notify(); } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setBrand(String brand) { this.brand = brand; } public String getBrand() { return brand; }}
功能分解二:生产者线程
public class ProducterThread extends Thread {//生产者线程 private Product p; public ProducterThread(Product p) { this.p = p; } @Override public void run() { for (int i = 1; i <p><strong>功能分解三:消费者线程</strong></p><pre class="brush:php;toolbar:false">public class CustomerThread extends Thread {//消费者线程 private Product pro; public CustomerThread(Product pro) { this.pro = pro; } @Override public void run() { for (int i = 1; i <p><strong>功能分解四:测试类</strong></p><pre class="brush:php;toolbar:false">public class Test { public static void main(String[] args) { Product p = new Product(); ProducterThread pth = new ProducterThread(p); CustomerThread cth = new CustomerThread(p); pth.start(); cth.start(); }}
结果:生产者生产一件商品,消费者消费一件商品,交替进行
Apprentissage recommandé : "Tutoriel vidéo 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!