1.sleep是否释放锁 【synchronized 和 lock】
1.synchronized 错误案例!
//错误示范:因为两个线程各自行为。不消费同个资源,无所谓的是否释放锁
@Slf4j
public class MyThread2 extends Thread {
@SneakyThrows
@Override
public synchronized void run() {
String name = Thread.currentThread().getName();
log.info("线程获取=====》锁{}", name);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("线程释放=====》锁{}", name);
}
public static void main(String[] args) {
MyThread2 mt=new MyThread2();
mt.start();
MyThread2 mt2=new MyThread2();
mt2.start();
}
//结果:
: 线程获取=====》锁Thread-0
: 线程获取=====》锁Thread-1
: 线程释放=====》锁Thread-1
: 线程释放=====》锁Thread-0
2.synchronized 正确案例——释放锁,至少两个线程针对同一资源消费
@Slf4j
public class MyThread2 implements Runnable {
@SneakyThrows
@Override
public synchronized void run() {
String name = Thread.currentThread().getName();
log.info("线程获取=====》锁{}", name);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("线程释放=====》锁{}", name);
}
public static void main(String[] args) {
MyThread2 run=new MyThread2();
//两个线程消耗/竞争同资源run
new Thread(run).start();
new Thread(run).start();
}
}
//
线程获取=====》锁Thread-0
线程释放=====》锁Thread-0
线程获取=====》锁Thread-1
线程释放=====》锁Thread-1
结论:Thread-0从获取 直到 释放锁,其他线程无法获取该锁。sleep方法不释放锁,锁的对象是this/run实例 对象锁
3.lock案例
@Slf4j
public class Runn2 implements Runnable {
private static final Lock lock = new ReentrantLock();
@Override
public void run() {
String name = Thread.currentThread().getName();
lock.lock();
log.info("线程获取=====》锁{}", name);
try {
Thread.sleep(3000);
log.info("线程释放=====》锁{}", name);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Runn2 run = new Runn2();
new Thread(run).start();
new Thread(run).start();
}
}
//
线程获取=====》锁Thread-1
线程释放=====》锁Thread-1
线程获取=====》锁Thread-0
线程释放=====》锁Thread-0
总结:上述synchronized、lock 证明sleep()方法不释放锁!,但不占用CPU资源。
2. 响应中断
/**
* 线程每隔1秒钟输出当前时间,运行时被中断
*/
public class Runn2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("我被中断了!");
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runn2());
thread.start();
Thread.sleep(6500);
thread.interrupt();//指定线程发送中断请求,并不是立马停止执行。
}
}
//
Mon Oct 30 16:09:21 CST 2023
Mon Oct 30 16:09:22 CST 2023
Mon Oct 30 16:09:23 CST 2023
Mon Oct 30 16:09:24 CST 2023
Mon Oct 30 16:09:25 CST 2023
Mon Oct 30 16:09:26 CST 2023
Mon Oct 30 16:09:27 CST 2023
我被中断了!
Mon Oct 30 16:09:27 CST 2023
java.lang.InterruptedException: sleep interrupted
at java.base/java.lang.Thread.sleep(Native Method)
at java.base/java.lang.Thread.sleep(Thread.java:334)
at java.base/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
at com.Xx.XX.XX.common.Runn2.run(Runn2.java:28)
at java.base/java.lang.Thread.run(Thread.java:829)
Mon Oct 30 16:09:28 CST 2023
Mon Oct 30 16:09:29 CST 2023
总结:线程中断是由线程自己决定的。其他线程只是给当前线程设置中断标志位。上述图中,主线程给副线程thread.interrupt()设置中断。 Thread.interrupted() 此方法检测线程是否中断,及清除中断标志位。第二次调用会返回false。 重点:图中副线程被中断,执行 TimeUnit.SECONDS.sleep(1)时抛出中断异常。【一般情况下,捕获中断异常,开发人员在代码中返回,或不再后续执行处理数据。】
中断作用:
假设用户下载一个很大的文件,当前网络比较慢,导致下载很缓慢,用户需要取消下载。
此时则需要让当前正在运行的线程终止!
3.Thread中interrupted()方法和isInterrupted()方法区别总结
1.interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态
2.isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会重置当前线程的中断状态
4.synchronized关键字 ——锁指哪些?
synchronized的作用:可修饰方法、代码块。一个线程正在执行run()
方法,其他线程需要等待,直到该方法执行完毕才能继续执行。
/**1.锁为某个对象,非自身 如 obj,此处修饰代码块*/
@Slf4j
public class Runn2 implements Runnable {
private Object obj = new Object();
@Override
public void run() {
test();
}
private int count = 0;
//代码块的锁 this 等同于 public sysnchronized void test(){...}
public void test() {
synchronized (obj) { //任何线程进入都需要拿到obj的对象锁,如果对象已经被锁定,那就只能等待。
count++;
System.out.println(Thread.currentThread().getName()+"===>>"+count);
}
}
public static void main(String[] args) throws InterruptedException {
Runn2 runn2 = new Runn2();
Thread thread1 = new Thread(runn2);
Thread thread2 = new Thread(runn2);
thread2.start();
thread1.start();
/*
Thread-1===>>1
Thread-0===>>2
*/
}
}
/**2.锁为自身 this关键字 资源对象本身*/
@Slf4j
public class Runn2 implements Runnable {
private Object obj = new Object();
@Override
public void run() {
test();
}
private int count = 0;
public void test() {
synchronized (this) { //任何线程进入都需要拿到obj的对象锁,如果对象已经被锁定,那就只能等待。
count++;
System.out.println(Thread.currentThread().getName()+"===>>"+count);
}
}
public static void main(String[] args) throws InterruptedException {
Runn2 runn2 = new Runn2();
Thread thread1 = new Thread(runn2);
Thread thread2 = new Thread(runn2);
thread2.start();
thread1.start();
/*
Thread-0===>>1
Thread-1===>>2
*/
}
}
/**3.类级别的锁 即类的Class对象锁*/
1中加个static 关键字
public static synchronized void test() {
// 代码逻辑
}
5.多个线程执行一个加锁,一个未加锁方法效果
@Slf4j
public class MyThread2 {
public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + " m1 start");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m1 end");
}
public void m2() {
System.out.println(Thread.currentThread().getName() + " m2 start");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " m2 end");
}
public static void main(String[] args) {
MyThread2 t = new MyThread2();
new Thread(t::m1).start();
new Thread(t::m2).start();
new Thread(t::m2).start();
}
}
/*
Thread-1 m2 start
Thread-2 m2 start
Thread-0 m1 start
Thread-2 m2 end
Thread-1 m2 end
Thread-0 m1 end
*/
总结:加锁、未加锁方法两者互不影响
6.针对5做个存款加锁,查看存款的例子
public class MyThread2 {
/**
* 名称
*/
private String acountName;
/**
* 存款
*/
private double money;
public synchronized void setValue(String acountName, double money) {
this.acountName = acountName;
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.money = money;
}
//查看存款的方法不加锁
public /*synchronized*/ double getMoney() {
return this.money;
}
public static void main(String[] args) {
MyThread2 a = new MyThread2();
new Thread(() -> a.setValue("张三", 100.0)).start();
System.out.println(a.getMoney()); // 0.0
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(a.getMoney()); // 100.0
}
/**
0.0
100.0
*/
}
7.同步的方法调用另一个同步方法,一个线程获得某个对象锁,再次申请仍会得到该对象锁
public class MyThread2 {
synchronized void m1() {
System.out.println("m1 start ");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
m2();System.out.println("m1 end");
}
synchronized void m2() {System.out.println("m2 start ");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" m2 end"); // 这句话会打印,调用m2时,不会发生死锁
}
}
8.线程出现异常会释放锁,没处理完的数据其他线程访问 问题
/**
* synchronized 代码块中,如果发生异常,锁会被释放.
* 在并发处理过程中,有异常要多加小心,不然可能发生数据不一致的情况。
*/
public class MyThread2 {
int count = 0;
synchronized void m() {
System.out.println(Thread.currentThread().getName() + " start");
while (true) {
count++;
System.out.println(Thread.currentThread().getName() + " count=" + count);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) { // 当count == 5 时,synchronized代码块会抛出异常
int i = 1 / 0;
}
}
}
public static void main(String[] args) {
MyThread2 t = new MyThread2 ();
Runnable r = new Runnable() {
@Override
public void run() {
t.m();
}
};
new Thread(r, "t1").start(); // 执行到第5秒时,抛出 ArithmeticException
// 如果抛出异常后,t2 会继续执行,就代表t2拿到了锁,即t1在抛出异常后释放了锁
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(r, "t2").start();
}
/**需要注意不被迷惑的点:t1、t2线程同时执行同一资源r,count为5产生异常,t2开始获取锁执行资源r,此时count值不会清零。同一份资源!同一资源!概念上易出错点
t1 start
t1 count=1
t1 count=2
t1 count=3
t1 count=4
t1 count=5
Exception in thread "t1" java.lang.ArithmeticException: / by zero
at com.xx.MyThread2.m(MyThread2.java:33)
at com.xx.MyThread2$1.run(MyThread2.java:43)
at java.base/java.lang.Thread.run(Thread.java:829)
t2 start
t2 count=6
t2 count=7
t2 count=8
t2 count=9
t2 count=10
*/
}
9.volatile关键字,确保可见性和有序性。但不确定原子性
public class MyThread2 {
volatile boolean running = true; // 对比有无volatile的情况下,整个程序运行结果的区别
void m() {
System.out.println(" m start ");
while (running) { // 直到主线程将running设置为false,T线程才会退出
// 在while中加入一些语句,可见性问题可能就会消失,这是因为加入语句后,CPU可能就会出现空闲,然后就会同步主内存中的内容到工作内存
// 所以,可见性问题可能会消失
/* try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
System.out.println(" m end ");
}
public static void main(String[] args) {
MyThread2 t = new MyThread2();
new Thread(t::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.running = false;
}
}
/**
1.当try处代码隐藏,加上volatile,会出现结果
m start
m end
Process finished with exit code 0
2.当try处代码隐藏,不加volatile,会出现结果
m start 一直显示不再变动
3.当try处代码不隐藏,加不加volatile,结果
m start
m end
Process finished with exit code 0
*/
上述原因:线程有自己独立空间,存储需要用的变量副本。线程对共享变量操作,会在自己工作内存中完成,再同步给主内存。 只有使用volatile,将会强制所有线程都去堆内存中读取running/共享变量的值
总结:多线程下访问共享变量,使用volatile
关键字可以确保可见性和有序性。
-
可见性(Visibility):当一个线程修改了一个被
volatile
修饰的变量的值时,这个新值会立即被写回主内存,并且其他线程在读取该变量时会立即从主内存中获取最新的值。这样可以避免线程之间使用过期的缓存值,保证了对该变量的修改对其他线程是可见的。
概述:多线程之间看到的最新的值 -
有序性(Ordering):
volatile
关键字可以防止指令重排优化。在没有volatile
修饰的情况下,编译器和处理器可能会对指令进行重排,以提高执行效率。然而,这种重排可能会导致多线程程序出现意外的行为。通过使用volatile
关键字,可以防止指令重排,确保指令按照程序顺序执行。
适用:标志位或计数器。符合操作类似递增,volatile无法提供原子性,考虑加锁或原子类。
3.原子性:原子性指的是一个操作是不可被中断的整体操作,要么完成,要么不执行。在多线程环境下,如果多个线程同时对共享变量进行修改,可能会导致数据竞争和不一致的结果。为了保证原子性,可以使用锁(如synchronized关键字、ReentrantLock等)或原子类(如AtomicInteger、AtomicLong等)来控制对共享变量的访问,从而保证操作的原子性。
synchronized
关键字在多线程编程中可以保证【同一时间只有一个线程访问共享资源,从而保证了线程之间的可见性和原子性。】
10.不要以字符串常量作为锁定对象
/**
字符串常量是全局唯一的,因此多个线程如果都使用相同的字符串常量作为锁定对象,就会导致所有这些线程都竞争同一个锁
1.其他代码的干扰:字符串常量作为锁定对象时,在整个应用程序中的任何地方,其他代码都可以使用相同的字符串常量作为锁定对象,这样就可能产生不可预期的同步问题。
2.可能引发死锁:如果多个线程都在不同的位置使用相同的字符串常量作为锁定对象,并且这些线程按照不同的顺序尝试获取这些锁,就有可能引发死锁。
*/
public class MyThread2 {
//建议使用的对象锁:private final Object lock = new Object();
String s1 = "s1";
String s2 = "s2";
void m1() {
synchronized (s1) {
}
}
void m2() {
synchronized (s2) {
}
}
}
11.锁的对象发生变化,引用变为另一个对象。锁就释放掉了
/**
* 锁定某个对象o,如果o属性发生变化,不影响锁的使用
* 但是如果o编程另一个对象,则锁定的对象发生变化,
* 所以锁对象通常要设置为 final类型,保证引用不可以变
*/
public class T {
Object o = new Object();
void m() {
synchronized (o) {
while (true) {
System.out.println(Thread.currentThread().getName());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
T t = new T();
new Thread(t::m, "线程1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread thread2 = new Thread(t::m, "线程2");
t.o = new Object(); // 改变锁引用, 线程2也有机会运行,否则一直都是线程1 运行
thread2.start();
}
}
Java高并发_timeunit.seconds.sleep(1);-CSDN博客 之前的模糊概念,重新理了一下遍。例子也好啊 推荐!
11.java 中sleep yield join wait的区别
1、sleep:让出CPU调度,Thread类的方法,必须带一个时间参数。会让当前线程休眠进入阻塞状态并释放CPU(cpu资源宝贵!只有在线程running的时候,才会获取cpu片段)
ep:main方法中Thread.sleep(3000)此时主线程休眠。 run方法中则是子线程休眠
谁执行Thread.sleep(arg)代码 谁休眠。 也可用
TimeUnit.MILLISECONDS.sleep(10) 不释放锁 2、yield:让出CPU调度,执行yield()的线程有可能在进入到可执行状态后马上又被执行,另外yield()方法只能使同优先级或者高优先级的线程得到执行机会,这也和sleep()方法不同。yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。调用yield方法只是一个建议,告诉线程调度器我的工作已经做的差不多了,可以让别的相同优先级的线程使用CPU了,没有任何机制保证采纳。 Thread类提供的一个静态方法 ep:thread1.yield() thread1暂让CPU,不释放锁3、join:让出CPU调度,join()方法会使当前线程等待调用join()方法的线程结束后才能继续执行 Thread类提供的一个非静态方法 不释放锁
ep:main方法中 thread1.join() 主线程等待thread1先执行完毕
4、wait:让出CPU调度,并且释放掉锁,wait()方法与sleep()方法的不同之处在于,wait()方法会释放对象的“锁标志”。当调用某一对象的wait()方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了notify()方法后,将从对象等待池中移出任意一个线程并放入锁标志等待池中,只有锁标志等待池中的线程可以获取锁标志,它们随时准备争夺锁的拥有权。当调用了某个对象的notifyAll()方法,会将对象等待池中的所有线程都移动到该对象的锁标志等待池。 释放锁
ep:(wait用于锁机制,sleep不是,sleep是线程的方法,跟锁没半毛钱关系。wait释放锁的原因,wait,notify,notifyall 都是Object对象的方法,是一起使用的,用于锁机制)
简单概况:Object对象方法随意组合,sleep睡了无其他方法唤醒,不具可操控性。
总结:1.CPU资源宝贵,上述方法会让出使用权。2.除了wait、notify等一组的是Object方法,其余都是Thread方法。3.sleep()方法在任何地方调用,wait()方法在同步方法、同步代码块中调用。 4.除了wait()其他不释放锁[sleep、yield、join]。 5.sleep调用停止后扔仍持有锁,到时间后接着执行。wait()调用放弃锁,等待其他线程notify()唤醒,去竞争同步资源锁。