多线程入门知识点及代码详解及相关面试八股文

news2024/11/16 16:47:17

多线程详解及相关面试八股文

多线程

线程概述

* 多线程:
 进程:当前正在运行的程序,一个应用程序在内存中的执行区域
 线程:进程中的一个执行控制单元,执行路径
 * 一个进程可以有一个线程,也可以有多个线程
 * 单线程:安全性高,但是效率低
 * 多线程:安全性低,效率高
 * 多线程案例:360,迅雷等
进程

进程相当于公司,多线程相当于公司的多个程序员。

线程

单线程特点:安全性高,一件事交给一个人干,确定性的知道干到哪里了。但是效率低
多线程特点: 安全性低因为一个人干一部分,需要对接,每个人的写法不一样,对接出现问题,一个人的错误导致整体的问题。但是效率高
例如:杀毒软件同时做多件事情,电脑体检、木马查杀、电脑清理等。在这里插入图片描述
多线程的优点:提高效率

1.多线程的实现方式一

参考资料:继承Thread类 & 实现Runnable接口 使用解析

继承Thread方法简介

在这里插入图片描述
详细实现步骤:

  1. 创建线程类(继承自Thread类)
  2. 复写run ()(定义线程行为)
  3. 创建线程对象(即实例化线程类)
  4. 通过线程对象控制线程的状态(如运行、睡眠。挂起1停止)

多线程的实现方式:
1.将类声明为Thread的子类,该子类应该重写thread类的run方法,
2.接下来可以分配并启动该子类的实例.

1.新建一个类,并将类声明为Thread的子类,该子类应该重写thread类的run方法。

package Thread;
//标准的多线程实现的类
public class MyThread extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

2.创建多线程实例

public class ThreadDemo1 {
    public static void main(String[] args) {
//        创建线程实例 声明实例
        MyThread t = new MyThread();
//        启动多线程
        t.start();    //执行一次 输出0-99,t.run和普通的方法一样,所以启动多线程不能用run()

    }
}

输出0-99,t.run和普通的方法一样,所以启动多线程不能用run()

线程相关方法

void setName(String name)
改变线程名称,使之与参数 name 相同。
String getName()
返回该线程的名称 。

getname的源码(点击ctrl+getName)

 public final String getName() {	//不能被重写,但是可以直接调用因为是public
        return name;
    }

void start()
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
void run()
如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。

多线程实现方法一代码

这个例子能够更加清晰每一个结果窒息的是第一个线程还是第二个线程。
1.创建多线程实现类

package Thread;
//标准的多线程实现的类
public class MyThread extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println(getName()+i);
        }
    }
}

2.分配并启动该子类的实例

package Thread;
/*多线程的实现方式:
* 1.将类声明为Thread的子类,该子类应该重写thread类的run方法,
* 接下来可以分配并启动该子类的实例*/
public class ThreadDemo1 {
    public static void main(String[] args) {
//        创建线程实例 声明实例
        MyThread t1 = new MyThread();
//        启动线程
//        t.run();    //执行一次 输出0-99,t.run和普通的方法一样,所以启动多线程不能用run() t2.run()就会先执行t1,再执行t2
       t1.setName("第一个线程");
        t1.start();

        MyThread t2= new MyThread();
        t2.setName("第二个线程");
        t2.start();
    }
}

运行结果:多个线程同时运行,什么时候执行1什么时候执行2?是CPU随机执行的,可以通过设置优先级,给另外一个线程睡眠时间来设置执行的顺序。
在这里插入图片描述
在这里插入图片描述

2.多线程实现方法二:通过Runnable接口来实现

Runnable接口实现多线程简介

在这里插入图片描述

  1. 创建线程辅助类(实现Runnable接口)
  2. 复写run ()(定义线程行为)
  3. 创建线程辅助对象(即实例化线程辅助类)
  4. 创建线程对象
    - 即实例化线程类
    - 线程类= Thread类
    - 创建时传入线程辅助类对象
  5. 通过线程对象控制线程的状态(如运行、睡眠、挂起1停止)
多线程实现方法2:实现Runnable接口的类,该类然后实现run方法,
然后可以分配该类的实例,在创建Thread时作为一个参数来传递并启动
Thread(Runnable target)
 *	static Thread currentThread() :返回当前线程对象

注意:

  • Java中真正能创建新线程的只有Thread类对象
  • -通过实现Runnable的方式,最终还是通过Thread类对象来创建线程
    所以对于实现了Runnable接口的类,称为线程辅助类Thread类才是真正的线程类
主方法是多线程的吗?

主方法main是单线程的。按照顺序执行方法。但是可以在主线程中调用多线程的程序。

package com.demo01;
/*
 * 主方法是多线程吗?
 * 		主方法是单线程的
 * 
 */
public class ThreadDemo3 {
	public static void main(String[] args) {
		method();
		function();	//顺序执行
	}
	public static void method() {}
	public static void function() {}
}

Runnable是一个抽象的接口
在这里插入图片描述

Runnable

API中Runnable的基本使用
在这里插入图片描述

在这里插入图片描述

currentThread

currentThread 返回对当前正在执行的线程对象的引用。

  • 当前正在执行的线程:在一个多线程程序中,有多个线程同时运行。每个线程代表了程序中的一个独立执行流。在某一时刻,只有一个线程会处于活动状态,也就是正在执行代码。这个活动的线程就被称为当前正在执行的线程。
  • 引用:则表示对这个线程对象的访问,可以用来执行操作或查询信息。
    在这里插入图片描述
多线程实现方法二代码
MyTheadpackage Thread.demo2;
/*多线程实现方法2:实现Runnable接口的类,该类然后实现run方法,
然后可以分配该类的实例,在创建Thread时作为一个参数来传递并启动
Thread(Runnable target)
 *	static Thread currentThread() :返回当前线程对象
 *	既然有了继承Thread为何还要整出来实现Runnable?*/
public class MyThread2 implements Runnable{
    int num;
    public MyThread2(int num){
        this.num = num;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            /*Thread t = Thread.currentThread();
            System.out.println(t.getName()+":"+i);*/
//            链式编程
            System.out.println(Thread.currentThread().getName()+":"+i+"参数"+num++);
        }
    }
}
分配该类的实例
package Thread.demo2;
/*多线程实现方法2:实现Runnable接口的类,该类然后实现run方法,
然后可以分配该类的实例,在创建Thread时作为一个参数来传递并启动
Thread(Runnable target)
 *	static Thread currentThread() :返回当前线程对象
 *	既然有了继承Thread为何还要整出来实现Runnable?*/
public class ThreadTwo {
    public static void main(String[] args) {
        method();
    }

//    创建多个实例实现多线程
    private static void method(){
//        实例1
        MyThread2 mt = new MyThread2(1);
        Thread t = new Thread(mt);
        t.setName("李四");
        t.start();
//实例2
        MyThread2 mt2 = new MyThread2(1);
        Thread t2 = new Thread(mt2);
        t2.setName("老王");
        t2.start();
    }

//    只创建一个线程类的实例,但是多个线程对象实现多线程
    private static void method2(){
        MyThread2 mt = new MyThread2(1);
        Thread t = new Thread(mt);
        t.setName("李四");
        t.start();

        Thread t2 = new Thread(mt);
        t2.setName("王五");
        t2.start();
    }
}

运行结果:

老王:1参数2
老王:2参数3
李四:7参数8
老王:3参数4
老王:4参数5
老王:5参数6
老王:6参数7
老王:7参数8
李四:8参数9
李四:9参数10
李四:10参数11
李四:11参数12
李四:12参数13
老王:8参数9
.......
老王:95参数96
老王:96参数97
老王:97参数98
李四:94参数95
李四:95参数96
李四:96参数97
李四:97参数98
李四:98参数99
李四:99参数100
老王:98参数99
老王:99参数100

通过结果发现执行的顺序是乱的,第一个线程没有执行完就执行了第二个线程,这就是造成了线程不安全的现象。

只实例化一个对象,启动多个线程。两个线程共用一个参数
在这里插入图片描述

既然有继承Thread的方法为什么还要Runnable方法?

总结:
1.因为java是单继承的,如果使用了继承Thread类那么就不能继承其他的类了,但是java是支持继承多个接口的
2.继承Thread必须创建多个实例对象,这些线程就会相对独立,无法共享资源,同时也会增加和浪费资源空间,并且多次创建和销毁线程非常的消耗系统资源。
3.Runnable避免了单继承的局限性,还可以不用构建多个实例,可以只实例化一个对象,多个线程共享一个参数,具体见方法二的代码。所以通常情况下用Runnable的情况更多。
在这里插入图片描述

3.多线程实现火车站卖火车票

火车站的窗口一直开着 while(true),卖掉一张窗口休息(sleep)100毫秒,睡的过程中窗口2进来了,窗口2睡了窗口3进来了。
1.构建一个类实现多个窗口卖票
TicketTest线程创建类:

package Thread.TicketTest;

public class TicketTest {
    public static void main(String[] args) {
        TicketThread tt = new TicketThread();   //只创建一个线程类的实例
//        创建多个线程对象
        Thread t1 = new Thread(tt);
        t1.setName("窗口1");
        Thread t2 = new Thread(tt);
        t2.setName("窗口2");
        Thread t3 = new Thread(tt);
        t3.setName("窗口3");
//        启动线程对象
        t1.start();
        t2.start();
        t3.start();
    }
}

2.线程方法实现类

package Thread.TicketTest;

import com.sun.xml.internal.ws.api.ha.StickyFeature;

/*火车站卖火车票模拟

 * */
public class TicketThread implements Runnable {
    int ticket = 100;   //总的票数

    @Override
    public void run() {
        while (true) {  //表示售票的窗口一直都是开着的
            if (ticket > 0) {
                try {
                    Thread.sleep(100); // 线程休眠100毫秒,模拟售票过程中的一些处理时间
                } catch (InterruptedException e) {  //捕获线程中断时的异常
                    e.printStackTrace(); // 处理InterruptedException异常,通常是线程被中断时抛出的异常
                }
                System.out.println(Thread.currentThread().getName() + ":" + ticket--);
                // 打印当前线程的名称和票号,并将票号减1,表示售出一张票
            }
        }
    }
}

结果:

窗口3:100
窗口2:98
窗口1:99
窗口2:97
窗口3:95
窗口1:96
窗口1:94
窗口3:92
窗口2:93
窗口2:91
窗口1:89
窗口3:90
窗口3:88
窗口1:86
窗口2:87
窗口1:84
窗口2:85
窗口3:83
窗口2:82
窗口3:80
窗口1:81
窗口1:79
窗口2:78
窗口3:77
窗口3:76
窗口2:74
窗口1:75
窗口3:73
窗口1:71
窗口2:72
窗口1:69
窗口2:70
.......
窗口2:13
窗口3:12
窗口2:11
窗口1:10
窗口3:9
窗口1:8
窗口2:7
窗口2:6
窗口1:4
窗口3:5
窗口3:3
窗口1:2
窗口2:1
窗口3:-1
窗口1:0

由上面的结果可以看出,一个线程执行没有结束,第二个线程又执行了,甚至修改了上一个线程的数据,例如窗口3以为还有1张票,但是实际上没有了,窗口3还继续卖,造成了-1的结果。
多线程卖票出现的问题原因:共享的数据被第一个线程取到后,第二个线程把这个共享数据修改了,但是第一个线程不知道被修改了。例如上厕所的例子。

* 问题出现的原因:
 * 		要有多个线程
 * 		要有被多个线程所共享的数据 ticket火车总票数
 * 		多个线程并发的访问共享的数据 ticket火车总票数
 * 
 * 在火车上上厕所
 * 张三来了,一看门是绿的,他就进去了,把门锁上了,门就变红了
 * 李四来了,一看门是红色的,他就只能憋着
 * 张三用完了厕所,把锁打开了,门就变成了绿色
 * 李四一看门变绿了,他就进去了,把门锁上,门就变红了
 * 王五来了,一看门是红色的,他也只能憋着
 * 李四用完厕所了,把锁打开了,肚子又不舒服了,扭头回去了,又把门锁上了

同步锁synchronized关键字|同步代码块和同步方法

锁要被所有的线程可以共用,安全性高,效率低,没有锁效率低,安全性高
需要加锁的代码越短越好,就说明占用的时间越短。alit+shift+m抽取出代码 再加锁
方法里面的锁对象默认是this,就是new的线程对象的本身。
synchronized定义:同步(锁),可以修饰代码块和方法,被修饰的代码块和方法一旦被某个线程访问,则直接锁住,其他的线程将无法访问。

同步方法:使用关键字synchronized修饰的方法,一旦被一个线程访问,则整个方法全部锁住,其他线程则无法访问
 * synchronized
 * 注意:
 * 		非静态同步方法的锁对象是this
 * 		静态的同步方法的锁对象是当前类的字节码对象

代码块加锁(同步代码块):
同步:安全性高,效率低
非同步:效率高,但是安全性低

同步代码块:
 * 			synchronized(锁对象){
 * 
 * 			}
public class TicketThread implements Runnable {
    int ticket = 100;   //总的票数
    Object object = new Object();   //用一个对象当做锁对象
    @Override
    public void run() {
        while (true) {  //表示售票的窗口一直都是开着的
            synchronized (object){  //代码块加锁
                if (ticket > 0) {
                    try {
                        Thread.sleep(100); // 线程休眠100毫秒,模拟售票过程中的一些处理时间
                    } catch (InterruptedException e) {  //捕获线程中断时的异常
                        e.printStackTrace(); // 处理InterruptedException异常,通常是线程被中断时抛出的异常
                    }
                    System.out.println(Thread.currentThread().getName() + ":" + ticket--);
                    // 打印当前线程的名称和票号,并将票号减1,表示售出一张票
                }
            }
        }
    }
}

方法加锁:

public class TicketThread implements Runnable {
    int ticket = 100;   //总的票数
    Object object = new Object();   //用一个对象当做锁对象
@Override
   public void run() {
       while (true) {  //表示售票的窗口一直都是开着的
               extracted();	//调用加锁的方法
       }
   }
//方法加锁
    private synchronized void extracted() {  //方法抽取快捷键 ctrl+alt+M
        if (ticket > 0) {
            try {
                Thread.sleep(100); // 线程休眠100毫秒,模拟售票过程中的一些处理时间
            } catch (InterruptedException e) {  //捕获线程中断时的异常
                e.printStackTrace(); // 处理InterruptedException异常,通常是线程被中断时抛出的异常
            }
            System.out.println(Thread.currentThread().getName() + ":" + ticket--);
            // 打印当前线程的名称和票号,并将票号减1,表示售出一张票
        }
    }
}

静态方法
用于执行与类相关但与特定对象无关的操作。

package Thread.SynchronizedDemo;
public class TicketThread implements Runnable {
  static int ticket = 100;   //总的票数
    Object object = new Object();   //用一个对象当做锁对象
  
   @Override
   public void run() {
       while (true) {  //表示售票的窗口一直都是开着的
               extracted2();
       }
   }   
    private static synchronized void extracted2() {  //方法抽取快捷键 ctrl+alt+M
        if (ticket > 0) {
            try {
                Thread.sleep(100); // 线程休眠100毫秒,模拟售票过程中的一些处理时间
            } catch (InterruptedException e) {  //捕获线程中断时的异常
                e.printStackTrace(); // 处理InterruptedException异常,通常是线程被中断时抛出的异常
            }
            System.out.println(Thread.currentThread().getName() + ":" + ticket--);
            // 打印当前线程的名称和票号,并将票号减1,表示售出一张票
        }
    }
}

执行结果:

窗口1:100
窗口1:99
窗口1:98
窗口1:97
窗口1:96
窗口3:95
窗口3:94
窗口3:93
窗口3:92
窗口3:91
窗口3:90
窗口3:89
窗口3:88
窗口3:87
窗口3:86
窗口3:85
窗口2:84
窗口2:83
窗口3:82
窗口1:81
窗口1:80
窗口1:79
窗口1:78
窗口3:77
窗口3:76
窗口2:75
窗口3:74
窗口3:73
窗口3:72
窗口3:71
窗口3:70
窗口3:69
窗口3:68
窗口3:67
。。。。。
窗口3:25
窗口3:24
窗口3:23
窗口3:22
窗口3:21
窗口3:20
窗口2:19
窗口3:18
窗口3:17
窗口1:16
窗口3:15
窗口2:14
窗口3:13
窗口3:12
窗口3:11
窗口3:10
窗口1:9
窗口1:8
窗口1:7
窗口1:6
窗口1:5
窗口1:4
窗口1:3
窗口1:2
窗口1:1

多线程相关的面试题总结

参考:图解线程安全

线程安全是什么?

参考:一文教会你什么线程安全以及如何实现线程安全
线程安全定义:在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且准确的执行,不会出现数据污染等意外情况。
换言之,线程安全就是某个函数在并发环境中调用时,能够处理好多个线程之间的共享变量,是程序能够正确执行完毕。也就是说我们想要确保在多线程访问的时候,我们的程序还能够按照我们的预期的行为去执行,那么就是线程安全了。
要考虑线程安全问题,就需要先考虑Java并发的三大基本特性:原子性、可见性以及有序性

  • 原子性
    在一个操作中就是cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成,要不都不执行。
    就好比转账,从账户A向账户B转1000元,那么必然包括2个操作:从账户A减去1000元,往账户B加上1000元。2个操作必须全部完成。
  • 可见性
    当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
    若两个线程在不同的cpu,那么线程1改变了i的值还没刷新到主存,线程2又使用了i,那么这个i值肯定还是之前的,线程1对变量的修改线程没看到这就是可见性问题。
  • 有序性
    程序执行的顺序按照代码的先后顺序执行,在多线程编程时就得考虑这个问题。
出现线程安全问题的原因?

出现线程安全问题的原因:
在多个线程并发环境下,多个线程共同访问同一共享内存资源时,其中一个线程对资源进行写操作的中途(写⼊入已经开始,但还没 结束),其他线程对这个写了一半的资源进⾏了读操作,或者对这个写了一半的资源进⾏了写操作,导致此资源出现数据错误。

如何避免线程安全问题?
  • 保证共享资源在同一时间只能由一个线程进行操作(原子性,有序性 即使用synchronizedvolatileLock
  • 将线程操作的结果及时刷新,保证其他线程可以立即获取到修改后的最新数据(可见性)。

线程安全通俗例子:
线程安全可以通过一个通俗的例子来解释。
假设你经营着一个小咖啡馆,你的咖啡馆有一个共用的账单本(记录客户点餐和付款的情况),并且有多个服务员在同时接待客人。现在,我们来看两种不同的情况,一种是线程不安全的,另一种是线程安全的:

情况一:线程不安全

在这种情况下,服务员没有采取任何特殊措施来确保账单本的安全。多个服务员可以同时写入账单本,但没有同步机制来保护它。这可能导致以下问题:

  • 当两名服务员同时写入账单本时,可能会出现写入冲突,其中一个服务员的记录可能会覆盖另一个的记录。
  • 当一名服务员正在写入账单本时,另一名服务员可能会同时读取它,导致不一致的数据。

结果是,账单本可能包含不正确或丢失的订单和付款信息,这会让你的咖啡馆的财务记录变得混乱,客户可能会被多次收款或遗漏。

情况二:线程安全

在这种情况下,你采取了一些措施来确保账单本的线程安全。可能的方法包括:

  • 使用一个锁(例如,只允许一个服务员同时写入账单本)来确保同时只有一个服务员能够写入。
  • 或者,使用一个专门的工作人员来负责维护账单本,其他服务员只能通过这个工作人员来提交订单和付款信息。

这样,无论多少服务员同时工作,账单本都会保持一致性,不会发生数据丢失或混乱。

在这个例子中,线程安全就像是保护账单本的机制,防止多个服务员同时修改它,从而确保了数据的一致性和准确性。线程不安全则代表了没有这种保护机制,可能导致数据损坏和错误。在软件开发中,线程安全的概念类似于这个例子,用来确保多线程环境下数据的正确性和稳定性。

线程同步是什么?如何保证线程同步?

**线程同步的定义:**用于协调多个线程之间的执行,以确保数据的一致性和避免竞态条件(Race Condition)。在多线程环境中,多个线程可能会同时访问和修改共享的资源,如果不进行适当的同步措施,就会导致数据不一致、不确定性和程序错误。
**线程同步的目标:**让多个线程协调工作,以确保它们在访问共享资源时不会发生冲突,从而保持数据的正确性。
Java 提供了一系列的关键字和类来保证线程安全。

synchronized关键字(见上几节同步锁synchronized)

1. 保证方法或代码块操作的原子性
Synchronized 保证⽅法内部或代码块内部资源(数据)的互斥访问。即同⼀时间、由同⼀个 Monitor(监视锁) 监视的代码,最多只能有⼀个线程在访问。
2.保证监视资源的可见性
保证多线程环境下对监视资源的数据同步。即任何线程在获取到 Monitor 后的第⼀时间,会先将共享内存中的数据复制到⾃⼰的缓存中;任何线程在释放 Monitor 的第⼀时间,会先将缓存中的数据复制到共享内存中。
3.保证线程间操作的有序性
Synchronized 的原子性保证了由其描述的方法或代码操作具有有序性,同一时间只能由最多只能有一个线程访问,不会触发 JMM 指令重排机制。

Volatile 关键字

保证被 Volatile 关键字描述变量的操作具有可见性和有序性(禁止指令重排)

注意:
1.Volatile 只对基本类型 (byte、char、short、int、long、float、double、boolean) 的赋值
操作和对象的引⽤赋值操作有效。
2 对于 i++ 此类复合操作, Volatile 无法保证其有序性和原子性。
3.相对 Synchronized 来说 Volatile 更加轻量一些。
作者:七彩祥云至尊宝
链接:https://juejin.cn/post/6844903890224152584

Lock 关键字 更加灵活

Lock 也是 java.util.concurrent 包下的一个接口,定义了一系列的锁操作方法。Lock 接口主要有 ReentrantLockReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock 实现类。与 Synchronized 不同是 Lock (手动释放)提供了获取锁和释放锁等相关接口,使得使用上更加灵活,同时也可以做更加复杂的操作,如:

// 创建一个可重入读写锁(ReentrantReadWriteLock)
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

// 获取读锁
Lock readLock = lock.readLock();

// 获取写锁
Lock writeLock = lock.writeLock();

// 定义一个私有整数变量 x,该变量将在多线程环境下被读取和修改
private int x = 0;

// 一个用于增加 x 值的方法,需要获取写锁
private void count() {
    writeLock.lock(); // 获取写锁,独占写权限
    try {
        x++; // 增加 x 的值
    } finally {
        writeLock.unlock(); // 释放写锁,允许其他线程写入
    }
}

// 一个用于打印 x 值的方法,可以同时被多个线程调用,获取读锁
private void print(int time) {
    readLock.lock(); // 获取读锁,允许多个线程同时读取
    try {
        for (int i = 0; i < time; i++) {
            System.out.print(x + " "); // 打印 x 的值
        }
        System.out.println(); // 换行
    } finally {
        readLock.unlock(); // 释放读锁,允许其他线程读取
    }
}

锁源码解析

synchronizedvolatileLock三者区别

下面是关于synchronizedvolatileLock之间的区别的表格描述:

特性synchronizedvolatileLock
类型关键字关键字接口
用途用于实现线程互斥用于保证可见性用于实现线程互斥和更多功能
适用范围方法、代码块、实例变量实例变量或类变量任何代码块
锁粒度细粒度锁,可精确指定粗粒度锁,适用于变量可以自由选择粒度
同步方式自动加锁和解锁不需要显式加锁和解锁需要显式加锁和解锁
性能性能相对较低性能较高,适用于可见性问题性能灵活,可根据需求选择
可重入性支持不支持支持
支持条件变量不直接支持不支持支持
适用于复杂情况适用不适用适用
锁的释放方式自动释放(退出同步块)自动释放(写入完成后释放)手动释放
异常处理异常时自动释放锁无需处理异常需要显式处理异常
可中断性支持不支持支持
死锁检测不支持不支持支持
公平性默认非公平不适用可以选择公平或非公平
自定义锁策略有限制不适用可以自定义锁策略

这个表格总结了synchronizedvolatileLock之间的主要区别。需要注意的是,选择哪种同步机制取决于具体的应用需求和性能要求。synchronized通常是最简单和最常见的选择,而volatile通常用于确保可见性。Lock则提供了更大的灵活性和控制,适用于复杂的同步需求。

线程的生命周期是什么样子?

在这里插入图片描述

  1. 新建 创建线程对象 TicketThread t = new TicketThread();
  2. 就绪 具备了执行的条件,没有具备执行的权利 只有CPU给了他执行权利才可以进入下一环节。 t.start();
  3. 运行 CPU给了线程执行的权利 执行t.start();
  4. 死亡 线程对象变成了垃圾
  5. 等待(可能存在)wait() 等待 notify()唤醒
    在这里插入图片描述
wait()

wait是Object类里面的方法。

public final void wait()
                throws InterruptedException

定义: ==在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。 ==
当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。

对于某一个参数的版本,实现中断和虚假唤醒是可能的,而且此方法应始终在循环中使用:

synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
     }

此方法只应由作为此对象监视器的所有者的线程来调用。有关线程能够成为监视器所有者的方法的描述,请参阅 notify 方法。

抛出:
IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。

notify()
public final void notify()

定义:唤醒在此对象监视器上等待的单个线程。
如果所有线程都在此对象上等待,则会选择**唤醒其中一个线程。**选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
**直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。**被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。
此方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:

  • 通过执行此对象的同步实例方法。
  • 通过执行在此对象上进行同步的 synchronized 语句的正文。
  • 对于 Class类型的对象,可以通过执行该类的同步静态方法。
    一次只能有一个线程拥有对象的监视器。
    抛出:
    IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。
notifyAll()

public final void notifyAll()唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。
此方法只应由作为此对象监视器的所有者的线程来调用。有关线程能够成为监视器所有者的方法的描述,请参阅 notify 方法。
抛出: IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1047703.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

B+tree 与 B-tree区别

原理:分批次的将磁盘块加载进内存中进行检索,若查到数据,则直接返回,若查不到,则释放内存,并重新加载同等数据量的索引进内存,重新遍历 结构: 数据 向下的指针 指向数据的指针 特点: 1&#xff0c;节点排序 2 .一个节点了可以存多个元索&#xff0c;多个元索也排序了 结构: 数…

vscode 打开后 默认terminal power shell 报错 名为“.C”的驱动器不存在。

这是 默认terminal power shell 打开报的错 Test-Path : 找不到驱动器。名为“.C”的驱动器不存在。 所在位置 C:\Users\HUAWEI\Documents\WindowsPowerShell\profile.ps1:4 字符: 5 If (Test-Path "C:\Users\HUAWEI\AppData\Local\Temp\_MEI319962\Scripts\ ... …

Java列表查询Long(id)到前端转换出错

Java列表查询Long到前端转换出错 问题描述排查思路思路一&#xff1a;SQL问题思路二&#xff1a;Long类型转换出错 解决方法 问题描述 做了一个列表查询功能&#xff0c;本来不应该有啥大问题的&#xff0c;但是往往事与愿违。 诶&#xff0c;你越觉得不可能出问题&#xff0c…

深入探讨 Presto 中的缓存

【squids.cn】 全网zui低价RDS&#xff0c;免费的迁移工具DBMotion、数据库备份工具DBTwin、SQL开发工具等 Presto是一种流行的开源分布式SQL引擎&#xff0c;使组织能够在多个数据源上大规模运行交互式分析查询。缓存是一种典型的提高 Presto 查询性能的优化技术。它为 Prest…

网络安全——黑客——自学

想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01;&#xff01;&#xff01; 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队…

信创办公–基于WPS的PPT最佳实践系列 (将文字转换为SmartArt)

信创办公–基于WPS的PPT最佳实践系列 &#xff08;将文字转换为SmartArt&#xff09; 目录 应用背景操作步骤第一种&#xff1a;将已有文字列表转换生成SmartArt形状。第二种&#xff1a;直接插入SmartArt形状。 应用背景 如何清晰有效的呈现文字信息&#xff1f;通常使用视觉效…

BM4 合并两个排序的链表

思路&#xff1a;先选择最小的作为Head&#xff0c;每次从两个队列中取最小的挂到Head后面&#xff0c;如果一个合并空&#xff0c;后面直接挂。此外判断几个为空链表的情况 /*** struct ListNode {* int val;* struct ListNode *next;* ListNode(int x) : val(x), next(nullp…

排序算法一 直接插入排序,希尔排序,直接选择排序,堆排序和冒泡排序

什么是排序 排序&#xff1a;排序就是使一串记录,按照其中的某个或者某些关键字的大小,递增或递减的排列起来的操作 稳定性: 假定在排序序列中,存在多个具有相同的关键字记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,存在r[i] r[j],且r[i]在 r[j] 之前&#xf…

Oracle-通过BBED强制打开数据库

前言: 在通过备份进行数据恢复时&#xff0c;经常会遇到由于备份的不完整&#xff0c;特别是归档日志的缺失&#xff0c;导致虽然数据文件已经顺利恢复&#xff0c;但是数据文件的恢复时间点没有完全一致或者数据文件的fuzzy的状态不一致&#xff0c;从而数据库无法正常的打开&…

三维模型3DTile格式轻量化压缩处理重难点分析

三维模型3DTile格式轻量化压缩处理重难点分析 在对三维模型3DTile格式进行轻量化压缩处理的过程中&#xff0c;存在一些重要而又困难的问题需要解决。以下是几个主要的重难点&#xff1a; 1、压缩率和模型质量之间的平衡&#xff1a;压缩技术的目标是尽可能地减少数据大小&…

用CRM系统提高客户转化率

影响客户转化的因素有很多&#xff0c;例如潜在客户是否真实有效、销售跟进策略、销售跟进流程和及时性等。不少企业正在使用CRM销售管理系统&#xff0c;下面说说CRM系统如何提高提高客户转化率&#xff1f; 1、甄别高质量获客渠道 CRM系统可以对获取的客户线索进行分析&…

premire 两个视频叠加显示

1 背景视频放到 v1 视频轨道 前视 视频 放到 v2 视频轨道 2 调整 前视 视频颜色参数 2.1 曲线 亮度 暗度 调整 黑白鲜明对比 2.2 基本校正 对比度 白色 黑色 饱和度 曝光 3 效果控件 -> 混合模式 -> 滤色 4 视频人物大小调整 位置调整

基于微信小程序的宠物交易商城系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言运行环境说明用户的主要功能有&#xff1a;管理员的主要功能有&#xff1a;具体实现截图详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考论文参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌…

电设备工厂的BOM管理系统

一、什么是BOM管理系统&#xff1f; BOM管理系统是一种软件工具&#xff0c;用于管理和跟踪产品的物料清单。它包括产品的组成结构、零部件的规格、数量和关联信息。BOM管理系统提供了一个集中存储和更新BOM数据的平台&#xff0c;并确保所有相关部门和团队都能够访问最新的BO…

2023工博会 | 上海添力网络营销公司 | 助力工业品线上推广

2023年9月23日&#xff0c;为期五天的工博会正式落下帷幕。本届工博会不仅有数量&#xff0c;更加有质量&#xff0c;国内外企业纷纷拿出看家本领&#xff0c;围绕着“绿色低碳”、“数字化转型”、“数字经济”、“科技创新”、“智能制造”等主题进行推陈出新。 本次工博会也…

未来预判:跨境电商全球化的下一个步伐

在当今全球化的时代&#xff0c;跨境电商已经成为了国际贸易和全球商业的主要驱动力之一。跨境电商不仅仅改变了消费者的购物方式&#xff0c;也对传统零售业产生了深远的影响。 然而&#xff0c;随着技术和市场的不断发展&#xff0c;跨境电商也在不断演化&#xff0c;其全球…

2023 现阶段H5的机型适配

个人愚见 现在的主流体验&#xff0c;是大屏手机展示更多的内容&#xff0c;并不着重于放大展示&#xff0c; 所以&#xff0c;外层布局使用vw,百分比&#xff0c;flex&#xff0c;内层直接px就行 参考&#xff1a; https://juejin.cn/post/7128051145431318535

LoadLibraryEx调用dll时有未经处理的异常,发生访问冲突

0x000000000006A220 处的第一机会异常(在 testHFHZDll.exe 中): 0xC0000005: 执行位置 0x000000000006A220 时发生访问冲突。 0x000000000006A220 处有未经处理的异常(在 testHFHZDll.exe 中): 0xC0000005: 执行位置 0x000000000006A220 时发生访问冲突。 最近做一个测试&#…

Matlab论文插图绘制模板第116期—带时间刻度的图

之前的文章中&#xff0c;分享了Matlab带线标记的图&#xff1a; 带阴影标记的图&#xff1a; 带箭头标记的图&#xff1a; 带图形标记的图&#xff1a; 带Latex公式的图&#xff1a; 进一步&#xff0c;分享一下带时间刻度的图&#xff0c;先来看一下成品效果&#xff1a; 特别…

步力宝科技爆款产品定位,开创智能物联网新商业

数据显示&#xff0c;中国处于 “亚健康”状态人口数量约占总人口的70%&#xff0c;亚健康是一种临界状态&#xff0c;指介于健康和疾病之间的状态。亚健康是一个动态演变的过程&#xff0c;既有向慢病发展的趋势&#xff0c;也能通过合理的干预使人体重返健康状态&#xff0c;…