JavaSE高级 线程

小结

  • 进程的概念:

    • 进程是运行中的程序。
  • 线程的概念:

    • 线程是属于进程的 ,一个进程可以包含多个线程。
  • 并发与并行的区别:

    • 并发:是一堆线程来抢占CPU执行自己!!
    • 并行:同时有多个线程执行!!
  • 如何开启新线程:

    • 调用start()方法。
  • Java中多线程运行原理:

    • 并发执行,出现随机性。
  • 继承类的方式创建多线程:

    a.定义一个线程类继承Thread类。
    b.重写Thread类的run()方法
    c.创建线程类的对象。
    d.调用线程类对象的start()方法启动线程
    
  • 实现接口的方式创建多线程:

    a.定义一个线程任务类实现Runnable接口。重写run()方法
    b.创建一个线程任务对象
    c.把线程任务对象包装成一个线程对象-- public Thread(Runnable target)
    d.调用线程对象的start()方法启动线程。
    
  • 实现接口方式的好处:

    实现接口以后,线程任务对象可以继续继承其他类或者继续实现其他接口,以后的功能可以扩展。
    实现接口适合做线程池。
    适合做资源共享操作
    
  • volatile关键字的作用:

    • 实现成员变量多个线程修改后的可见性。
    • 不能保证原子性
    • 禁止指令重排
  • volatile关键字和synchronized关键字的区别:

    • volatile 修饰实例变量和类变量 ; synchronized修饰同步方法、同步代码块
    • volatile保证数据的可见性,但是不保证原子性(即:多线程进行写操作,不保证线程安全);synchronized是一种排他(互斥)的机制,实现线程安全。
    • 从性能上说,volatile更好点,仅仅是对实现线程间变量的可见性上。
  • 原子类的工作机制:

    • 基于CAS乐观锁。保证了安全性。
  • [ ]原子类AtomicInteger的使用:

    • private AtomicInteger atomicInteger = new AtomicInteger();
      int count = atomicInteger.incrementAndGet(); // 底层变量+1且返回!
      
  • ConcurrentHashMap类的作用:

  • 安全性高,线程安全的,综合性能好。

  • 能够描述CountDownLatch类的作用:

  • 倒计时的计数器(-1)

  • 能够描述CyclicBarrier类的作用:

  • 可循环使用的计数器(+1)

  • 能够表述Semaphore类的作用:

  • 控制线程的并发数量

  • 能够描述Exchanger类的作用:

  • 用于线程间的数据交换

  • 能够描述Java中线程池运行原理:

  • 一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待任务,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

  • 能够描述死锁产生的原因:

  • 1.多线程间相互持有对方资源

  • 2.线程同步代码块出现了嵌套

第1章 多线程

1.1 并发与并行

  • 串行:串行运行的,即一个线程一个线程的去运行
  • 并行:指两个或多个事件在同一时刻发生(同时执行)。
  • 并发:指两个或多个事件在同一个时间段内发生(交替执行)。

在这里插入图片描述

​ 在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。

​ 而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核 CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。

注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度。

1.2 线程与进程

  • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
  • 线程:是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

进程

在这里插入图片描述

线程

在这里插入图片描述

进程与线程的区别

  • 进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
  • 线程:堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。

注意:下面内容为了解知识点

1:因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。

2:Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。

3:由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。

区别

  • 进程:

  • 1.是指一个内存中运行的应用程序

  • 2.每个进程都有一个独立的内存空间

  • 3.进程也是程序的一次执行过程,是系统运行程序的基本单位

  • 线程:

  • 1.是进程中的一个执行单元

  • 2.一个进程中至少有一个主线程

  • 3.一个进程中允许存在多条线程

  • 4.创建一个线程的开销比创建一个进程的开销小的多

线程调度:

  • 分时调度

    ​ 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

  • 抢占式调度

    ​ 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

1.3 多线程的优势与弊端

  • 多线程的优势:

  • 1.创建一个线程,比创建一个进程的消耗小的多,在处理并发任务时会优先选择创建线程来执行

  • 2.大型高并发技术的核心技术

  • 3.多线程可以解决很多业务模型

  • 4.为了保证程序的并发性,提高程序的执行效率,可以让进程有更多机会得到CPU

  • 多线程的弊端:

  • 1.创建过多的线程,也会造成程序卡顿,维护困难

  • 2.多线程并发访问资源,会造成资源争抢的情况,引发线程安全的问题

1.4 Thread类

线程开启我们需要用到了java.lang.Thread类,API中该类中定义了有关线程的一些方法,具体如下:

构造方法:

  • public Thread():分配一个新的线程对象。
  • public Thread(String name):分配一个指定名字的新的线程对象。
  • public Thread(Runnable target):分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

常用方法:

  • public String getName():获取当前线程名称。
  • public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
  • public void run():此线程要执行的任务在此处定义代码。
  • public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用。

翻阅API后得知创建线程的方式总共有两种,一种是继承Thread类方式,一种是实现Runnable接口方式,方式一我们上一天已经完成,接下来讲解方式二实现的方式。

1.5 创建线程方式一__继承方式

​ Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程

代码如下:

测试类:

/* Thread类 - 线程* 一.线程创建的过程* 1.自定义一个类继承extends Thread* 2.在自定义的线程类中,重写run(),规定执行的任务* 3.创建自定义线程类的对象* 4.调用自定义线程类的对象的start(),真正启动线程,执行任务** 二.通过Thread类创建线程的优势及劣势* 1.优势:代码简洁* 2.劣势:使用extends Thread,Java中类与类之间是单继承的关系,一旦继承了这个类,则不再可以继承别的类*/
public class ThreadDemo {public static void main(String[] args) {//1.创建线程对象,并且指定线程的名字MyThread my = new MyThread("t1");//Thread[t1,5,main]  线程名t1  线程优先级5  所属的线程组是mainSystem.out.println(my);//2.执行任务my.start();   //t1线程在执行任务,start()才是真正启动线程的方法//my.run();     //main 主线程在执行任务,run()并不会真正启动线程//my.start();   //IllegalThreadStateException  重复启动多次线程//main主线程执行任务for (int i=1;i<=10;i++){System.out.println(Thread.currentThread().getName()+":i = "+i);}}}

自定义线程类:

//自定义一个线程
public class MyThread extends Thread{public MyThread(String name) {super(name);}//定义一个任务@Overridepublic void run() {for (int i=1;i<=10;i++){//Thread.currentThread()  获取当前正在执行的线程对象Thread thread = Thread.currentThread();//thread.getName();  获取当前正在执行的线程的名字String name = thread.getName();System.out.println(name+":i = "+i);}}
}

1.6 创建线程的方式二__实现方式

采用java.lang.Runnable也是非常常见的一种,我们只需要重写run方法即可。

步骤如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
  3. 调用线程对象的start()方法来启动线程。

代码如下:

/*** Runnable接口  - 任务* 一.线程创建的过程* 1.先创建任务对象,implements Runnable接口* 2.重写任务类中的run(),规定执行的任务* 3.创建任务对象* 4.创建线程对象,传入任务对象* 5.通过start()启动线程** 二.通过Runnable接口创建线程的优势及劣势* 优势:*      1.允许创建不同的线程,执行同一个任务(共享同一个资源)*      2.避免Java中的单继承的局限性*      3.增加程序的健壮性,实现解耦操作,任务代码可以被多个线程共享,任务代码和线程独立*      4.线程池只能放入实现Runnable或Callable类* 劣势:代码麻烦*/
public class RunnableDemo {public static void main(String[] args) {//1.创建任务对象MyRunnable myRun = new MyRunnable();//2.创建线程对象Thread thread1 = new Thread(myRun,"t1");Thread thread2 = new Thread(myRun,"t2");//3.启动线程//运行多条线程,执行同一个任务thread1.start();thread2.start();}}
//自定义一个任务类
class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 1; i <= 10; i++) {System.out.println(Thread.currentThread().getName()+":i = "+i);}}
}

​ 通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

​ 在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。

​ 实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

tips:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。

Thread和Runnable的区别

总结:

实现Runnable接口比继承Thread类所具有的优势:

  1. 适合多个相同的程序代码的线程去共享同一个资源。
  2. 可以避免java中的单继承的局限性。
  3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
  4. 线程池只能放入实现Runable或Callable类线程。

1.7 创建线程的方式三__实现方式

采用java.util.concurrent.Callable,我们可以创建一个带有返回值的任务类

步骤如下:

1.定义Callable接口的实现类,并重写该接口的call()方法,该call()方法的方法体同样是该线程的线程执行体。

2.创建带有返回值的任务对象,创建FutureTask对象。

3.创建线程Thread对象,传入FutureTask对象,通过start()方法真正启动线程。

4.通过FUtureTask对象,调用get()方法获取执行完毕的任务结果

/*** Callable接口 - 带有返回值的任务* 一.线程创建的过程* 1.自定义一个带有返回值的任务类,implements Callable<T>* 2.重写Callable<T>接口中的T call()* 3.创建带有返回值的任务对象* 4.创建FutureTask对象* 5.创建线程Thread对象,传入FutureTask对象* 6.通过start()真正启动线程* 7.通过FutureTask对象,get()获取执行完毕后的任务结果** 二.通过Callable接口创建线程的优势及劣势* 优势:*      1.允许创建不同的线程,执行同一个任务(共享同一个资源)*      2.避免Java中的单继承的局限性*      3.增加程序的健壮性,实现解耦操作,任务代码可以被多个线程共享,任务代码和线程独立*      4.线程池只能放入实现Runnable或Callable类* 劣势:代码麻烦*/
public class CallableDemo {public static void main(String[] args) {//1.创建带有返回值的任务对象MyCallable myCall = new MyCallable();//2.创建FutureTask对象,获取将来执行完毕的结果FutureTask task1 = new FutureTask(myCall);FutureTask task2 = new FutureTask(myCall);//3.创建线程对象Thread thread1 = new Thread(task1,"t1");Thread thread2 = new Thread(task2,"t2");//4.启动线程thread1.start();thread2.start();//5.获得任务对象执行的结果try {String str = task1.get().toString();System.out.println("执行结果:"+str);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}try {String str = task2.get().toString();System.out.println("执行结果:"+str);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}}
//自定义一个任务类,实现带有返回值的任务类
class MyCallable implements Callable<String>{//定义一个带有返回值的任务方法@Overridepublic String call() throws Exception {int sum = 0;for (int i = 1; i <=10 ; i++) {sum+=i;System.out.println(Thread.currentThread().getName()+":i = "+i);}return Thread.currentThread().getName()+":sum = "+sum;}
}

1.8 匿名内部类方式

使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同的线程任务操作。

使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run方法:

public class NoNameInnerClassThread {public static void main(String[] args) {	   	
//		new Runnable(){
//			public void run(){
//				for (int i = 0; i < 20; i++) {
//					System.out.println("张宇:"+i);
//				}
//			}  
//	   	}; //---这个整体  相当于new MyRunnable()Runnable r = new Runnable(){public void run(){for (int i = 0; i < 20; i++) {System.out.println("张宇:"+i);}}  };new Thread(r).start();for (int i = 0; i < 20; i++) {System.out.println("费玉清:"+i);}}
}

第2章 线程安全

2.1 线程安全

如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

我们通过一个案例,演示线程的安全问题:

​ 电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票),需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟。

模拟票:

public class Ticket implements Runnable {private int ticket = 100;/** 执行卖票操作*/@Overridepublic void run() {//每个窗口卖票的操作 //窗口 永远开启 while (true) {if (ticket > 0) {//有票 可以卖//出票操作//使用sleep模拟一下出票时间 try {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}//获取当前线程对象的名字 String name = Thread.currentThread().getName();System.out.println(name + "正在卖:" + ticket--);}}}
}

测试类:

public class Demo {public static void main(String[] args) {//创建线程任务对象Ticket ticket = new Ticket();//创建三个窗口对象Thread t1 = new Thread(ticket, "窗口1");Thread t2 = new Thread(ticket, "窗口2");Thread t3 = new Thread(ticket, "窗口3");//同时卖票t1.start();t2.start();t3.start();}
}

结果中有一部分这样现象:

在这里插入图片描述

发现程序出现了两个问题:

  1. 相同的票数,比如5这张票被卖了两回。
  2. 不存在的票,比如0票与-1票,是不存在的。

这种问题,几个窗口(线程)票数不同步了,这种问题称为线程不安全。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

2.2 线程同步

线程同步是为了解决线程安全问题。

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。

要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。

根据案例简述:

窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。

那么怎么去使用呢?

有三种方式完成同步操作:

  1. 同步代码块。
  2. 同步方法。
  3. 锁机制。

2.3 同步代码块

  • 同步代码块synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:

synchronized(同步锁){需要同步操作的代码
}

同步锁:

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

  1. 锁对象 可以是任意类型。
  2. 多个线程对象 要使用同一把锁。

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

使用同步代码块解决代码:

public class Ticket implements Runnable{private int ticket = 100;Object lock = new Object();/** 执行卖票操作*/@Overridepublic void run() {//每个窗口卖票的操作 //窗口 永远开启 while(true){synchronized (lock) {if(ticket>0){//有票 可以卖//出票操作//使用sleep模拟一下出票时间 try {Thread.sleep(50);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}//获取当前线程对象的名字 String name = Thread.currentThread().getName();System.out.println(name+"正在卖:"+ticket--);}}}}
}

当使用了同步代码块后,上述的线程的安全问题,解决了。

2.4 同步方法

  • 同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

格式:

public synchronized void method(){可能会产生线程安全问题的代码
}

同步锁是谁?

​ 对于非static方法,同步锁就是this。

​ 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

使用同步方法代码如下:

public class Ticket implements Runnable{private int ticket = 100;/** 执行卖票操作*/@Overridepublic void run() {//每个窗口卖票的操作 //窗口 永远开启 while(true){sellTicket();}}/** 锁对象 是 谁调用这个方法 就是谁 *   隐含 锁对象 就是  this*    */public synchronized void sellTicket(){if(ticket>0){//有票 可以卖	//出票操作//使用sleep模拟一下出票时间 try {Thread.sleep(100);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}//获取当前线程对象的名字 String name = Thread.currentThread().getName();System.out.println(name+"正在卖:"+ticket--);}}
}

2.5 Lock锁

java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大

Lock锁也称同步锁,加锁与释放锁方法化了,如下:

  • public void lock():加同步锁。
  • public void unlock():释放同步锁。

使用如下:

public class Ticket implements Runnable{private int ticket = 100;Lock lock = new ReentrantLock();/** 执行卖票操作*/@Overridepublic void run() {//每个窗口卖票的操作 //窗口 永远开启 while(true){lock.lock();if(ticket>0){//有票 可以卖//出票操作 //使用sleep模拟一下出票时间 try {Thread.sleep(50);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}//获取当前线程对象的名字 String name = Thread.currentThread().getName();System.out.println(name+"正在卖:"+ticket--);}lock.unlock();}}
}

2.6 synchronized和Lock锁的区别

 1.存在层次上,synchronized是Java的关键字,在jvm层面上;而Lock是一个类2.锁的释放上,synchronized是 1、以获取锁的线程执行完同步代码,释放锁 2、线程执行发生异常,jvm会让线程释放锁;而Lock是在finally中必须释放锁(lock.unlock()),不然容易造成线程死锁3.锁的获取上,synchronized是假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待;而Lock是尝试获得锁,线程可以不用一直等待4.锁状态上,synchronized是无法判断;而Lock是可以判断5.锁类型上,synchronized是可重入 不可中断 非公平;而Lock是可重入 可判断 可公平(两者皆可) - 默认使用ReentrantLock无参构造方法,则创建非公平锁6.性能上,synchronized是尽量同步少量代码;而Lock是可以同步大量代码7.目前上synchronized已经逐步优化,并且是JVM"亲生的"名词介绍:可重入锁:在执行对象中所有同步方法不用再次获得锁可中断锁:在等待获取锁过程中可中断公平锁: 按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利读写锁:对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写

第3章 线程状态

3.1 线程状态概述

线程由生到死的完整过程:

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在API中java.lang.Thread.State这个枚举中给出了六种线程状态:

这里先列出各个线程状态发生的条件,下面将会对每种状态进行详细解析

线程状态导致状态发生条件
NEW(新建)线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread只有线程对象,没有线程特征。
Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。调用了t.start()方法 :就绪(经典叫法)
Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

在这里插入图片描述

​ 我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable(可运行)状态与非运行状态之间的转换问题。

3.2 睡眠sleep方法

我们看到状态中有一个状态叫做计时等待,可以通过Thread类的方法来进行演示.

public static void sleep(long time) 让当前线程进入到睡眠状态,到毫秒后自动醒来继续执行

public class Test{public static void main(String[] args){for(int i = 1;i<=5;i++){Thread.sleep(1000);System.out.println(i)   } }
}

这时我们发现主线程执行到sleep方法会休眠1秒后再继续执行。

3.2.1 jion方法

join() 无参方法,进入无限等待状态,直至调用者的线程执行结束为止

join(long millis) 有参方法,进入计时等待状态,直至等待时间结束为止

public class MyThread1 extends Thread{public MyThread1(String name){super(name);}@Overridepublic void run() {System.out.println("MyThread1开始执行");for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+": i = "+i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("MyThread1结束执行");}
}
class MyThread2 extends Thread{private MyThread1 my1;public MyThread2(MyThread1 my1, String name){super(name);this.my1 = my1;}@Overridepublic void run() {System.out.println("MyThread2开始执行");for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+": i = "+i);}//当前MyThread2执行,必须等待MyThread1执行完毕再结束//join() 无参方法,进入无限等待状态,直至调用者的线程执行结束为止//join(long millis) 有参方法,进入计时等待状态,直至等待时间结束为止try {my1.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("MyThread2结束执行");}}
class Test {public static void main(String[] args) {System.out.println("main线程开始执行");MyThread1 my1 = new MyThread1("线程1");MyThread2 my2 = new MyThread2(my1,"线程2");my1.start();my2.start();//此时main主线程,会等待my2线程执行结束后,方才结束//结束顺序:MyThread1结束执行 -> MyThread2结束执行 ->main线程结束执行try {my2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("main线程结束执行");}}

3.2.2 yield方法

static void yield() 给调度程序的一个提示,当前线程愿意得到它当前的处理器的使用。

public class ThreadDemo {public static void main(String[] args) {new Thread(()->{for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+": i = "+i);Thread.yield();}},"线程1").start();new Thread(()->{for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+": i = "+i);Thread.yield();}},"线程2").start();}}

3.2.3 sleep、yield的区别

  • sleep():

  • 1.static void sleep(long millis) 当前正在执行的线程休眠(暂停执行)为指定的毫秒数,根据精度和系统定时器和调度的准确性。

  • 2.一旦线程调用sleep(),则当前线程会进入休眠状态,指定的时间暂停执行的最小时间

  • 3.sleep()不会释放对象锁

  • 4.sleep()执行后,会让出当前线程的CPU执行权,直到沉睡时间结束,再次争抢到时间片,再会执行任务

  • 5.存在编译期异常 InterruptedException 中断异常

  • yield():

  • 1.static void yield() 给调度程序的一个提示,当前线程愿意得到它当前的处理器的使用。

  • 2.“假意退让”,将当前CPU的执行权退让给优先级更高的线程,再次进行争抢时间片,一旦获得时间片,将会继续执行任务

sleep(),jion(),yield()的区别

sleep(): 进入到沉睡状态,直至沉睡时间结束,会继续争抢时间片,一旦抢到时间片,则继续执行
yield() : 进入"假意退让"的状态,会继续争抢时间片,一旦抢到时间片,则继续执行
join() : 无参的方法则进入无限等待状态;有参的方法则进入到计时等待状态。等待时间结束,会继续争抢时间片,一旦抢到时间片,则继续执行

3.3 等待和唤醒

Object类的方法

public void wait() : 让当前线程进入到等待状态 此方法必须锁对象调用.

public class Demo1_wait {public static void main(String[] args) throws InterruptedException {// 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.new Thread(() -> {try {System.out.println("begin wait ....");synchronized ("") {"".wait();}System.out.println("over");} catch (Exception e) {}}).start();}

public void notify() : 唤醒当前锁对象上等待状态的线程 此方法必须锁对象调用.

public class Demo2_notify {public static void main(String[] args) throws InterruptedException {// 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.new Thread(() -> {try {System.out.println("begin wait ....");synchronized ("") {"".wait();}System.out.println("over");} catch (Exception e) {}}).start();//步骤2:  加入如下代码后, 3秒后,会执行notify方法, 唤醒wait中线程.Thread.sleep(3000);new Thread(() -> {try {synchronized ("") {System.out.println("唤醒");"".notify();}} catch (Exception e) {}}).start();}
}

3.4 sleep()和wait()的区别

/*** sleep()和wait()的区别* 1.sleep(long time) 静态方法,属于Thread类*   wait() 非静态方法,属于Object类* 2.sleep(long time) 计时等待,时间一结束,获得到时间片后,则继续执行*   wait() 无限等待,直到被唤醒notifyAll(),获得对象锁,则继续执行*   wait(long time) 计时等待,要么时间到了,要么被唤醒了,获得对象锁,则继续执行* 3.sleep(long time) 不会释放对象锁*   wait() 会释放对象锁* 4.sleep(long time) 不一定需要搭配synchronized关键字一起使用;*   wait() 必须在线程安全的情况下使用,必须搭配synchronized关键字使用,否则会出现IllegalMonitorStateException异常*/
public class SleepAndWaitDemo {public static void main(String[] args) {//对象锁 lockObject lock = new Object();Stream.of("线程1","线程2").forEach(n->new Thread(n){@Overridepublic void run() {synchronized (lock){String name = Thread.currentThread().getName();System.out.println(name+"线程开始执行");try {//线程沉睡//Thread.sleep(1000);//计时等待lock.wait(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(name+"线程结束执行");}}}.start());}}

3.5 等待唤醒案例(包子铺卖包子)

定义一个集合,包子铺线程完成生产包子,包子添加到集合中;吃货线程完成购买包子,包子从集合中移除。
1. 当包子没有时(包子状态为false),吃货线程等待.
2. 包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态)

代码示例:

生成包子类:

public class BaoZiPu extends Thread{private List<String> list ;public BaoZiPu(String name,ArrayList<String> list){super(name);this.list = list;}@Overridepublic void run() {int i = 0; while(true){//list作为锁对象synchronized (list){while(list.size()>0){//存元素的线程进入到等待状态try {list.wait();} catch (InterruptedException e) {e.printStackTrace();}}//如果线程没进入到等待状态 说明集合中没有元素//向集合中添加元素list.add("包子"+i++);System.out.println(list);//集合中已经有元素了 唤醒获取元素的线程list.notify();}}}
}

消费包子类:

public class ChiHuo extends Thread {private List<String> list ;public ChiHuo(String name,ArrayList<String> list){super(name);this.list = list;}@Overridepublic void run() {//为了能看到效果 写个死循环while(true){//由于使用的同一个集合 list作为锁对象synchronized (list){//如果集合中没有元素,获取元素的线程进入到等待状态while(list.size()==0){try {list.wait();} catch (InterruptedException e) {e.printStackTrace();}}//如果集合中有元素 则获取元素的线程获取元素(删除)list.remove(0);//打印集合 集合中没有元素了System.out.println(list);//集合中已经没有元素 则唤醒添加元素的线程 向集合中添加元素list.notify();}}}
}

测试类:

public class Demo {public static void main(String[] args) {//等待唤醒案例List<String> list = new ArrayList<>();// 创建线程对象        BaoZiPu bzp = new BaoZiPu("包子铺",list);ChiHuo ch = new ChiHuo("吃货",list);// 开启线程bzp.start();ch.start();}
}

3.6 虚假唤醒案例

有一个商铺,库存容量为10,生产者完成生产,库存+1,消费者完成消费,库存-11.当库存为10(库存容量满了),生产者停止生产,进行等待,并通知消费者进行消费。
2.当库存为0(没有库存了),消费者停止消费,进行等待,并通知生产者进行生产。

商铺类(使用synchronized):

/*** @Description 售货员 - 补货/售卖** 生产者和消费者模型:* 1.线程通讯是一种等待唤醒机制* 2.线程通讯一定是以多条线程并发访问共同资源为前提* 3.确保线程安全的下,进行线程通讯,否则没有任何意义* 4.锁对象调用wait()和notifyAll(),必须搭配synchronized一起使用,若不搭配则会出现IllegalMonitorStateException异常* 5.可能导致虚假唤醒的问题,必须在循环中使用* 6.线程通讯必须确保对象锁是唯一的** 虚假唤醒出现的前提:* 当前处于等待状态,被唤醒所有后,没有再次去判断条件是否满足,而直接继续往下执行* 虚假唤醒的解决方案:* 在循环中使用wait()*/
public class Clerk {//库存  最多可以存10件private int num;//补货public synchronized void add() {// num>=1 测试一个情况 - 虚假唤醒//解决方案,将if换成whileif(num>=1){System.out.println("仓库已满...");//一旦仓库中满10件商品,就不允许生产者再进行生产,只能进入等待状态try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"生产了:"+ ++num);//一旦生产者生产了商品,唤醒消费者,进行消费this.notifyAll();}//售卖public synchronized void sale() {//虚假唤醒解决方案,将if换成whileif(num<=0){System.out.println("仓库已空...");//一旦仓库空了,没有商品,就不允许消费者进行消费,只能进入等待状态try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"消费了:"+ num--);//一旦消费者消费了商品,唤醒生产者,进行生产this.notifyAll();}
}

商铺类(使用lock锁):

/*** @Description 售货员 - 补货/售卖** 生产者和消费者模型:* 1.多条线程并发访问共同资源为前提* 2.确保线程安全的下,进行线程通讯,否则没有任何意义* 3.Lock锁,lock()加锁  lock.unlock();解锁* 通过Lock锁,获取Condition对象,await()进入等待状态;调用signalAll()唤醒所有等待线程*/
public class Clerk {//库存  最多可以存10件private int num;//创建锁private final Lock lock = new ReentrantLock();//创建ConditionCondition condition = lock.newCondition();//补货public void add() {lock.lock();try {while(num>=10){System.out.println("仓库已满...");//一旦仓库中满10件商品,就不允许生产者再进行生产,只能进入等待状态condition.await();}System.out.println(Thread.currentThread().getName()+"生产了:"+ ++num);//一旦生产者生产了商品,唤醒消费者,进行消费condition.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}//售卖public void sale() {lock.lock();try {while(num<=0){System.out.println("仓库已空...");//一旦仓库空了,没有商品,就不允许消费者进行消费,只能进入等待状态condition.await();}System.out.println(Thread.currentThread().getName()+"消费了:"+ num--);//一旦消费者消费了商品,唤醒生产者,进行生产condition.signalAll();} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}}
}

生产者类:

/*** @Description 生产者 - 生产 - implements Runnable*/
public class Product implements Runnable{private Clerk clerk;public Product(Clerk clerk){this.clerk = clerk;}@Overridepublic void run() {for (int i = 0; i < 15; i++) {try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}clerk.add();}}
}

消费者类:

/*** @Description 消费者 - 消费  - implements Runnable*/
public class Customer implements Runnable{private Clerk clerk;public Customer(Clerk clerk){this.clerk = clerk;}@Overridepublic void run() {for (int i = 0; i < 15; i++) {clerk.sale();}}
}

测试类:

public class Test {public static void main(String[] args) {Clerk clerk = new Clerk();//产生者任务Product product = new Product(clerk);//消费者任务Customer customer = new Customer(clerk);Thread t1 = new Thread(product, "生产者 A");t1.start();Thread t2 = new Thread(customer, "消费者 B");t2.start();Thread t3 = new Thread(product, "生产者 C");t3.start();Thread t4 = new Thread(customer, "消费者 D");t4.start();}}

第4章 线程池方式

4.1 线程池的思想

在这里插入图片描述

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间,线程也属于宝贵的系统资源。

那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池。

一 .简介
线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的。在jdk1.5之后这一情况有了很大的改观。Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用。为我们在开发中处理线程的问题提供了非常大的帮助。

二.线程池
1.线程池的作用:线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;
少了浪费了系统资源,多了造成系统拥挤效率不高。
用线程池控制线程数量,其他线程排队等候。

一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待任务,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

2.为什么要用线程池:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

Java里面线程池的顶级接口是Executors,严格意义上讲Executors并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口ExecutorService

3.比较重要的几个类:
ExecutorService 真正的线程池接口。
ScheduledExecutorService 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutor ExecutorService的默认实现。
ScheduledThreadPoolExecutor 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。

  1. newSingleThreadExecutor
    创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
  2. newFixedThreadPool
    创建固定大小的线程池。
    每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。
    线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,
    那么线程池会补充一个新线程。
  3. newCachedThreadPool
    创建一个可缓存的线程池。
    如果线程池的大小超过了处理任务所需要的线程,
    那么会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,
    此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,
    线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
  4. newScheduledThreadPool
    创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

4.2 线程池概念

  • **线程池:**其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:

合理利用线程池能够带来三个好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

4.3 线程池的使用

Java里面线程池的顶级接口是java.util.concurrent.Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService

要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在java.util.concurrent.Executors线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象。

Executors类中有个创建线程池的方法如下:

  • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)

获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:

  • public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行

    Future接口:用来记录线程任务执行完毕后产生的结果。

使用线程池中线程对象的步骤:

  1. 创建线程池对象。
  2. 创建Runnable接口子类对象。(task)
  3. 提交Runnable接口子类对象。(take task)
  4. 关闭线程池(一般不做)。

Runnable实现类代码:

public class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("我要一个教练");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("教练来了: " + Thread.currentThread().getName());System.out.println("教我游泳,交完后,教练回到了游泳池");}
}

线程池测试类:

public class ThreadPoolDemo {public static void main(String[] args) {// 创建线程池对象ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象// 创建Runnable实例对象MyRunnable r = new MyRunnable();//自己创建线程对象的方式// Thread t = new Thread(r);// t.start(); ---> 调用MyRunnable中的run()// 从线程池中获取线程对象,然后调用MyRunnable中的run()service.submit(r);// 再获取个线程对象,调用MyRunnable中的run()service.submit(r);service.submit(r);// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。// 将使用完的线程又归还到了线程池中// 关闭线程池//service.shutdown();}
}

Callable测试代码:

  • <T> Future<T> submit(Callable<T> task) : 获取线程池中的某一个线程对象,并执行.

    Future : 表示计算的结果.

  • V get() : 获取计算完成的结果。

public class ThreadPoolDemo2 {public static void main(String[] args) throws Exception {// 创建线程池对象ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象// 创建Runnable实例对象Callable<Double> c = new Callable<Double>() {@Overridepublic Double call() throws Exception {return Math.random();}};// 从线程池中获取线程对象,然后调用Callable中的call()Future<Double> f1 = service.submit(c);// Futur 调用get() 获取运算结果System.out.println(f1.get());Future<Double> f2 = service.submit(c);System.out.println(f2.get());Future<Double> f3 = service.submit(c);System.out.println(f3.get());}
}

4.4 线程池的练习

需求: 使用线程池方式执行任务,返回1-n的和

分析: 因为需要返回求和结果,所以使用Callable方式的任务

代码:

public class Demo04 {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService pool = Executors.newFixedThreadPool(3);SumCallable sc = new SumCallable(100);Future<Integer> fu = pool.submit(sc);Integer integer = fu.get();System.out.println("结果: " + integer);SumCallable sc2 = new SumCallable(200);Future<Integer> fu2 = pool.submit(sc2);Integer integer2 = fu2.get();System.out.println("结果: " + integer2);pool.shutdown();}
}

SumCallable.java

public class SumCallable implements Callable<Integer> {private int n;public SumCallable(int n) {this.n = n;}@Overridepublic Integer call() throws Exception {// 求1-n的和?int sum = 0;for (int i = 1; i <= n; i++) {sum += i;}return sum;}
}

第5章 死锁

5.1 什么是死锁

在多线程程序中,使用了多把锁,造成线程之间相互等待.程序不往下走了。

5.2 产生死锁的条件

1.有多把锁
2.有多个线程
3.有同步代码块嵌套

5.3 线程死锁的出现原因和解决方案

线程死锁出现的原因:

1.多线程间相互持有对方资源

2.线程同步代码块出现了嵌套

线程死锁的解决方案

1.同步代码块 不要嵌套使用

2.不要相互持有对方的资源

5.4 死锁代码

public class Demo05 {public static void main(String[] args) {MyRunnable mr = new MyRunnable();new Thread(mr).start();new Thread(mr).start();}
}class MyRunnable implements Runnable {Object objA = new Object();Object objB = new Object();/*嵌套1 objA嵌套1 objB嵌套2 objB嵌套1 objA*/@Overridepublic void run() {synchronized (objA) {System.out.println("嵌套1 objA");synchronized (objB) {// t2, objA, 拿不到B锁,等待System.out.println("嵌套1 objB");}}synchronized (objB) {System.out.println("嵌套2 objB");synchronized (objA) {// t1 , objB, 拿不到A锁,等待System.out.println("嵌套2 objA");}}}
}

注意:我们应该尽量避免死锁

第6章 可见性

6.1 看程序说结果

public class VolatileThread extends Thread {// 定义成员变量private boolean flag = false ;public boolean isFlag() { return flag;}@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 将flag的值更改为truethis.flag = true ;System.out.println("flag=" + flag);}
}public class VolatileThreadDemo {// 测试类public static void main(String[] args) {// 创建VolatileThread线程对象VolatileThread volatileThread = new VolatileThread() ;volatileThread.start();// main方法while(true) {if(volatileThread.isFlag()) {System.out.println("执行了======");}}}
}

结果:

在这里插入图片描述

我们看到,VolatileThread线程中已经将flag设置为true,但main()方法中始终没有读到,从而没有打印。

6.2 JMM

概述:JMM(Java Memory Model) Java内存模型,是java虚拟机规范中所定义的一种内存模型。

Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。

所有的共享变量都存储于主内存。这里所说的变量指的是实例变量和类变量。不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题。

**每一个线程还存在自己的工作内存,**线程的工作内存,保留了被线程使用的变量的工作副本。线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存完成。

在这里插入图片描述

关于JMM的一些同步的约定:

1.线程解锁前,必须把共享变量立刻刷回主存

2.线程加锁前,必须读取主存中的最新值到工作内存中

3.加锁和解锁是同一把锁

6.3 问题分析

在这里插入图片描述

  1. VolatileThread线程从主内存读取到数据放入其对应的工作内存
  2. 将flag的值更改为true,但是这个时候flag的值还没有写回主内存
  3. 此时main方法读取到了flag的值为false
  4. 当VolatileThread线程将flag的值写回去后,但是main函数里面的while(true)调用的是系统比较底层的代码,速度快,快到没有时间再去读取主存中的值,所以while(true)读取到的值一直是false。(如果有一个时刻main线程从主内存中读取到了主内存中flag的最新值,那么if语句就可以执行,main线程何时从主内存中读取最新的值,我们无法控制)

引发:

​ 多个线程访问共享变量,会出现一个线程修改变量的值后,其他线程看不到变量最新值的情况。

总结:

​ 并发编程下,多线程修改变量,会出现线程间变量的不可见性。

不可见性原因:

​ 每个线程都有自己的工作内存,线程都是从主内存拷贝共享变量的副本值。

​ 每个线程都是在自己的工作内存中操作共享变量的。

6.4 问题处理

方式一:加锁synchronized

使用synchronized关键字:

  // main方法//加锁synchronized//解决原因:会清空工作内存,读取主内存中最新值到工作内存中来。while(true){//加锁:清空工作内存中的共享变量的副本,强制线程去主内存中读取最新的共享变量的值synchronized (VisibleDemo1.class){if(myRunnable1.isFlag()){System.out.println("执行了....");}}

工作原理:

某一个线程进入synchronized代码块前后,执行过程入如下:

a.线程获得锁

b.清空工作内存

c.从主内存拷贝共享变量最新的值到工作内存成为副本

d.执行代码

e.将修改后的副本的值刷新回主内存中

f.线程释放锁

方式二:volatile关键字

使用volatile关键字:

//实例变量  volatile保证此变量的可见性
//通过使用volatile修饰共享变量,当有线程修改了此变量,直接让其它线程的工作内存中变量副本失效,
//此时各线程中只能从主内存中重新获取最新的共享变量的值来进行使用   
private volatile boolean flag ;

工作原理:

在这里插入图片描述

1.VolatileThread线程从主内存读取到数据放入其对应的工作内存中

2.将flag的值更改为true,但是这个时候flag的值还没有写回主内存

3.此时main方法main方法读取到了flag的值为false

4.当VolatileThread线程将flag的值写回去后,失效其他线程对此变量副本

5.再次对flag进行操作的时候线程会从主内存读取最新的值,放入到工作内存中

总结: volatile保证不同线程对共享变量操作的可见性,也就是说一个线程修改了volatile修饰的变量,当修改写回主内存时,另外一个线程立即看到最新的值。

现象:

​ 并发编程下,多线程修改变量,会出现线程间变量的不可见性。

解决线程间变量的不可见性的方案有两种常见方式:

​ 1.加锁

​ 解决原因:会清空工作内存,读取主内存中最新值到工作内存中来。

​ 2.对共享的变量进行volatile关键字修饰

​ 解决原因:一旦一个线程中的变量,添加了volatile修饰符,其它线程可以立即读取到最新值

6.5 volatile与synchronized的区别

1).volatile只能修饰实例变量和类变量,而synchronized可以修饰方法,以及代码块。

2).volatile保证数据的可见性,但是不保证原子性(即:多线程进行写操作,不保证线程安全);而synchronized是一种排他(互斥)的机制,实现线程安全。

3).从性能上说,volatile更好点,仅仅是对实现线程间变量的可见性上。

第7章 原子性

​ 概述:所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行。

7.1 看程序说结果

public class VolatileAtomicThread1 implements Runnable {// 定义一个int类型的遍历private int count = 0 ;@Overridepublic void run() {// 对该变量进行++操作,100次for(int x = 1 ; x <= 100 ; x++) {count++ ;					System.out.println("count =========>>>> " + count);}}
}public class VolatileAtomicThreadDemo1 {public static void main(String[] args) {// 创建VolatileAtomicThread对象VolatileAtomicThread1 volatileAtomicThread = new VolatileAtomicThread1() ;// 开启100个线程对count进行++操作for(int x = 1 ; x <= 100 ; x++) {new Thread(volatileAtomicThread).start();}}
}

执行结果:不保证一定是10000

7.2 问题原理说明

以上问题主要是发生在count++操作上:

count++操作包含3个步骤:

  • 从主内存中读取数据到工作内存
  • 对工作内存中的数据进行++操作
  • 将工作内存中的数据写回到主内存

count++操作不是一个原子性操作,也就是说在某一个时刻对某一个操作的执行,有可能被其他的线程打断。

在这里插入图片描述

1)假设此时x的值是100,线程A需要对改变量进行自增1的操作,首先它需要从主内存中读取变量x的值。由于CPU的切换关系,此时CPU的执行权被切换到了B线程。A线程就处于就绪状态,B线程处于运行状态。

2)线程B也需要从主内存中读取x变量的值,由于线程A没有对x值做任何修改因此此时B读取到的数据还是100。

3)线程B工作内存中x执行了+1操作,但是未刷新到主内存中。

4)此时CPU的执行权切换到了A线程上,由于此时线程B没有将工作内存中的数据刷新到主内存,因此A线程工作内存中的变量值还是100,没有失效。A线程对工作内存中的数据进行了+1操作。

5)线程B将101写入到主内存。

6)线程A将101写入到主内存。

虽然计算了2次,但是只对A进行了1次修改。

7.3 volatile原子性测试

代码测试

// 定义一个int类型的变量
private volatile int count = 0 ;

小结:在多线程环境下,volatile关键字可以保证共享数据的可见性,但是并不能保证对数据操作的原子性(在多线程环境下volatile修饰的变量也是线程不安全的)。

在多线程环境下,要保证数据的安全性,我们还需要使用锁机制。

volatile的使用场景

  • 开关控制

    利用可见性特点,控制某一段代码执行或者关闭(比如今天课程的第一个案例)。

  • 多个线程操作共享变量,但是是有一个线程对其进行写操作,其他的线程都是读

7.4 问题解决

方式一:使用锁机制synchronized

​ 我们可以给count++操作添加锁,那么count++操作就是临界区的代码,临界区只能有一个线程去执行,所以count++就变成了原子操作。

public class VolatileAtomicThread2 implements Runnable {// 定义一个int类型的变量private volatile int count = 0 ;private static final Object obj = new Object();@Overridepublic void run() {// 对该变量进行++操作,100次for(int x = 1 ; x <= 100 ; x++) {synchronized (obj) {count++ ;System.out.println("count =========>>>> " + count);}}}
}public class VolatileAtomicThreadDemo2 {public static void main(String[] args) {// 创建VolatileAtomicThread对象VolatileAtomicThread2 volatileAtomicThread = new VolatileAtomicThread2() ;// 开启100个线程对count进行++操作for(int x = 1 ; x <= 100 ; x++) {new Thread(volatileAtomicThread).start();}}
}

方式二:原子类

​ Java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。

AtomicInteger

原子型Integer,可以实现原子更新操作

public AtomicInteger():	   				初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integerint get():   			 				 获取值
int getAndIncrement():      			 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet():    				 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data):				 以原子方式将输入的数值与实例中的值(AtomicInteger里的                                            value)相加,并返回结果。
int getAndSet(int value):   			 以原子方式设置为newValue的值,并返回旧值。

案例改造

使用AtomicInteger对案例进行改造.

public class VolatileAtomicThread3 implements Runnable {// 定义一个int类型的变量private AtomicInteger atomicInteger = new AtomicInteger() ;@Overridepublic void run() {// 对该变量进行++操作,100次for(int x = 1; x <= 100 ; x++) {int i = atomicInteger.getAndIncrement();System.out.println("count =========>>>> " + i);}}
}public class VolatileAtomicThreadDemo3 {public static void main(String[] args) {// 创建VolatileAtomicThread对象VolatileAtomicThread3 volatileAtomicThread = new VolatileAtomicThread3() ;// 开启100个线程对count进行++操作for(int x = 1 ; x <= 100 ; x++) {new Thread(volatileAtomicThread).start();}}
}

7.5 原子类CAS机制实现线程安全

7.5.1 概述

在这里插入图片描述

CAS的全称是: Compare And Swap(比较再交换);是现代CPU广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。

CAS可以将read-modify-check-write转换为原子操作,这个原子操作直接由处理器保证。

CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

举例:

  1. 在内存地址V当中,存储着值为10的变量。

在这里插入图片描述

  1. 此时线程1想要把变量的值增加1。对线程1来说,旧的预期值A=10,要修改的新值B=11。

在这里插入图片描述

  1. 在线程1要提交更新之前,另一个线程2抢先一步,把内存地址V中的变量值率先更新成了11。

在这里插入图片描述

  1. 线程1开始提交更新,首先进行A和地址V的实际值比较(Compare),发现A不等于V的实际值,提交失败。

在这里插入图片描述

  1. 线程1重新获取内存地址V的当前值,并重新计算想要修改的新值。此时对线程1来说,A=11,B=12。这个重新尝试的过程被称为自旋。

在这里插入图片描述

  1. 这一次比较幸运,没有其他线程改变地址V的值。线程1进行Compare,发现A和地址V的实际值是相等的。

在这里插入图片描述

  1. 线程1进行SWAP,把地址V的值替换为B,也就是12。

在这里插入图片描述

可以使用AtomicInteger类中的public final boolean compareAndSet(int expect , int update)方法进行校验。

7.5.2 案例介绍

原理: 比较当前工作内存中的值和主内存中的值,如果主内存中的值是期望的,则执行交换;否则自旋获取主内存中的最新值。

public class CASDemo1 {public static void main(String[] args) {//原子类的实例,并且指定初始值value=10AtomicInteger atomicInteger = new AtomicInteger(10);new Thread(()->{//第一个参数:期望值   第二个参数:更新值//返回true代表交换成功   返回false代表交换失败boolean flag1 = atomicInteger.compareAndSet(10, 11);System.out.println("flag1 = "+flag1);  //trueSystem.out.println(atomicInteger.intValue()); //11boolean flag2 = atomicInteger.compareAndSet(11, 12);System.out.println("flag2 = "+flag2); //trueSystem.out.println(atomicInteger.intValue());  //12},"线程1").start();}}

7.5.3 CAS - 引发的ABA 问题

问题引入:ABA问题,线程1执行 A-> D, 线程2执行 A->B->A,当线程2先执行完毕A又变为A,线程1的期望值expect和当前值 A是一致的,仍然可以满足执行条件,而这和原本设计目的相悖

public class CASDemo2 {public static void main(String[] args) {//原子类的实例,并且指定初始值value=10AtomicInteger atomicInteger = new AtomicInteger(10);new Thread(()->{//第一个参数:期望值   第二个参数:更新值boolean flag1 = atomicInteger.compareAndSet(10, 11);System.out.println("flag1 = "+flag1);  //trueSystem.out.println(atomicInteger.intValue()); //11boolean flag2 = atomicInteger.compareAndSet(11, 10);System.out.println("flag2 = "+flag2); //trueSystem.out.println(atomicInteger.intValue());  //10},"线程1").start();new Thread(()->{//第一个参数:期望值   第二个参数:更新值   引发ABA问题,期望的10已经不是当初主内存中的10了boolean flag1 = atomicInteger.compareAndSet(10, 66);System.out.println("flag1 = "+flag1);  //trueSystem.out.println(atomicInteger.intValue()); //66},"线程2").start();}}

7.5.4CAS - ABA问题的解决

解决方案:AtomicStampedReference 原子类+版本号

public class CASDemo3 {public static void main(String[] args) {//带版本号的原子类  第一个参数:初始值   第二个参数:初始版本号AtomicStampedReference asr = new AtomicStampedReference(10,1);new Thread(()->{System.out.println("a1版本号 -->"+asr.getStamp()); //1//第一个参数:期望值  第二个参数:更新值  第三个参数:期望的版本号  第四个参数:更新后的版本号//返回true代表交换成功   返回false代表交换失败System.out.println(asr.compareAndSet(10, 11,asr.getStamp(), asr.getStamp()+1));  //trueSystem.out.println("更新值后:"+asr.getReference()); //11System.out.println("a2版本号 -->"+asr.getStamp()); //2System.out.println("b1版本号 -->"+asr.getStamp()); //2//第一个参数:期望值  第二个参数:更新值  第三个参数:期望的版本号  第四个参数:更新后的版本号//返回true代表交换成功   返回false代表交换失败System.out.println(asr.compareAndSet(11, 10,asr.getStamp(), asr.getStamp()+1));  //trueSystem.out.println("更新值后:"+asr.getReference());  //10System.out.println("b2版本号 -->"+asr.getStamp()); //3},"线程1").start();new Thread(()->{int stamp = asr.getStamp();System.out.println("c1版本号 -->"+stamp);try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(asr.compareAndSet(10, 66,stamp, stamp + 1));//falseSystem.out.println("更新值后:"+asr.getReference()); //自旋,获取主内存的值 10System.out.println("c2版本号 -->"+asr.getStamp()); //自旋,获取主内存的值版本 3},"线程2").start();}}

7.5.5 CAS总结

比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作,如果不是就一直循环。

缺点:

1.循环会耗时

2.一次性只能保证一个共享变量的原子性

3.会出现ABA问题(此时建议引入原子引用,使用带版本号的原子操作,例如:AtomicStampedReference中的getStamp()方法)

7.6 CAS与Synchronized:乐观锁与悲观锁

共同点:

CAS和Synchronized都可以保证多线程环境下共享数据的安全性。

不同点:

Synchronized是从悲观的角度出发**(悲观锁)**

​ 总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。因此Synchronized我们也将其称之为悲观锁。JDK中的ReentrantLock也是一种悲观锁。性能较差!

CAS是从乐观的角度出发**(乐观锁)**

​ 总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。CAS这种机制我们也可以将其称之为乐观锁。综合性能较好!

第8章 指令重排

8.1 什么是重排序?

​ 重排序就是编译器或者CPU的代码的的结构重排排序,已达到最佳的执行效果。重排大概分为编译器重排,处理器重排。

// 编译器重排
//优化前
int x = 1;
int y = 2;
int a1 = x * 1;
int b1 = y * 1;
int a2 = x * 2;
int b2 = y * 2;//优化后
int x = 1;
int y = 2;
int a1 = x * 1;
int a2 = x * 2;
int b1 = y * 1;
int b2 = y * 2;//经过这样的优化,可能对于CPU来说只要读取一次的x和y值。而原来的可能需要反复读取寄存器来交替x和y的值。
// 处理器重排
//初始化:
int a = 0;
int b = 0;
int x = 0;
int y = 0;//处理器A执行
a = 1; //A1
x = b; //A2  //处理器B执行
b = 2; //B1
y = a; //B2  

在这里插入图片描述

​ 由于处理器有读、写缓存区,写缓存区没有及时刷新到内存,造成其他处理器读到的值不是最新的,使得处理器执行的读写操作与内存上反应出的顺序不一致。

​ 上面这个例子,可能造成处理器A读到的b=0,处理器B读到的a=0。A1写a=1先写到处理器A的写缓存区中,此时内存中a=0。如果这时处理器B从内存中读a,读到的将是0。

8.2 代码案例

public class Barrier {int a = 0;int b = 0;int x = 0;int y = 0;//创建单个线程的线程池private static ExecutorService executorService1= Executors.newSingleThreadExecutor();private static ExecutorService executorService2= Executors.newSingleThreadExecutor();private static ExecutorService executorService3= Executors.newSingleThreadExecutor();public static void main(String ... args){for (int i=0;i< 1000000;i++){//初始化Barrier barrier=new Barrier();//处理器A执行executorService1.submit(()->{//写  缓冲区barrier.a = 1; //A1//可能出现仍然是从内存中读取到的0barrier.x = barrier.b; //A2print(barrier);});//处理器B执行executorService2.submit(()->{//写  缓冲区barrier.b = 2; //B1//可能出现仍然是从内存中读取到的0barrier.y = barrier.a; //B2print(barrier);});}}public static void print(Barrier barrier){executorService3.submit(()->{if(barrier.x==0 && barrier.y==0){
System.out.println("=======>"+barrier.a+" , "+barrier.b+" , "+barrier.x+" , "+barrier.y);}else {
System.out.println(barrier.a+" , "+barrier.b+" , "+barrier.x+" , "+barrier.y);}});}}

此时运行结果, 可以看到,确实有可能都是0。那么我们有没有办法解决这个问题呢?这时候我们就要提到内存屏障了。

8.3 内存屏障

8.3.1 概述

为了解决上述问题,处理器还是使用提供了个武器——内存屏障指令(Memory Barrier):

  1. 写内存屏障(Store Memory Barrier):处理器将当前存储缓存的值写回主存,以阻塞的方式。
  2. 读内存屏障(Load Memory Barrier):处理器处理失效队列,以阻塞的方式。

​ 通过加入内存屏障,保证了两个操作之间数据的可见性。 volatile关键字通过“内存屏障”来防止指令被重排序 , volatile会在读取数据前插入一个读屏障,写数据之后加入一个写屏障,所以,它可以避免CPU重排导致的问题,实现多线程之间数据的可见性。

8.3.2 内存屏障的主要类型

为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序。然而,对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎不可能,为此,Java内存模型采取保守策略。

下面是基于保守策略的JMM内存屏障插入策略:
在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的前面插入一个LoadStore屏障。

内存屏障的主要类型(以下了解即可)
不同硬件实现内存屏障的方式不同,Java内存模型屏蔽了这种底层硬件平台的差异,由JVM来为不同的平台生成相应的机器码。

Java内存屏障主要有Load和Store两类。
对Load Barrier来说,在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据
对Store Barrier来说,在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存

对于Load和Store,在实际使用中,又分为以下四种:

LoadLoad 屏障
序列:Load1,Loadload,Load2
确保Load1所要读入的数据能够在被Load2和后续的load指令访问前读入。通常能执行预加载指令或/和支持乱序处理的处理器中需要显式声明Loadload屏障,因为在这些处理器中正在等待的加载指令能够绕过正在等待存储的指令。 而对于总是能保证处理顺序的处理器上,设置该屏障相当于无操作。

StoreStore 屏障
序列:Store1,StoreStore,Store2
确保Store1的数据在Store2以及后续Store指令操作相关数据之前对其它处理器可见(例如向主存刷新数据)。通常情况下,如果处理器不能保证从写缓冲或/和缓存向其它处理器和主存中按顺序刷新数据,那么它需要使用StoreStore屏障。

LoadStore 屏障
序列: Load1; LoadStore; Store2
确保Load1的数据在Store2和后续Store指令被刷新之前读取。在等待Store指令可以越过loads指令的乱序处理器上需要使用LoadStore屏障。

StoreLoad 屏障
序列: Store1; StoreLoad; Load2
确保Store1的数据在被Load2和后续的Load指令读取之前对其他处理器可见。StoreLoad屏障可以防止一个后续的load指令 不正确的使用了Store1的数据,而不是另一个处理器在相同内存位置写入一个新数据。正因为如此,所以在下面所讨论的处理器为了在屏障前读取同样内存位置存过的数据,必须使用一个StoreLoad屏障将存储指令和后续的加载指令分开。Storeload屏障在几乎所有的现代多处理器中都需要使用,但通常它的开销也是最昂贵的。它们昂贵的部分原因是它们必须关闭通常的略过缓存直接从写缓冲区读取数据的机制。这可能通过让一个缓冲区进行充分刷新(flush),以及其他延迟的方式来实现。

8.3.3 示例

下面是在保守策略下,volatile读插入内存屏障后生成的指令序列示意图:

在这里插入图片描述

上述volatile写和volatile读的内存屏障插入策略非常保守。在实际执行时,只要不改变volatile写-读的内存语义,编译器可以根据具体情况省略不必要的屏障。

下面我们通过具体的示例代码来说明:

class VolatileBarrierExample {int a;volatile int v1 = 1;volatile int v2 = 2;void readAndWrite() {int i = v1;           // 第一个volatile读int j = v2;           // 第二个volatile读a = i + j;            //普通写v1 = i + 1;           // 第一个volatile写v2 = j * 2;           // 第二个 volatile写}//其他方法
}

针对readAndWrite()方法,编译器在生成字节码时可以做如下的优化:

在这里插入图片描述

8.4 volatile总结

volatile是Java虚拟机提供的轻量级的同步机制

1.保证可见性

2.不保证原子性

3.禁止指令重排

volatile是可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生。

第9章 并发包

在JDK的并发包里提供了几个非常有用的并发容器和并发工具类。供我们在多线程开发中进行使用。

9.1 ConcurrentHashMap

为什么要使用ConcurrentHashMap:

  1. HashMap线程不安全,会导致数据错乱
  2. 使用线程安全的Hashtable效率低下

基于以上两个原因,便有了ConcurrentHashMap的登场机会。

  • HashMap线程不安全演示。

公有、静态的集合:

public class Const {public static HashMap<String,String> map = new HashMap<>();
}

线程,向map中写入数据:

public void run() {for (int i = 0; i < 500000; i++) {Const.map.put(this.getName() + (i + 1), this.getName() + i + 1);}System.out.println(this.getName() + " 结束!");}

测试类:

public class Demo {public static void main(String[] args) throws InterruptedException {Thread1A a1 = new Thread1A();Thread1A a2 = new Thread1A();a1.setName("线程1-");a2.setName("线程2-");a1.start();a2.start();//休息10秒,确保两个线程执行完毕Thread.sleep(1000 * 5);//打印集合大小System.out.println("Map大小:" + Const.map.size());}
}

说明:两个线程分别向同一个map中写入50000个键值对,最后map的size应为:100000,但多运行几次会发现有以下几种错误:

  1. 假死:

    在这里插入图片描述

  2. 异常:

    在这里插入图片描述

  3. 错误结果:

    在这里插入图片描述

  • 为了保证线程安全,可以使用Hashtable。注意:线程中加入了计时

    公有、静态的集合:

    public class Const {public static Hashtable<String,String> map = new Hashtable<>();
    }
    

    线程,向map中写入数据:

    public void run() {long start = System.currentTimeMillis();for (int i = 0; i < 500000; i++) {Const.map.put(this.getName() + (i + 1), this.getName() + i + 1);}long end = System.currentTimeMillis();System.out.println(this.getName() + " 结束!用时:" + (end - start) + " 毫秒");}
    

    测试类:

    public class Demo {public static void main(String[] args) throws InterruptedException {Thread1A a1 = new Thread1A();Thread1A a2 = new Thread1A();a1.setName("线程1-");a2.setName("线程2-");a1.start();a2.start();//休息10秒,确保两个线程执行完毕Thread.sleep(1000 * 5);//打印集合大小System.out.println("Map大小:" + Const.map.size());}
    }
    

    执行结果:

    在这里插入图片描述

可以看到,Hashtable保证的线程安全,时间是2秒多。

  • 再看ConcurrentHashMap

    公有、静态的集合:

    public class Const {public static ConcurrentHashMap<String,String> map = new ConcurrentHashMap<>();
    }
    

    线程,向map中写入数据:

    public void run() {long start = System.currentTimeMillis();for (int i = 0; i < 500000; i++) {Const.map.put(this.getName() + (i + 1), this.getName() + i + 1);}long end = System.currentTimeMillis();System.out.println(this.getName() + " 结束!用时:" + (end - start) + " 毫秒");}
    

    测试类:

    public class Demo {public static void main(String[] args) throws InterruptedException {Thread1A a1 = new Thread1A();Thread1A a2 = new Thread1A();a1.setName("线程1-");a2.setName("线程2-");a1.start();a2.start();//休息10秒,确保两个线程执行完毕Thread.sleep(1000 * 5);//打印集合大小System.out.println("Map大小:" + Const.map.size());}
    }
    

    执行结果:

    在这里插入图片描述

    ConcurrentHashMap仍能保证结果正确,而且提高了效率。

HashTable效率低下原因:

public synchronized V put(K key, V value) 
public synchronized V get(Object key)

HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法,其他线程也访问HashTable的同步方法时,会进入阻塞状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。

在这里插入图片描述

ConcurrentHashMap高效的原因:CAS + 局部(synchronized)锁定分段式锁

在这里插入图片描述

9.2 CountDownLatch

CountDownLatch允许一个或多个线程等待其他线程完成操作,再执行自己。

例如:线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后,要线程2打印B之后才能打印C,所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行。

CountDownLatch构造方法:

public CountDownLatch(int count)// 初始化一个指定计数器的CountDownLatch对象

CountDownLatch重要方法:

public void await() throws InterruptedException// 让当前线程等待
public void countDown()	// 计数器进行减1
  • 示例
    1). 制作线程1:
public class ThreadA extends Thread {private CountDownLatch down ;public ThreadA(CountDownLatch down) {this.down = down;}@Overridepublic void run() {System.out.println("A");try {down.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("C");}
}

2). 制作线程2:

public class ThreadB extends Thread {private CountDownLatch down ;public ThreadB(CountDownLatch down) {this.down = down;}@Overridepublic void run() {System.out.println("B");down.countDown();}
}

3).制作测试类:

public class Demo {public static void main(String[] args) {CountDownLatch down = new CountDownLatch(1);//创建1个计数器new ThreadA(down).start();new ThreadB(down).start();}
}

4). 执行结果:
会保证按:A B C的顺序打印。

说明:

CountDownLatch中count down是倒数的意思,latch则是门闩的含义。整体含义可以理解为倒数的门栓,似乎有一点“三二一,芝麻开门”的感觉。

CountDownLatch是通过一个计数器来实现的,每当一个线程完成了自己的任务后,可以调用countDown()方法让计数器-1,当计数器到达0时,调用CountDownLatch的await()方法的线程阻塞状态解除,继续执行。

9.3 CyclicBarrier

概述

CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。

例如:公司召集5名员工开会,等5名员工都到了,会议开始。

我们创建5个员工线程,1个开会线程,几乎同时启动,使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。

CyclicBarrier构造方法:

public CyclicBarrier(int parties, Runnable barrierAction)// 用于在线程到达屏障时,优先执行barrierAction,方便处理更复杂的业务场景

CyclicBarrier重要方法:

public int await()// 每个线程调用await方法告诉CyclicBarrier我已经到达了屏障,然后当前线程被阻塞
  • 示例代码:
    1). 制作员工线程:
public class PersonThread extends Thread {private CyclicBarrier cbRef;public PersonThread(CyclicBarrier cbRef) {this.cbRef = cbRef;}@Overridepublic void run() {try {Thread.sleep((int) (Math.random() * 1000));System.out.println(Thread.currentThread().getName() + " 到了! ");cbRef.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}}
}

2). 制作开会线程:

public class MeetingThread extends Thread {@Overridepublic void run() {System.out.println("好了,人都到了,开始开会......");}
}

3). 制作测试类:

public class Demo {public static void main(String[] args) {CyclicBarrier cbRef = new CyclicBarrier(5, new MeetingThread());//等待5个线程执行完毕,再执行MeetingThreadPersonThread p1 = new PersonThread(cbRef);PersonThread p2 = new PersonThread(cbRef);PersonThread p3 = new PersonThread(cbRef);PersonThread p4 = new PersonThread(cbRef);PersonThread p5 = new PersonThread(cbRef);p1.start();p2.start();p3.start();p4.start();p5.start();}
}

4). 执行结果:

在这里插入图片描述

使用场景

使用场景:CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。

需求:使用两个线程读取2个文件中的数据,当两个文件中的数据都读取完毕以后,进行数据的汇总操作。

9.4 Semaphore

Semaphore(发信号)的主要作用是控制线程的并发数量。

synchronized可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行。

Semaphore可以设置同时允许几个线程执行。

Semaphore字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。

Semaphore构造方法:

public Semaphore(int permits)						permits 表示许可线程的数量
public Semaphore(int permits, boolean fair)			fair 表示公平性,如果这个设为 true 的话,下次执行的线程会是等待最久的线程

Semaphore重要方法:

public void acquire() throws InterruptedException	表示获取许可
public void release()								release() 表示释放许可
  • 示例一:同时允许1个线程执行

1). 制作一个Service类:

public class Service {private Semaphore semaphore = new Semaphore(1);//1表示许可的意思,表示最多允许1个线程执行acquire()和release()之间的内容public void testMethod() {try {semaphore.acquire();System.out.println(Thread.currentThread().getName()+ " 进入 时间=" + System.currentTimeMillis());Thread.sleep(1000);System.out.println(Thread.currentThread().getName()+ "   结束 时间=" + System.currentTimeMillis());semaphore.release();//acquire()和release()方法之间的代码为"同步代码"} catch (InterruptedException e) {e.printStackTrace();}}
}

2). 制作线程类:

public class ThreadA extends Thread {private Service service;public ThreadA(Service service) {super();this.service = service;}@Overridepublic void run() {service.testMethod();}
}

3). 测试类:

public class Demo {public static void main(String[] args) {Service service = new Service();//启动5个线程for (int i = 1; i <= 5; i++) {ThreadA a = new ThreadA(service);a.setName("线程 " + i);a.start();//5个线程会同时执行Service的testMethod方法,而某个时间段只能有1个线程执行}}
}

4). 结果:

在这里插入图片描述

  • 示例二:同时允许2个线程同时执行
    1). 修改Service类,将new Semaphore(1)改为2即可:
public class Service {private Semaphore semaphore = new Semaphore(2);//2表示许可的意思,表示最多允许2个线程执行acquire()和release()之间的内容public void testMethod() {try {semaphore.acquire();System.out.println(Thread.currentThread().getName()+ " 进入 时间=" + System.currentTimeMillis());Thread.sleep(5000);System.out.println(Thread.currentThread().getName()+ "   结束 时间=" + System.currentTimeMillis());semaphore.release();//acquire()和release()方法之间的代码为"同步代码"} catch (InterruptedException e) {e.printStackTrace();}}
}

2). 再次执行结果:

在这里插入图片描述

9.5 Exchanger

概述

Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。

这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。

Exchanger构造方法:

public Exchanger()

Exchanger重要方法:

public V exchange(V x)
  • 示例一:exchange方法的阻塞特性

1).制作线程A,并能够接收一个Exchanger对象:

public class ThreadA extends Thread {private Exchanger<String> exchanger;public ThreadA(Exchanger<String> exchanger) {super();this.exchanger = exchanger;}@Overridepublic void run() {try {System.out.println("线程A欲传递值'礼物A'给线程B,并等待线程B的值...");System.out.println("在线程A中得到线程B的值=" + exchanger.exchange("礼物A"));} catch (InterruptedException e) {e.printStackTrace();}}}

2). 制作main()方法:

public class Demo {public static void main(String[] args) {Exchanger<String> exchanger = new Exchanger<String>();ThreadA a = new ThreadA(exchanger);a.start();}
}

3).执行结果:

在这里插入图片描述

  • 示例二:exchange方法执行交换

1).制作线程A:

public class ThreadA extends Thread {private Exchanger<String> exchanger;public ThreadA(Exchanger<String> exchanger) {super();this.exchanger = exchanger;}@Overridepublic void run() {try {System.out.println("线程A欲传递值'礼物A'给线程B,并等待线程B的值...");System.out.println("在线程A中得到线程B的值=" + exchanger.exchange("礼物A"));} catch (InterruptedException e) {e.printStackTrace();}}
}

2).制作线程B:

public class ThreadB extends Thread {private Exchanger<String> exchanger;public ThreadB(Exchanger<String> exchanger) {super();this.exchanger = exchanger;}@Overridepublic void run() {try {System.out.println("线程B欲传递值'礼物B'给线程A,并等待线程A的值...");System.out.println("在线程B中得到线程A的值=" + exchanger.exchange("礼物B"));} catch (InterruptedException e) {e.printStackTrace();}}
}

3).制作测试类:

public class Demo {public static void main(String[] args) throws InterruptedException {Exchanger<String> exchanger = new Exchanger<String>();ThreadA a = new ThreadA(exchanger);ThreadB b = new ThreadB(exchanger);a.start();b.start();}
}

4).执行结果:

在这里插入图片描述

  • 示例三:exchange方法的超时

1).制作线程A:

public class ThreadA extends Thread {private Exchanger<String> exchanger;public ThreadA(Exchanger<String> exchanger) {super();this.exchanger = exchanger;}@Overridepublic void run() {try {System.out.println("线程A欲传递值'礼物A'给线程B,并等待线程B的值,只等5秒...");System.out.println("在线程A中得到线程B的值 =" + exchanger.exchange("礼物A",5, TimeUnit.SECONDS));System.out.println("线程A结束!");} catch (InterruptedException e) {e.printStackTrace();} catch (TimeoutException e) {System.out.println("5秒钟没等到线程B的值,线程A结束!");}}
}

2).制作测试类:

public class Run {public static void main(String[] args) {Exchanger<String> exchanger = new Exchanger<String>();ThreadA a = new ThreadA(exchanger);a.start();}
}

3).测试结果:

在这里插入图片描述

使用场景

使用场景:可以做数据校对工作

需求:比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水。为了避免错误,采用AB岗两人进行录入,录入到两个文件中,系统需要加载这两个文件,并对两个文件数据进行校对,看看是否录入一致。

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. Nacos是什么

    本文来说下Nacos是什么 文章目录概述概述...

    2024/4/27 7:46:48
  2. HtmlUnit 爬虫简单案例——模拟登陆CSDN

    最近要弄一个爬虫程序,想着先来个简单的模拟登陆, 在权衡JxBrowser和HtmlUnit 两种技术, JxBowser有界面呈现效果,但是对于某些js跳转之后的效果获取比较繁琐。随后考虑用HtmlUnit, 想着借用咱们CSND的登陆练练手。谁知道CSDN的登陆,js加载时间超长,不设置长一点的加载…...

    2024/5/3 0:42:08
  3. 聚合查询+联合查询+子查询

    文章目录一、聚合查询1.聚合函数2.GROUP BY子句3.HAVING二、联合查询join on1.内连接2.外连接3.自连接三、子查询EXISTS关键字四、联合查询总结一、聚合查询 1.聚合函数 常见的统计总数&#xff0c;计算平均值的操作&#xff0c;可以用聚合函数来实现&#xff0c;常见的聚合函…...

    2024/4/6 12:16:21
  4. Mac报错✨Unity IOS打包包错:LocationService class is used but Locations Usage Description is empty.

    哈喽大家好&#xff0c;你的橙哥突然出现~ 本系列博客地址&#xff1a;传送门 导入GF后&#xff0c;切换为IOS平台&#xff0c;在打包XCode时&#xff0c;Unity打包包错。提示&#xff1a; LocationService class is used but Locations Usage Description is empty. App will …...

    2024/5/2 22:33:36
  5. 用动态规划算法求数列中三个数的最大值,且该三个数的下标间距需大于等于K

    1. 题目描述 程序设计竞赛的参赛队伍通常由3人组成。如何从学生中选出三人组成最强的队伍而又不失公平&#xff08;即不让选出的学生均集中在个别班级里&#xff09;呢。虽然每个班级的学生人数有出入&#xff0c;但上限是固定的&#xff0c;并且同一个教学班的学生其学号是连续…...

    2024/4/15 13:50:27
  6. 【Java】如何提高算法效率——时间复杂度和空间复杂度

    【写在前边】 当我们学习编程语言到达一定程度之后&#xff0c;就会开始注重代码的效率&#xff0c;这时候就会开始研究算法这么个东西&#xff0c;算法顾名思义就是计算方法&#xff0c;就好比你做一道数学题&#xff0c;有简单的办法也有麻烦的办法&#xff0c;但是简单的办法…...

    2024/5/3 3:23:15
  7. 初识R语言之数据处理篇

    差异基因筛选 差异系数&#xff0c;可以体现对象数据与标准数据的相对差异,数值越大&#xff0c;表示不平衡程度越大[1] 设x与y分别为对象数据和标准数据&#xff0c;则差异系数k的表达式为&#xff1a; 差异倍数 差异表达基因分析&#xff1a;差异倍数(fold change), 差异的…...

    2024/4/15 13:50:17
  8. 3.逻辑成员身份运算符(not,and,or,in)

    1.not,and,or条件运算not:就是把紧跟其后的那个条件结果取反在纯and语句中&#xff0c;(从左到右)碰到假&#xff0c;返回假&#xff1b;如果左右都为真&#xff0c;返回最后一个真 (串)在纯or语句中&#xff0c;(从左到右)碰到真&#xff0c;就返回真&#xff1b;如果左右…...

    2024/4/27 20:21:18
  9. 安装Redis

    CentOS 7.6上安装Redis 查看gcc版本(centos7.6中的gcc默认为4.8.5版本 , redis 6 需要5.3以上的gcc) gcc -v 通过yum安装centos-release-scl软件集yum -y install centos-release-scl安装gcc9及相关组件yum -y install devtoolset-9-gcc devtoolset-9-gcc-c devtoolset-9-binu…...

    2024/4/16 3:44:34
  10. javaweb笔记

    重新学习了一下javaweb&#xff0c;参考于狂神说java学习视频&#xff0c;手写一份javaweb笔记以便于加强印象巩固知识。 视频地址&#xff1a;https://www.bilibili.com/video/BV12J411M7Sj 记得三连...

    2024/4/15 13:50:27
  11. meterpreter shell 出入 cmd

    进入&#xff1a;shell 出去&#xff1a;exit...

    2024/4/6 12:16:13
  12. 所驼门王的宝藏

    所驼门王的宝藏 题目大意 给出一张大小为RCR\times CRC的矩形&#xff0c;矩形上有一些点设有传送门传送门有三种 “横天门”&#xff1a;由该门可以传送到同行的任一宫室&#xff1b;“纵寰门”&#xff1a; 由该门可以传送到同列的任一宫室&#xff1b;“自由门”&#xf…...

    2024/4/16 14:28:05
  13. JavaScript 数学曲线—连锁螺线

    引子 继等角螺线&#xff0c;接着尝试连锁螺线。 OriginMy GitHub 简介 在 阿基米德螺线 中提到的通用的公式&#xff0c;当 c -2 时&#xff0c;就是连锁螺线&#xff0c;又称为 Lituus 曲线。Roger Cotes 在他的著作 《Harmonia Mensurarum》(1722) 中对该曲线进行了描述…...

    2024/4/19 12:28:21
  14. 正负链表分离的另一种核心代码写法(C语言,带头节点)

    #include<stdio.h> #include<stdlib.h> struct Node{int Data;struct Node *Next; }; void insert_tail(struct Node *L,int num){/*尾插法*/struct Node *p,*q;pL;while(p->Next!NULL){pp->Next;}q(struct Node *)malloc(sizeof(struct Node));q->Datanu…...

    2024/4/20 5:34:44
  15. idea查看jdk编译版本

    ...

    2024/4/21 18:45:47
  16. Android Intent的几种跳转方式

    1、显示跳转 Intent intentnew Intent(); intent.setClass(MainActivity6.this, MainActivity6son1.class); startActivity(intent); 2、隐式跳转 Intent intentnew Intent(); intent.setAction("aaa.bbb.ccc.ddd"); startActivity(intent); manifest里需添加act…...

    2024/4/5 7:08:08
  17. 从单张图重建三维人体模型综述(二)

    《3D Human Body Reconstruction from a Single Image via Volumetric Regression》 本文提出使用端到端卷积神经网络通过体积回归直接重建人体的三维几何结构。所提出的方法不需要拟合形状模型&#xff0c;并且可以从各种输入类型&#xff08;无论是标记点、图像还是分割模板&…...

    2024/4/5 7:08:07
  18. LocalServices

    记录一下&#xff0c;可以理解为进程级ThreadLocal&#xff0c;源码比较简单。 /** Copyright (C) 2013 The Android Open Source Project** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance wi…...

    2024/4/15 13:51:23
  19. 优点三维逆向建模——消除实景三维应用后顾之忧

    随着实景三维数据获取技术的日趋成熟&#xff0c;空间三维点云数据广泛应用于各个领域&#xff0c;实景三维数据的应用成为大家关注的焦点&#xff01; 优点逆向三维系统具有足够完善的实景三维数据应用&#xff0c;精良的渲染及仿真效果&#xff0c;智能化操作系统&#xff0c…...

    2024/4/6 12:16:09
  20. 后代选择器

    作用&#xff1a;根据HTML标签的嵌套关系&#xff0c;选择父元素后代中满足条件的元素 选择器语法:选择器1 选择器2 {css} 结果:在选择器1所找到标签的后代(儿子&#xff0c;孙子&#xff0c;重孙子...&#xff09;中&#xff0c;找到满足选择器2的标签&#xff0c;设置样 …...

    2024/4/15 13:51:08

最新文章

  1. DRF解析器源码分析

    DRF解析器源码分析 1 解析器 解析请求者发来的数据&#xff08;JSON&#xff09; 使用 request.data 获取请求体中的数据。 这个 reqeust.data 的数据怎么来的呢&#xff1f;其实在drf内部是由解析器&#xff0c;根据请求者传入的数据格式 请求头来进行处理。 drf默认的解…...

    2024/5/3 7:37:51
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 【LeetCode热题100】【二叉树】二叉树的中序遍历

    题目链接&#xff1a;94. 二叉树的中序遍历 - 力扣&#xff08;LeetCode&#xff09; 中序遍历就是先遍历左子树再遍历根最后遍历右子树 class Solution { public:void traverse(TreeNode *root) {if (!root)return;traverse(root->left);ans.push_back(root->val);tra…...

    2024/4/29 11:15:33
  4. 解决npm install安装node-sass包容易失败的问题

    具体问题如下&#xff1a; npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependency tree npm ERR! npm ERR! While resolving: XXX3.4.0 npm ERR! Found: webpack5.31.2 npm ERR! node_modules/webpack npm ERR! peer webpack”^4.0.0 || ^5.0.0″ from html-…...

    2024/5/2 18:30:38
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/5/2 16:16:39
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/5/2 9:28:15
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/5/2 15:04:34
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/5/2 9:07:46
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57