“明日复明日,明日何其多。

我生待明日,万事成蹉跎。

世人若被明日累,春去秋来老将至。

朝看水东流,暮看日西坠。

百年明日能几何?请君听我明日歌。

明日复明日,明日何其多!

日日待明日,万世成蹉跎。

世人皆被明日累,明日无穷老将至。

晨昏滚滚水东流,今古悠悠日西坠。

百年明日能几何?请君听我明日歌。”

这首《明日歌》是明朝的钱福所写。大意是,

明天又一个明天,明天何等的多。

我的一生都在等待明日,什么事情都没有进展。

世人和我一样辛苦地被明天所累,一年年过去马上就会老。

早晨看河水向东流逝,傍晚看太阳向西坠落才是真生活。

百年来的明日能有多少呢?请诸位听听我的《明日歌》。

这首诗七次提到“明日”,诗人在作品中告诫和劝勉人们要牢牢地抓住稍纵即逝的今天,要珍惜时间,今日的事情今日做,不要拖到明天,不要蹉跎岁月。不要把任何计划和希望寄托在未知的明天。诗歌的意思浅显,语言明白如话,说理通俗易懂。给人的启示是:世界上的许多东西都能尽力争取和失而复得,只有时间难以挽留。人的生命只有一次,时间永不回头。不要今天的事拖明天,明天拖后天,要“今天的事,今日毕”。告诫我们不要学寒号鸟,要珍惜时间,不要把事情都放到明天,今天的事情今天搞定。

继续总结多线程同步常用的方法或者类,之前介绍了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对于使用过的人来说应该比较好理解,也更容易学习它的高级用法,运用起来会显得很轻松;对于没有使用过的,可能只是停留在理解概念的层面,实际在使用时还是不太好下手,不知在何时何地来使用。所以解决不熟悉的唯一办法就是要勇敢大胆的去使用它,不要怕出错,多实践和多练习,这样才能很好的掌握它。



本公众号将以推送Android各种技术干货或碎片化知识,以及整理老司机日常工作中踩过的坑涉及到的经验知识为主,也会不定期将正在学习使用的新技术总结出来进行分享。每天一点干货小知识把你的碎片时间充分利用起来。

知识点干货—多线程同步【6】之synchronized的更多相关文章

  1. 牛客网Java刷题知识点之多线程同步的实现方法有哪些

    不多说,直接上干货! 为何要使用同步?      java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),  将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避 ...

  2. Java多线程同步机制(synchronized)

    参看:http://enetor.iteye.com/blog/986623

  3. JAVA之旅(十三)——线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this

    JAVA之旅(十三)--线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this 我们继续上个篇幅接着讲线程的知识点 一.线程的安全性 当我们开启四个窗口(线程 ...

  4. Java多线程-同步:synchronized 和线程通信:生产者消费者模式

    大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同 ...

  5. Java多线程同步 synchronized 关键字的使用

    代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A, ...

  6. Java多线程:线程同步与关键字synchronized

    一.同步的特性1. 不必同步类中所有的方法, 类可以同时拥有同步和非同步方法.2. 如果线程拥有同步和非同步方法, 则非同步方法可以被多个线程自由访问而不受锁的限制. 参见实验1:http://blo ...

  7. synchronized、锁、多线程同步的原理是咋样

    先综述个结论: 一般说的synchronized用来做多线程同步功能,其实synchronized只是提供多线程互斥,而对象的wait()和notify()方法才提供线程的同步功能. 一般说synch ...

  8. 关于synchronized和ReentrantLock之多线程同步详解

    一.线程同步问题的产生及解决方案 问题的产生: Java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突. 如下例:假设有一个卖票 ...

  9. Java自学-多线程 同步synchronized

    Java 多线程同步 synchronized 多线程的同步问题指的是多个线程同时修改一个数据的时候,可能导致的问题 多线程的问题,又叫Concurrency 问题 步骤 1 : 演示同步问题 假设盖 ...

随机推荐

  1. (转)Docker之Compose服务编排

    转自:https://www.cnblogs.com/52fhy/p/5991344.html Compose是Docker的服务编排工具,主要用来构建基于Docker的复杂应用,Compose 通过 ...

  2. Codeforces 842A Kirill And The Game【暴力,水】

    A. Kirill And The Game time limit per test:2 seconds memory limit per test:256 megabytes input:stand ...

  3. BZOJ2425: [HAOI2010]计数

    题目:http://www.lydsy.com/JudgeOnline/problem.php?id=2425 其实能够构成的数就是原数的排列(算前导0),然后组合计数一下就可以了. #include ...

  4. 在R12下加载Java Bean,配置FORMS_WEB_CONFIG_FILE文件/通过AutoConfig实现Form Server配置文件的修改

    1.定位模版文件$AD_TOP/bin/adtmplreport.sh contextfile=$CONTEXT_FILE target=$FORMS_WEB_CONFIG_FILE以上命令,通过查看 ...

  5. c+(内存)

    内存是程序运行的基础.所有正在运行的代码都保存在内存里面.内存需要处理各种各样的数据,包括键盘的数据.鼠标的数据.usb的数据.串口的数据.摄像头的数据,那么这些数据经过程序的处理之后,就要进行输出到 ...

  6. java构建学生管理系统(一)

    用java搭建学生管理系统,重要还是对数据库的操作,诸如增删改查等. 1.基本的功能: 老师完成对学生信息的查看和修改,完成对班级的信息的概览. 学生可以看自己的成绩和对自己信息的修改. 学生和老师有 ...

  7. 使用Eclipse在Excel中找出两张表中相同证件号而姓名或工号却出现不同的的项

    1:首先把Excel中的文本复制到txt中,复制如下: A表: 证件号                           工号  姓名 310110xxxx220130004 101 傅家宜3101 ...

  8. 用NPOI导出Excel,生成下拉列表、以及下拉联动列表(第1篇/共3篇)

    最近帅帅的小毛驴遇到一个很奇葩的需求: 导出Excel报表,而且还要带下拉框,更奇葩的是,下拉框还是联动的. 小毛驴一天比较忙,所以这等小事自然由我来为她分忧了.经历了两天,做了几种解决方案,最后完美 ...

  9. bat脚本设置系统环境变量即时生效

    关于bat的资料多但零碎,记录一下. 1.设置环境变量即时生效:通过重启explorer来实现即时生效(亲测有效) @echo off set curPath=%cd% wmic ENVIRONMEN ...

  10. QQ邮箱开启SMTP服务的步骤

    首先要确保你的QQ邮箱已经要开启超过一个月.对于新开启的邮箱,腾讯是不开放这些功能的. 方法/步骤 首先点QQ头像旁边的信封符号进入邮箱. 当然你也可以使用 mail.qq.com进邮箱 进入邮箱后点 ...