明日复明日, 明日何其多
我生待明日, 万事成蹉跎
世人若被明日累, 春去秋来老将至
朝看水东流, 暮看日西坠
百年明日能几何? 请君听我明日歌
明日复明日, 明日何其多!
日日待明日, 万世成蹉跎
世人皆被明日累, 明日无穷老将至
晨昏滚滚水东流, 今古悠悠日西坠
百年明日能几何? 请君听我明日歌
这首明日歌是明朝的钱福所写大意是,
明天又一个明天, 明天何等的多
我的一生都在等待明日, 什么事情都没有进展
世人和我一样辛苦地被明天所累, 一年年过去马上就会老
早晨看河水向东流逝, 傍晚看太阳向西坠落才是真生活
百年来的明日能有多少呢? 请诸位听听我的明日歌
这首诗七次提到明日, 诗人在作品中告诫和劝勉人们要牢牢地抓住稍纵即逝的今天, 要珍惜时间, 今日的事情今日做, 不要拖到明天, 不要蹉跎岁月不要把任何计划和希望寄托在未知的明天诗歌的意思浅显, 语言明白如话, 说理通俗易懂给人的启示是: 世界上的许多东西都能尽力争取和失而复得, 只有时间难以挽留人的生命只有一次, 时间永不回头不要今天的事拖明天, 明天拖后天, 要今天的事, 今日毕告诫我们不要学寒号鸟, 要珍惜时间, 不要把事情都放到明天, 今天的事情今天搞定
继续总结多线程同步常用的方法或者类, 之前介绍了 CountDownLatch,CyclicBarriar 和 Exchanger,Phaser 以及 Semaphore, 这次介绍一个大家比较熟悉的关键字 --synchronized 大多数人应该或多或少的使用过它, 那我们对它是所有用法都彻底了解吗? 这个就不见的了, 今天我们就全方位的介绍一下它
1 定义
先看一下百度百科给出的定义: synchronized--Java 语言的关键字, 可用来给对象和方法或者代码块加锁, 当它锁定一个方法或者一个代码块的时候, 同一时刻最多只有一个线程执行这段代码当两个并发线程访问同一个对象 object 中的这个加锁同步代码块时, 一个时间内只能有一个线程得到执行另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块然而, 当一个线程访问 object 的一个加锁代码块时, 另一个线程仍可以访问该 object 中的非加锁代码块
什么意思呢? 就是说 synchronized 是 Java 中用来对对象和方法或者代码块进行加锁的一种方法, 借助它可以在多线程并发时, 保证同一时刻只有一个线程执行某个同步方法或代价块, 这样能充分保证线程按顺序执行, 保证它们同步进行, 按照我们的逻辑使多线程按我们的心意来依次执行, 这也是一种解决多线程同步的方法
2 对象锁和类锁的概念
在使用 synchronized 前, 我们要先理解两个概念, 对象锁和类锁
对象锁
对象锁是指 Java 为临界区 (指程序中的一个代码段)synchronized(Object) 语句指定的对象进行加锁它用于程序片段或者 method 上, 此时将获得对象的锁, 所有想要进入该对象的 synchronized 的方法或者代码段的线程都必须获取对象的锁, 如果没有, 则必须等其他线程释放该锁
当一个对象中有 synchronized method 或 synchronized block 的时候调用此对象的同步方法或进入其同步区域时, 就必须先获得对象锁如果此对象的对象锁已被其他调用者占用, 则需要等待此锁被释放
类锁
实际上是没有这个概念的, 但是为了区分对象锁的不同使用场景, 我们增加了一个类锁这样的概念对象锁指的是对象的某个方法或代码块进行加锁, 那类锁指的是针对类方法或者类变量进行加锁由于一个 class 不论被实例化多少次, 其中的静态方法和静态变量在内存中都只有一份所以, 一旦一个静态方法被申明为 synchronized, 此类所有的实例化对象在调用此方法, 共用同一把锁, 所以我们称之为类锁
在程序中可以尝试用以下方式获取类锁
- synchronized (xxx.class) {...}
- synchronized (Class.forName("xxx")) {...}
同时获取类锁和对象锁是可以的, 并不会产生问题但使用类锁时要格外注意, 因为一旦产生类锁的嵌套获取的话, 就会产生死锁, 因为每个 class 在内存中都只能生成一个 Class 实例对象
3 使用方法
了解了对象锁和类锁后, 我们知道了 synchronized 可以用于多个场景, 既可以修改代码块和方法, 还可以用来修饰类方法和类虽然锁针的对象不同, 但它们的含义是一样的
synchronized 具体有如下四种使用场景:
(1)修饰一个代码块, 被修饰的代码块称为同步语句块, 其作用的范围是大括号 {} 括起来的代码, 作用的对象是调用这个代码块的对象多个线程在同时使用这个对象的此代码块时会遇到对象锁, 需要进行同步等待;
代码示例:
- class DemoThread implements Runnable {
- private static int count;
- public DemoThread() {
- count = 0;
- }
- public void run() {
- synchronized(this) {
- for (int i = 0; i < 5; i++) {
- try {
- System.out.println(Thread.currentThread().getName() + ":" + (count++));
- Thread.sleep(100);
- } catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public int getCount() {
- return count;
- }
- }
- DemoThread demoThread = new DemoThread();
- Thread thread1 = new Thread(demoThread, "DemoThread1");
- Thread thread2 = new Thread(demoThread, "DemoThread2");
- thread1.start();
- thread2.start();
结果如下:
- DemoThread1:0
- DemoThread1:1
- DemoThread1:2
- DemoThread1:3
- DemoThread1:4
- DemoThread2:5
- DemoThread2:6
- DemoThread2:7
- DemoThread2:8
- DemoThread2:9
分析: 当两个并发线程 (thread1 和 thread2) 同事访问同一个对象 (demoThread) 中的 synchronized 代码块时, 在同一时刻只能有一个线程执行, 另一个线程阻塞在 synchronized 位置, 必须等待正在访问的线程执行完这个代码块以后才能执行该代码块所以才会看到这样的结果, 开始只有 DemoThread1 的 Log,DemoThread1 执行完成后才能看到 DemoThread 的 Log
这里需要注意一下, 当一个线程访问对象的一个 synchronized(this)同步代码块时, 另一个线程仍然可以访问该对象中的非 synchronized(this)同步代码块也就是说如果 thread1 正在访问 synchronized 修饰的代码块, thread2 虽然此时无法访问这个代码块, 但它可以访问其他的代码块
(2)修饰一个方法, 被修饰的方法称为同步方法, 其作用的范围是整个方法, 作用的对象是调用这个方法的对象在多线程执行时和同步代码块相同, 针对的是某个对象;
代码示例:
- class DemoThread implements Runnable {
- private static int count;
- public DemoThread() {
- count = 0;
- }
- public synchronized void run() {
- for (int i = 0; i < 5; i++) {
- try {
- System.out.println(Thread.currentThread().getName() + ":" + (count++));
- Thread.sleep(100);
- } catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- public int getCount() {
- return count;
- }
- }
- DemoThread demoThread = new DemoThread();
- Thread thread1 = new Thread(demoThread, "DemoThread1");
- Thread thread2 = new Thread(demoThread, "DemoThread2");
- thread1.start();
- thread2.start();
结果如下:
- DemoThread1:0
- DemoThread1:1
- DemoThread1:2
- DemoThread1:3
- DemoThread1:4
- DemoThread2:5
- DemoThread2:6
- DemoThread2:7
- DemoThread2:8
- DemoThread2:9
可以看到他们的执行结果是相同的既然结果相同, 那修饰代码块和修改方法名有什么区别呢?
这个问题也是 synchronized 的缺陷
synchronized 的缺陷: 当某个线程进入同步方法获得对象锁, 那么其他线程访问这里对象的同步方法时, 必须等待或者阻塞, 这对高并发的系统是致命的, 这很容易导致系统的崩溃如果某个线程在同步方法里面发生了死循环, 那么它就永远不会释放这个对象锁, 那么其他线程就要永远的等待, 这问题一旦出现就是是一个致命的问题既然无法完全避免这种缺陷, 那么就应该将风险降到最低同步代码块就是为了降低风险而存在的因为如果某一线程调用 synchronized 修饰的代码方法, 那么当某个线程进入了这个方法之后, 这个对象其他同步方法都不能被其他线程访问了假如这个方法需要执行的时间很长, 那么其他线程会一直阻塞, 影响到系统的性能而如果这时用 synchronized 来修饰代码块, 情况就不同了, 这个方法加锁的对象是某个对象, 跟执行这行代码的对象或者承载这个方法的对象没有关系, 那么当一个线程执行这个方法时, 其他同步方法仍旧可以访问, 因为他们持有的锁不一样
(3)修饰一个静态的方法, 其作用的范围是整个静态方法, 作用的对象是这个类的所有对象多线程在使用此方法时, 会涉及到类锁
代码示例:
- class DemoThread implements Runnable {
- private static int count;
- public DemoThread() {
- count = 0;
- }
- public synchronized static void method() {
- for (int i = 0; i < 5; i++) {
- try {
- System.out.println(Thread.currentThread().getName() + ":" + (count++));
- Thread.sleep(100);
- } catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- public synchronized void run() {
- method();
- }
- }
- DemoThread demoThread = new DemoThread();
- Thread thread1 = new Thread(demoThread, "DemoThread1");
- Thread thread2 = new Thread(demoThread, "DemoThread2");
- thread1.start();
- thread2.start();
结果如下:
- DemoThread1:0
- DemoThread1:1
- DemoThread1:2
- DemoThread1:3
- DemoThread1:4
- DemoThread2:5
- DemoThread2:6
- DemoThread2:7
- DemoThread2:8
- DemoThread2:9
可以看到结果和上两例相同 DemoThread1 和 DemoThread2 是 DemoThread 的两个对象, 但在 thread1 和 thread2 并发执行时却保持了线程同步这是因为 run 中调用了静态方法 method, 而静态方法是属于类的, 所以 syncThread1 和 syncThread2 相当于用了同一把锁
(4)修饰一个类, 其作用的范围是 synchronized 后面括号括起来的部分, 作用的对象是这个类的所有对象多线程使用此类时涉及到类锁
代码示例:
- class DemoThread implements Runnable {
- private static int count;
- public DemoThread() {
- count = 0;
- }
- public static void method() {
- synchronized(DemoThread.class) {
- for (int i = 0; i < 5; i++) {
- try {
- System.out.println(Thread.currentThread().getName() + ":" + (count++));
- Thread.sleep(100);
- } catch(InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public synchronized void run() {
- method();
- }
- }
- DemoThread demoThread = new DemoThread();
- Thread thread1 = new Thread(demoThread, "DemoThread1");
- Thread thread2 = new Thread(demoThread, "DemoThread2");
- thread1.start();
- thread2.start();
结果如下:
- DemoThread1:0
- DemoThread1:1
- DemoThread1:2
- DemoThread1:3
- DemoThread1:4
- DemoThread2:5
- DemoThread2:6
- DemoThread2:7
- DemoThread2:8
- DemoThread2:9
可以看到结果也是相同的 synchronized 作用于一个类时, 是给这个类加锁, 类的所有对象用的是同一把锁
4 总结
synchronized 对于使用过的人来说应该比较好理解, 也更容易学习它的高级用法, 运用起来会显得很轻松; 对于没有使用过的, 可能只是停留在理解概念的层面, 实际在使用时还是不太好下手, 不知在何时何地来使用所以解决不熟悉的唯一办法就是要勇敢大胆的去使用它, 不要怕出错, 多实践和多练习, 这样才能很好的掌握它
来源: https://www.cnblogs.com/laoxiao79/p/8421994.html