补充一个synchronized关键字的结论:

  1. 线程A先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法
  2. A线程现持有object对象的Lock锁,B线程如果这个时候调用object对象中的synchronized类型的方法则需要等待,也就是同步
  3. 当线程A调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕才可以调用X方法,而B线程如果调用声明了synchronize关键字的非X方法时,必须等A线程将X方法执行完,也就是释放对象锁之后才可以调用。这时A已经执行了一个完整的任务,也就是变量已经完成了变化,不存在脏读的基本环境。(X方法和非X方法均处在同一个类下面,是同一个对象的不同方法)
  4. 关键字synchronized取得的锁都是对象锁,而不是把一段代码或方法(函数)当作锁,所以在前面例子中,哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁Lock,那么其他线程只能呈等待状态,前提是多个线程访问同一个对象
  5. 如果多个线程访问多个对象,则JVM会创建多个锁。会以异步的方式运行。

同步的单词为synchronized 异步的单词为asynchronized

当一个线程出现异常时,锁会自动释放。

同步不具有继承性,即:父类方法中加了synchronized关键字,子类调用父类的方法时,这个被继承来的方法不具备同步的特性。
public class Main {
synchronized public void serviceMethod() {
try {
System.out.println("int main 下一步 sleep begin threadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("int main 下一步 sleep end threadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//-----------------------------------------------------------------------------
public class Sub extends Main {
@Override
public void serviceMethod() {
try {
System.out.println("int sub 下一步 sleep begin threadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
Thread.sleep(5000);
System.out.println("int sub 下一步 sleep end threadName="
+ Thread.currentThread().getName() + " time="
+ System.currentTimeMillis());
super.serviceMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//-----------------------------------------------------------------------------
public class MyThreadA extends Thread {
private Sub sub; public MyThreadA(Sub sub) {
super();
this.sub = sub;
} @Override
public void run() {
sub.serviceMethod();
}public class MyThreadB extends Thread {
private Sub sub; public MyThreadB(Sub sub) {
super();
this.sub = sub;
} @Override
public void run() {
sub.serviceMethod();
}
}//-----------------------------------------------------------------------------
public class Test {
public static void main(String[] args) {
Sub subRef = new Sub();
MyThreadA a = new MyThreadA(subRef);
a.setName("A");
a.start();
MyThreadB b = new MyThreadB(subRef);
b.setName("B");
b.start();
}
}

synchronized同步语句块

用关键字synchronized声明方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待较长的时间,在这种情况下,可以使用synchronized同步语句块来解决。

public class Task {
private String getData1;
private String getData2; public synchronized void doLongTimeTask(){
try {
System.out.println("begin task");
Thread.sleep(3000);
getData1="长时间处理任务后从远程返回的值1 threadName="
+Thread.currentThread().getName();
getData2="长时间处理任务后从远程返回的值2 threadName="
+Thread.currentThread().getName();
System.out.println(getData1);
System.out.println(getData2);
System.out.println("end task");
}catch (InterruptedException e){
e.printStackTrace();
}
}
} public class CommonUtils {
public static long beginTime1;
public static long endTime1;
public static long beginTime2;
public static long endTime2;
} public class ThreadA extends Thread{ private Task task; public ThreadA(Task task){
super();
this.task=task;
} @Override
public void run() {
super.run();
CommonUtils.beginTime1=System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime1=System.currentTimeMillis();
}
} public class ThreadB extends Thread{ private Task task; public ThreadB(Task task){
super();
this.task=task;
} @Override
public void run() {
super.run();
CommonUtils.beginTime2=System.currentTimeMillis();
task.doLongTimeTask();
CommonUtils.endTime2=System.currentTimeMillis();
}
} public class Run { public static void main(String[] args){
Task task=new Task();
ThreadA thread1=new ThreadA(task);
thread1.start();
ThreadB thread2=new ThreadB(task);
thread1.start(); try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
} long beginTime=CommonUtils.beginTime1;
if(CommonUtils.beginTime2<CommonUtils.beginTime1){
beginTime=CommonUtils.beginTime2;
} long endTime=CommonUtils.endTime1;
if (CommonUtils.endTime2>CommonUtils.endTime1){
endTime=CommonUtils.endTime2;
} System.out.println("耗时:"+(endTime-beginTime)/1000);
}
}

程序运行大约6秒后结束。其中synchronized修饰的方法dolongtimetask同步执行耗时很长。

结论写在前面:不在synchronized块中就是异步执行,在synchronized块中就是同步执行

使用同步代码块解决同步方法的弊端:

 public class Task {
private String getData1;
private String getData2; public synchronized void doLongTimeTask(){
try {
System.out.println("begin task");
Thread.sleep(3000);
getData1="长时间处理任务后从远程返回的值1 threadName="
+Thread.currentThread().getName();
getData2="长时间处理任务后从远程返回的值2 threadName="
+Thread.currentThread().getName();
synchronized (this){
System.out.println(getData1);
System.out.println(getData2);
}
System.out.println("end task");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}

第13-16行代码被修改了,他们被放入了同步代码块中,显然不在同步代码块中的内容被异步执行,程序运行结束后,耗时为3秒。

在使用同步代码块的时候需要注意,当一个线程访问object的一个synchrnized同步代码块时,其他线程对同一个object中所有其他synchronized同步代码块的访问将被阻塞,这说明synchronized使用的对象监视器是同一个。

问题来了,什么是对象监视器?

在JVM的规范中,有这么一些话:  
“在JVM中,每个对象和类在逻辑上都是和一个监视器相关联的”  
“为了实现监视器的排他性监视能力,JVM为每一个对象和类都关联一个锁”  
“锁住了一个对象,就是获得对象相关联的监视器”  
   
从这些话,看出监视器和对象锁好像是一回事,那为何要定义两个东西,若不一样,他们的关系如何?监视器好比一做建筑,它有一个很特别的房间,房间里有一些数据,而且在同一时间只能被一个线程占据,进入这个建筑叫做"进入监视器",进入建筑中的那个特别的房间叫做"获得监视器",占据房间叫做"持有监视器",离开房间叫做"释放监视器",离开建筑叫做"退出监视器".  而一个锁就像一种任何时候只允许一个线程拥有的特权.   一个线程可以允许多次对同一对象上锁.对于每一个对象来说,java虚拟机维护一个计数器,记录对象被加了多少次锁,没被锁的对象的计数器是0,线程每加锁一次,计数器就加1,每释放一次,计数器就减1.当计数器跳到0的时候,锁就被完全释放了.       

java虚拟机中的一个线程在它到达监视区域开始处的时候请求一个锁.JAVA程序中每一个监视区域都和一个对象引用相关联.  

监视器:monitor  
  锁:lock(JVM里只有一种独占方式的lock)  
  进入监视器:entermonitor  
  离开/释放监视器:leavemonitor  
  (entermonitor和leavemonitor是JVM的指令)  
  拥有者:owner  
   
  在JVM里,monitor就是实现lock的方式。  
  entermonitor就是获得某个对象的lock(owner是当前线程)  
  leavemonitor就是释放某个对象的lock  
------------------------->这些内容都是JVM的内容,下一部博文准备写JVM

同步代码块锁非this对象

这里引出新的问题this关键字

 (1)this调用本类中的属性,也就是类中的成员变量;
(2)this调用本类中的其他方法;
(3)this调用本类中的其他构造方法,调用时要放在构造方法的首行。
this是一个引用,它指向自身的这个对象。
结论写在前面:一.在多个线程持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象X)同步代码块中的代码
二.当持有“对象监视器”为同一个对象的前提下,同一时间只有一个线程可以执行synchronized(非this对象X)同步代码块中的代码
public class Service {
private String usernameParam;
private String passwordParam;
private String anString = new String();
public void setUsernamePassword(String username,String password){
try{
synchronized (anString){
System.out.println("线程名称是:"+Thread.currentThread().getName()
+"在" + System.currentTimeMillis()+"进入同步块");
usernameParam = username;
Thread.sleep(3000);
passwordParam = password;
System.out.println("线程名称是:"+Thread.currentThread().getName()
+"在" + System.currentTimeMillis()+"离开同步块");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public class ThreadA extends Thread{
private Service service;
public ThreadA(Service service){
this.service = service;
}
@Override
public void run(){
service.setUsernamePassword("a","apsssss");
}
} public class ThreadB extends Thread{
private Service service;
public ThreadB(Service service){
this.service = service;
}
@Override
public void run(){
service.setUsernamePassword("BBB","ISSSSBBB");
}
} public class Run {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("A线程");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B线程");
b.start(); }
}

上面的代码运行结果为:

线程名称是:A线程在1574247406143进入同步块
线程名称是:A线程在1574247409171离开同步块
线程名称是:B线程在1574247409172进入同步块
线程名称是:B线程在1574247412174离开同步块
结论:锁非this对象具有一定的优点,如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但会受到阻塞,所以会影响运行效率;但如果使用同步代码块锁非this对象,则synchronized(非this对象)代码块中的程序与同步方法是异步的,不与其他锁this方法争抢this锁,则可大大提高运行效率。
public class Service {
private String usernameParam;
private String passwordParam; public void setUsernamePassword(String username,String password){
try{
String anString = new String();
synchronized (anString){
System.out.println("线程名称是:"+Thread.currentThread().getName()
+"在" + System.currentTimeMillis()+"进入同步块");
usernameParam = username;
Thread.sleep(3000);
passwordParam = password;
System.out.println("线程名称是:"+Thread.currentThread().getName()
+"在" + System.currentTimeMillis()+"离开同步块");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

使用synchronized(非this对象X)同步代码块格式进行同步操作时,对象监视器必须是同一个对象。如果不是同一个对象监视器,运行的结果就是异步调用,就会交叉运行。因为上面代码中声明 anString 的位置发生了变化,可以实现同步的是在对象属性声明的部位创建anString,导致异步结果的事下面这个在方法内声明变量的操作。

三个结论!

synchronized(非this对象X)格式的写法是将x对象本身作为对象监视器,这样就可以得到下面3个结论

  1. 当多线程同时执行synchronized(x){}同步代码块时,呈同步效果
  2. 当其他线程执行x对象中synchronized同步方法时,呈同步效果
  3. 当其他线程执行x对象方法里面的synchronized(this)代码块时,也呈同步效果

但需要注意的是,如果其他线程调用不加synchronized关键字的方法时,还是异步调用。

java多线程学习笔记(五)的更多相关文章

  1. java多线程学习笔记——详细

    一.线程类  1.新建状态(New):新创建了一个线程对象.        2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中, ...

  2. Java多线程学习笔记(一)——多线程实现和安全问题

    1. 线程.进程.多线程: 进程是正在执行的程序,线程是进程中的代码执行,多线程就是在一个进程中有多个线程同时执行不同的任务,就像QQ,既可以开视频,又可以同时打字聊天. 2.线程的特点: 1.运行任 ...

  3. JAVA多线程学习笔记(1)

    JAVA多线程学习笔记(1) 由于笔者使用markdown格式书写,后续copy到blog可能存在格式不美观的问题,本文的.mk文件已经上传到个人的github,会进行同步更新.github传送门 一 ...

  4. Java IO学习笔记五:BIO到NIO

    作者:Grey 原文地址: Java IO学习笔记五:BIO到NIO 准备环境 准备一个CentOS7的Linux实例: 实例的IP: 192.168.205.138 我们这次实验的目的就是直观感受一 ...

  5. Java多线程学习笔记

    进程:正在执行中的程序,其实是应用程序在内存中运行的那片空间.(只负责空间分配) 线程:进程中的一个执行单元,负责进程汇总的程序的运行,一个进程当中至少要有一个线程. 多线程:一个进程中时可以有多个线 ...

  6. Java多线程学习(五)线程间通信知识点补充

    系列文章传送门: Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Java多 ...

  7. Java多线程学习笔记--生产消费者模式

    实际开发中,我们经常会接触到生产消费者模型,如:Android的Looper相应handler处理UI操作,Socket通信的响应过程.数据缓冲区在文件读写应用等.强大的模型框架,鉴于本人水平有限目前 ...

  8. Java设计模式学习笔记(五) 单例模式

    前言 本篇是设计模式学习笔记的其中一篇文章,如对其他模式有兴趣,可从该地址查找设计模式学习笔记汇总地址 1. 使用单例模式的原因 以Windows任务管理器为例,在Windows系统中,任务管理器是唯 ...

  9. java 多线程学习笔记

    这篇文章主要是个人的学习笔记,是以例子来驱动的,加深自己对多线程的理解. 一:实现多线程的两种方法 1.继承Thread class MyThread1 extends Thread{ public ...

  10. Java 多线程学习笔记:生产者消费者问题

    前言:最近在学习Java多线程,看到ImportNew网上有网友翻译的一篇文章<阻塞队列实现生产者消费者模式>.在文中,使用的是Java的concurrent包中的阻塞队列来实现.在看完后 ...

随机推荐

  1. chromedriver安装报错

     解决方法:   可以使用 npm init -f命令生成package.json,package.json中缺少的字段可以参照模板 package.json进行填充,package.json中的字段 ...

  2. 自动化测试常用断言的使用方法(python)-(转载@zhuquan0814

    自动化测试中寻找元素并进行操作,如果在元素好找的情况下,相信大家都可以较熟练地编写用例脚本了,但光进行操作可能还不够,有时候也需要对预期结果进行判断. 这里介绍几个常用断言的使用方法,可以一定程度上帮 ...

  3. Linux(Ubuntu)常用命令(二)

    归档管理: 打包: tar -cvf xxx.tar 打包对象 (一般来说就是 -cvf 一起用)但这种不压缩的打包通常不用,接下来会说. -options:-c    生成档案文件,创建打包文件. ...

  4. linux下部署springboot vue项目

    使用的工具是 XFTP5 XSHELL5 docker pull gmaslowski/jdk 拉取jdk docker images 查询下载的镜像ID (如:390b58b1be42) docke ...

  5. Linux shell 常用命令大全 每日一更

    大一上学期学习了Linux的基本操作,已经很久没使用了,虚拟机也近半年没开(作为一个计算机类专业的少年真的不应该).为了补回这些知识和为将来的学习打下基础,现在每天更新一条shell命令及其子命令,欢 ...

  6. 整理eclipse,升级jdk环境小记录

    这2天在整理项目: 需要把eclipse 32位,jdk1.6 32位的更改为eclipse 64位,jdk1.8 64位版本的,于是我就在一台window7的电脑上直接操作,遇到了一下几点问题,记录 ...

  7. 多线性方程组迭代算法——Jacobi迭代算法的Python实现

    多线性方程(张量)组迭代算法的原理请看这里:若想看原理部分请留言,不方便公开分享 Gauss-Seidel迭代算法:多线性方程组迭代算法——Gauss-Seidel迭代算法的Python实现 impo ...

  8. Visual Studio Code如何编写运行C、C++

    Visual Studio Code如何编写运行C.C++ 作者:知乎用户链接:https://www.zhihu.com/question/30315894/answer/154979413来源:知 ...

  9. Springboot整合Hikari数据库连接池,密码加密

    1.application.yml配置 spring: datasource: jdbcUrl: jdbc:mysql://127.0.0.1:3306/jby?serverTimezone=UTC& ...

  10. 2019-8-31-Latex-公式速查

    title author date CreateTime categories Latex 公式速查 lindexi 2019-08-31 16:55:58 +0800 2018-05-25 16:5 ...