文章目录
- 1.线程不安全问题
- 2.synchronized
- 3.volatile
- 4.wait()方法和notify()方法
1.线程不安全问题
java">public class demo2 {public static int count=0;public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread (()->{for (int i = 0; i <50000 ; i++) {increase();}});Thread thread2 = new Thread (()->{for (int i = 0; i <50000 ; i++) {increase();}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(count);}public static void increase(){count++;}
}
出现线程安全问题
原因:
1.线程是抢占执行的(执行顺序是随机的)
线程的执行是人为无法改变的,完全由CPU自己调度,抢占式线程是造成线程不安全的主要原因.
2.共享资源的并发性
多个线程修改同一个变量会引发线程不安全
当多个线程同时访问和修改同一个共享资源(如变量、对象、数据结构等)时,如果没有适当的同步机制,就可能导致数据不一致。
3.非原子性
某些操作(如复合操作)并不是原子性的,即它们可以被中断。例如,读取一个变量、修改它并写回的过程可能被其他线程打断,导致数据不一致。
4.可见性问题
可见性是指当一个线程修改了共享变量的值,其他线程能够立即看到这个修改。 由于 CPU 缓存的存在,线程对共享变量的修改可能不会立即刷新到主内存中,导致其他线程读取到过时的数据。
5.指令重排序
为了提高性能,编译器和处理器可能会对指令进行重排序。 指令重排序可能会导致程序的执行顺序与代码的编写顺序不一致,从而导致线程安全问题。
2.synchronized
synchronized 关键字: 这是最基本的同步机制。 它可以用于修饰方法或代码块。
同步方法: 当一个线程进入 synchronized 方法时,它会获取该方法所属对象的锁。 其他线程必须等待该线程释放锁才能进入该方法。
加上synchronized关键字此代码逻辑正确
-
同步方法
使用 synchronized 修饰一个实例方法,表示该方法在同一时间只能被一个线程访问。
没有加synchronized关键字
加上synchronized关键字
-
同步代码块
使用 synchronized 修饰代码块,可以指定一个对象作为锁,这样可以提高程序的并发性,因为不同的代码块可以使用不同的锁
-
静态同步方法
如果使用 synchronized 修饰静态方法,那么锁定的是类的 Class 对象,而不是实例对象。这意味着同一时间只有一个线程可以访问该类的所有静态同步方法。
只给一个线程加锁也会出现线程安全问题
没有出现锁竞争线程安全出现问题。
线程获取锁:
1.若只有一个线程A可以获取锁,不会产生锁竞争
2.线程A与线程B不是竞争同一把锁,不会产生锁竞争
3.线程A与线程B竞争同一把锁时,产生锁竞争,谁先抢到就执行自己逻辑,另外一线程阻塞等待,直到待持锁的完成,再参与锁竞争
synchronized特性:
1.通过锁保证了原子性
2.通过串行避免线程读取到过时的数据
3.不保证有序性(不会禁止指令重排序)
3.volatile
- volatile 关键字
volatile 能保证内存可⻅性
volatile 修饰的变量, 能够保证 “内存可⻅性”.
比特就业课
代码在写⼊ volatile 修饰的变量的时候,
• 改变线程⼯作内存中volatile变量副本的值
• 将改变后的副本的值从⼯作内存刷新到主内存
代码在读取 volatile 修饰的变量的时候,
• 从主内存中读取volatile变量的最新值到线程的⼯作内存中
• 从⼯作内存中读取volatile变量的副本
java">public class demo5 {// 创建两个线程// * 1. 第一个线程,不停循环去执行自己的任务// * 2. 第二个线程,输入一个停止标识,使第一个线程退出static int flag = 0;static Object joker;public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println("t1线程启动");while (flag == 0) {//不停循环,处理任务}System.out.println("t1线程结束");});t1.start();Thread t2 = new Thread(() -> {System.out.println("t2线程启动");Scanner scanner=new Scanner(System.in);System.out.println("输入一个非零整数");flag=scanner.nextInt();System.out.println("t2线程结束");});t2.start();}}
使用volatile
volatile特性:
1.不保证原子性
2.实现了内存可见性
3.禁止指令重排序
4.wait()方法和notify()方法
wait()⽅法
wait 做的事情:
• 使当前执⾏代码的线程进⾏等待. (把线程放到等待队列中)
• 释放当前的锁
• 满⾜⼀定条件时被唤醒, 重新尝试获取这个锁.
wait 要搭配 synchronized 来使⽤. 脱离synchronized 使⽤ wait 会直接抛出异常.
wait 结束等待的条件:
• 其他线程调⽤该对象的 notify ⽅法.
• wait 等待时间超时 (wait ⽅法提供⼀个带有 timeout 参数的版本, 来指定等待时间).
• 其他线程调⽤该等待线程的 interrupted ⽅法, 导致 wait 抛出 InterruptedException 异常.
notify ⽅法是唤醒等待的线程.
• ⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其
它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
• 如果有多个线程等待,则有线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 “先来后到”)
• 在notify()⽅法后,当前线程不会⻢上释放该对象锁,要等到执⾏notify()⽅法的线程将程序执⾏
完,也就是退出同步代码块之后才会释放对象锁
java">public static void main(String[] args) {// 定义一个锁对象Object locker = new Object();// 创建调用wait() 的线程Thread t1 = new Thread(() -> {while (true) {synchronized (locker) {System.out.println("调用wait()之前...");// 执行线程的逻辑// 如果没有满足线程所需要的数据,那么就等待try {locker.wait(0l);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("wait()唤醒之后..");System.out.println("===================");}}});Thread t2 = new Thread(() -> {while (true) {System.out.println("notifyAll()之前...");// 同等待的锁对象进行唤醒synchronized (locker) {locker.notifyAll();}System.out.println("notifyAll()之后...");try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}});// 启动线程t1.start();t2.start();}
wait()与notify()必须配合synchronized使用,并且使用同一对象