3月14日工作内容 前端判断出错导致编辑时出现误导提醒
昨天晚上客户提到:
在公司管理员工列表页面,点击编辑,明明是赵X的账户,想调整一下接数据的数量,然后点确认跳出来的却是该员工是曹X
检查没有出现请求错误,并且确定返回参数的职级关系无误之后,把问题反馈给前端,就没我事了。
今天大多时间没什么任务,我一边复习多线程。
Java复习 线程 线程挺抽象的,一直很混淆,在这篇文章中弄清楚了一些:
1.9 多线程编程 (yuque.com)
同步锁 看过很多锁的例子,说句实在的,很难把握得住,很多时候看起来很像的代码运行起来结果都不一样,所以我打算记住一两种就够了。
通过synchronized(lock)
的方式来加锁,其实是在锁对象(可以是任意对象最简单的如private byte[] lock = new byte[0];
)加入一个标识符,当执行同步括住的代码的时候,标识符不让其他线程使用该锁对象,只有代码块执行结束了才会释放该对象。
实际在操作的时候,要保证的一点就是各线程中锁对象是同一个。
而将锁放在实现Runnable
接口的类中,通过传入该Runnable
类实例化不同的线程,这样子创建出来的线程就是具有锁的功能的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class SynchronizedDemo implements Runnable { private int count = 100 ; private byte [] lock = new byte [0 ]; @Override public void run () { while (true ){ synchronized (lock) { if (count > 0 ) { System.out.println(Thread.currentThread().getName() + "卖出了一张票,余票:" + --count); } } } } public static void main (String[] args) { SynchronizedDemo synchronizedDemo = new SynchronizedDemo (); Thread thread1 = new Thread (synchronizedDemo, "窗口一" ); Thread thread2 = new Thread (synchronizedDemo, "窗口二" ); Thread thread3 = new Thread (synchronizedDemo, "窗口三" ); thread1.start(); thread2.start(); thread3.start(); } }
运行结果是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ... 窗口二卖出了一张票,余票:20 窗口三卖出了一张票,余票:19 窗口二卖出了一张票,余票:18 窗口一卖出了一张票,余票:17 窗口三卖出了一张票,余票:16 窗口三卖出了一张票,余票:15 窗口一卖出了一张票,余票:14 窗口二卖出了一张票,余票:13 窗口二卖出了一张票,余票:12 窗口一卖出了一张票,余票:11 窗口三卖出了一张票,余票:10 窗口三卖出了一张票,余票:9 窗口一卖出了一张票,余票:8 窗口二卖出了一张票,余票:7 窗口二卖出了一张票,余票:6 窗口一卖出了一张票,余票:5 窗口三卖出了一张票,余票:4 窗口二卖出了一张票,余票:3 窗口三卖出了一张票,余票:2 窗口一卖出了一张票,余票:1 窗口三卖出了一张票,余票:0
微小的改动下,这个代码就得出不一样的结果:
比如这个,就没办法分配给其他线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class SynchronizedDemo implements Runnable { private int count = 100 ; private byte [] lock = new byte [0 ]; @Override public void run () { synchronized (lock) { while (count>0 ){ System.out.println(Thread.currentThread().getName() + "卖出了一张票,余票:" + --count); } } } public static void main (String[] args) { SynchronizedDemo synchronizedDemo = new SynchronizedDemo (); Thread thread1 = new Thread (synchronizedDemo, "窗口一" ); Thread thread2 = new Thread (synchronizedDemo, "窗口二" ); Thread thread3 = new Thread (synchronizedDemo, "窗口三" ); thread1.start(); thread2.start(); thread3.start(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ... 窗口一卖出了一张票,余票:20 窗口一卖出了一张票,余票:19 窗口一卖出了一张票,余票:18 窗口一卖出了一张票,余票:17 窗口一卖出了一张票,余票:16 窗口一卖出了一张票,余票:15 窗口一卖出了一张票,余票:14 窗口一卖出了一张票,余票:13 窗口一卖出了一张票,余票:12 窗口一卖出了一张票,余票:11 窗口一卖出了一张票,余票:10 窗口一卖出了一张票,余票:9 窗口一卖出了一张票,余票:8 窗口一卖出了一张票,余票:7 窗口一卖出了一张票,余票:6 窗口一卖出了一张票,余票:5 窗口一卖出了一张票,余票:4 窗口一卖出了一张票,余票:3 窗口一卖出了一张票,余票:2 窗口一卖出了一张票,余票:1 窗口一卖出了一张票,余票:0
再比如这个,偶尔会出现买多了的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class SynchronizedDemo implements Runnable { private int count = 100 ; private Object lock = new Object (); @Override public void run () { while (count>0 ) { synchronized (lock) { System.out.println(Thread.currentThread().getName() + "卖出了一张票,余票:" + count--); } } } public static void main (String[] args) { SynchronizedDemo synchronizedDemo = new SynchronizedDemo (); Thread thread1 = new Thread (synchronizedDemo, "窗口一" ); Thread thread2 = new Thread (synchronizedDemo, "窗口二" ); Thread thread3 = new Thread (synchronizedDemo, "窗口三" ); thread1.start(); thread2.start(); thread3.start(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 ... 窗口一卖出了一张票,余票:20 窗口三卖出了一张票,余票:19 窗口一卖出了一张票,余票:18 窗口二卖出了一张票,余票:17 窗口一卖出了一张票,余票:16 窗口三卖出了一张票,余票:15 窗口二卖出了一张票,余票:14 窗口三卖出了一张票,余票:13 窗口二卖出了一张票,余票:12 窗口一卖出了一张票,余票:11 窗口三卖出了一张票,余票:10 窗口二卖出了一张票,余票:9 窗口一卖出了一张票,余票:8 窗口二卖出了一张票,余票:7 窗口一卖出了一张票,余票:6 窗口三卖出了一张票,余票:5 窗口一卖出了一张票,余票:4 窗口二卖出了一张票,余票:3 窗口三卖出了一张票,余票:2 窗口二卖出了一张票,余票:1 窗口一卖出了一张票,余票:0 窗口三卖出了一张票,余票:-1
对于这两种异常情况的解释,GPT是这样说的:
在同步块中使用 while
循环和将 while
循环条件放在同步块之外然后在 while
循环中使用 synchronized
,这两种方式的区别在于锁的释放时机和锁的粒度。
**将 while
循环条件放在同步块之外然后在 while
循环中使用 synchronized
**:
1 2 3 4 5 javaCopy codesynchronized (lock) { while (count > 0) { // 操作 count } }
这种方式下,线程进入同步块后首先获取锁,然后检查 count
的值,如果条件满足则执行相应的操作,否则退出同步块。在这种情况下,锁的粒度比较粗,因为锁的持有范围是在整个 while
循环内。这可能会导致其他线程在等待锁时被阻塞,即使它们只是想读取 count
的值。
将 while
循环放在同步块内 :
1 2 3 4 5 6 7 8 9 javaCopy codewhile (true) { synchronized (lock) { if (count > 0) { // 操作 count } else { break; } } }
这种方式下,线程进入 while
循环后首先获取锁,然后检查 count
的值,如果条件满足则执行相应的操作,否则退出 while
循环。在这种情况下,锁的持有范围是在每次迭代中,因此锁的粒度更细。这意味着即使某个线程持有了锁,其他线程仍然可以读取 count
的值,而不需要等待整个 while
循环结束。
在第二种方式中,每次迭代时,线程都会释放锁,这样其他线程就有机会获得锁并进行操作。这有助于减少竞争和提高并发性能,并且能够避免出现负数的情况,因为每个线程在获取锁之后都会检查 count
的值,如果小于等于 0,则会退出循环。
记法就是细粒化到同步锁住里面的每一次操作都做一次判断 。
线程通信
使用 wait() 使方法进入等待状态
使用 notify() 唤醒等待的线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 package singleTest.threadTest;public class Egg { private boolean hasEgg = false ; private byte [] lock = new byte [0 ]; Thread pickEgg = new Thread (()->{ synchronized (lock) { while (true ) { if (hasEgg) { System.out.println("捡到鸡蛋了" ); hasEgg = false ; } else { System.out.println("等另一线程丢鸡蛋" ); try { lock.wait(); } catch (InterruptedException e) { throw new RuntimeException (e); } } } } }, "捡鸡蛋线程" ); Thread throwEgg = new Thread (()->{ while (true ) { try { Thread.sleep(1000 ); } catch (InterruptedException e) { throw new RuntimeException (e); } synchronized (lock) { System.out.println("生鸡蛋了" ); hasEgg = true ; lock.notify(); } } }, "生鸡蛋线程" ); public static void main (String[] args) { Egg egg = new Egg (); egg.pickEgg.start(); egg.throwEgg.start(); } }
wait()
和sleep()
的区别
刚好刷到了两道跟线程通信相关的常见面试题:
交叉打印的实例 线程通信可以用于交叉打印:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package singleTest.threadTest;import cn.hutool.crypto.GlobalBouncyCastleProvider;public class CrossPrint implements Runnable { private byte [] lock = new byte [0 ]; private int num = 0 ; private final int CAPACITY = 100 ; @Override public void run () { while (true ) { synchronized (lock) { lock.notify(); if (num < CAPACITY) { System.out.println("Print[" + ++num + "]: Thread-" + Thread.currentThread().getName()); try { lock.wait(); } catch (InterruptedException e) { throw new RuntimeException (e); } } else break ; } } } public static void main (String[] args) { CrossPrint crossPrint = new CrossPrint (); Thread thread1 = new Thread (crossPrint, "线程一" ); Thread thread2 = new Thread (crossPrint, "线程二" ); thread1.start(); thread2.start(); } }
阻塞队列 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 package singleTest.threadTest;import java.util.ArrayList;import java.util.List;import java.util.concurrent.ThreadLocalRandom;public class BlockQueue { private final int CAPACITY = 20 ; private List<Integer> queue = new ArrayList <>(CAPACITY); private byte [] lock = new byte [0 ]; public void produce (int num) { synchronized (lock) { lock.notify(); if (queue.size() < 20 ) { System.out.println("队列长度:[" +queue.size()+"],新增元素:[" +num+"],当前线程:[" +Thread.currentThread().getName()+"]" ); queue.add(queue.size(),num); } else { System.out.println("队列已满,[" +Thread.currentThread().getName()+"]线程wait" ); try { lock.wait(); } catch (InterruptedException e) { throw new RuntimeException (e); } } } } public void consume () { synchronized (lock) { lock.notify(); if (queue.size()>0 ) { System.out.println("队列长度:[" +queue.size()+"],消费元素:[" +queue.remove(0 )+"],当前线程:[" +Thread.currentThread().getName()+"]" ); } else { System.out.println("队列为空,[" +Thread.currentThread().getName()+"]线程wait" ); try { lock.wait(); } catch (InterruptedException e) { throw new RuntimeException (e); } } } } public static void main (String[] args) { BlockQueue blockQueue = new BlockQueue (); new Thread (()->{ while (true ){ blockQueue.produce(ThreadLocalRandom.current().nextInt()); try { Thread.sleep(100 ); } catch (InterruptedException e) { throw new RuntimeException (e); } } },"生产者线程" ).start(); new Thread (()->{ while (true ){ blockQueue.consume(); try { Thread.sleep(1000 ); } catch (InterruptedException e) { throw new RuntimeException (e); } } },"消费者线程" ).start(); new Thread (()->{ while (true ){ System.out.println("当前队列长度:[" + blockQueue.queue.size() + "],当前线程:[" + Thread.currentThread().getName() + "]" ); try { Thread.sleep(1000 ); } catch (InterruptedException e) { throw new RuntimeException (e); } } },"监控线程" ).start(); } }
异步锁 文章中没有提到异步锁,我找了其他文章看一看了:
同步(Synchronous)和异步(Asynchronous) - 小cai一碟 这篇文章中解释了同步与异步 的区别:
同步和异步强调的是消息通信机制 (synchronous communication/ asynchronous communication)。所谓同步,就是在发出一个”调用”时,在没有得到结果之前,该“调用”就不返回。但是一旦调用返回,就得到返回值了 。换句话说,就是由“调用者”主动等待这个“调用”的结果。而异步则是相反,”调用”在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在”调用”发出后,”被调用者”通过状态、通知来通知调用者,或通过回调函数处理这个调用
Java中的Atomic类提供了一组原子操作来实现异步锁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package singleTest.threadTest;import java.util.concurrent.atomic.AtomicInteger;public class AtomicDemo { private AtomicInteger count = new AtomicInteger (0 ); public int increment () { return count.incrementAndGet(); } public int decrement () { return count.decrementAndGet(); } public int getValue () { return count.intValue(); } public static void main (String[] args) { AtomicDemo atomicDemo = new AtomicDemo (); Thread thread1 = new Thread (() -> { for (int i = 0 ; i < 1000 ; i++) { atomicDemo.increment(); } System.out.println(Thread.currentThread().getName() + ":结果:【" + atomicDemo.getValue() + "】" ); }, "加法线程" ); Thread thread2 = new Thread (() -> { for (int i = 0 ; i < 1000 ; i++) { atomicDemo.decrement(); } System.out.println(Thread.currentThread().getName() + ":结果:【" + atomicDemo.getValue() + "】" ); }, "减法线程" ); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { throw new RuntimeException (e); } System.out.println(Thread.currentThread().getName() + ":结果:【" + atomicDemo.getValue() + "】" ); } }
结果是:
1 2 3 减法线程:结果:【0】 加法线程:结果:【0】 main:结果:【0】