(二)对象以及变量的并发访问--synchronized的使用细节,用法
具体的记录synchronized关键的各种使用方式,注意事项。感觉一步一步跟我来都可以看懂滴
大致是按照以下思路进行书写的。黑体字可以理解为结论,
1.synchronized锁的是什么?
2.synchronized能够锁住所有方法吗?
3.synchronized能够用来锁住一个方法之中的部分代码吗?
4.synchronized能够锁住除了this以外的其他对象吗?有什么用?有什么需要注意的?
------------------------------------------------------------------------------------------正文------------------------------------------------------------------------------------------
1.synchronized锁的是什么?
首先,要明白非线程安全存在于实例变量之中,即大家都可以更改的变量,私有变量不存在线程安全问题。那么解决非线程安全问题,我们需要用用到 synchronized 来给某一个方法或者对象上锁,避免交叉访问的现象出现。那么synchronized到底锁的是什么呢?
先说结论,锁的是一个对象,一个类的实例,而不是将一个方法锁起来,如果想要在加上synchronized关键字之后同步运行,那多个线程访问的必须是同一个对象,这是锁的前提。也可以理解为加上synchronized关键字之后同步访问的前提是多个线程访问的是同一个资源,相当于他们是资源共享的。
用一个例子来说明:
twoNum.java是我们的测试类,里面有带锁的addNum方法,根据目前的线程名字来赋予num不同的值,a线程为100,b线程为200
MyThread.java:是自定义线程类,用于,run方法运行twoNum对象的addNum()方法
test.java:main函数
twoNum.java:
package 第二章;
public class twoNum {
private int num=;
synchronized public void addNum(){
try{
if(Thread.currentThread().getName().equals("a")){
num=;
System.out.println("a线程设置num的值");
Thread.sleep();
}else{
num=;
System.out.println("b线程设置num的值");
}
System.out.println(Thread.currentThread().getName()+" "+num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
MyThread.java,
package 第二章;
import 第二章.twoNum; public class MyThread extends Thread {
private twoNum twonum;
public MyThread(twoNum temp){
super();
this.twonum=temp;
}
public void run(){
super.run();
twonum.addNum();
}
}
test.java:
package 第二章;
public class test {
public static void main(String[] args){
twoNum twonum = new twoNum();
MyThread threadA = new MyThread(twonum);
threadA.setName("a");
MyThread threadB = new MyThread(twonum);
threadB.setName("b");
threadA.start();
threadB.start();
}
}
运行上述代码,不出意料的,是线程安全的,并同步运行,因为我们给twonum对象的addNum方法上了锁,并且线程A,B是用一个twoNum初始化的,

更改test.java代码如下,用两个twoNum对象实例分别给A,B线程来初始化:
package 第二章;
public class test {
public static void main(String[] args){
twoNum twonum1 = new twoNum();
twoNum twonum2 = new twoNum();
MyThread threadA = new MyThread(twonum1);
threadA.setName("a");
MyThread threadB = new MyThread(twonum2);
threadB.setName("b");
threadA.start();
threadB.start();
}
}
运行结果如下:

可以看到现在A,B两个线程执行顺序虽然是异步的,但是数据仍然是正常的。为什么呢?很明显,因为有两个twoNum对象,所以有两个对象锁,A,B线程持有不同的锁,所以他们在访问时,访问的是不同对象,那当然能异步运行了,同时也有两个num变量,从属于不同的线程,A线程并不能够更改B线程当中的num变量,所以数据也是正常的。
上面的例子看得出,锁 关键字锁的是对象,
2.synchronized能够锁住所有方法吗?
那么synchronized锁的是整个对象里面的所有方法,还是怎么样呢?
先说结论:synchronized只能够锁住一个对象当中带锁的方法,并不是全部方法。可以理解为局部同步。
这就意味着假如A线程拿到了一个对象的锁,正在访问该对象之中的一个同步方法,这时候B线程也尝试拿到同一个对象锁,如果B线程访问的是该对象当中不带锁的方法,那么久能够拿到该锁并访问,如果访问的是该对象之中带锁的方法,那么B线程无法拿到该锁,只能等A线程释放锁之后才能拿到锁。
下面是一个例子:
修改twoNum.java如下:增加了一个没有锁的方法addNum2
package 第二章;
public class twoNum {
private int num=;
synchronized public void addNum(){
try{
if(Thread.currentThread().getName().equals("a")){
num=;
System.out.println("a线程设置num的值");
Thread.sleep();
}else{
num=;
System.out.println("b线程设置num的值");
}
System.out.println(Thread.currentThread().getName()+" "+num);
}catch (InterruptedException e){
e.printStackTrace();
}
}
public void addNum2(){
try {
System.out.println(Thread.currentThread().getName() + "正在访问");
Thread.sleep();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("访问结束");
}
}
修改MyThread.java文件如下:两个线程,一个运行有锁的方法,另一个运行没有锁的
package 第二章;
import 第二章.twoNum; class MyThread1 extends Thread {
private twoNum twonum;
public MyThread1(twoNum temp){
super();
this.twonum=temp;
}
public void run(){
super.run();
twonum.addNum();
}
}
class MyThread2 extends Thread {
private twoNum twonum;
public MyThread2(twoNum temp){
super();
this.twonum=temp;
}
public void run(){
super.run();
twonum.addNum2();
}
}
test.java:
public class test {
public static void main(String[] args){
twoNum twonum = new twoNum();
MyThread1 threadA = new MyThread1(twonum);
threadA.setName("a");
MyThread2 threadB = new MyThread2(twonum);
threadB.setName("b");
threadA.start();
threadB.start();
}
}
运行结果如图:

可以看到A对象拿到了锁,但是B线程仍然在同时访问了没有锁的addNum2()方法,证明了上述结论,其他线程可以在对象已经被占用的情况下可以异步访问同一个对象的没有锁的方法,但是有锁的方法却不行。有锁的不行这里不演示了,很简单。
理解了这一个概念,就可以解决有时候会碰到的脏读现象,
脏读很好理解,比如你现在执行一个setValue()函数,该函数更改两个值,username和password,当更改完username还没有更改password的时候,调用了getValue()方法获取这两个变量的值,那获取到的username是已经更改过的,但是password是没有更改的,这就出现了脏读。
这时候,运用我们所掌握的知识,给setValue()和getValue()方法都加上锁,这样子在setValue()执行结束之前,getValue()就无法拿到对象锁获取信息,这就解决了脏读问题。
接下来记录几个结论,比较容易理解就不展示例子了,只做记录:
1.出现异常,锁自动释放
2,同步方法不具有继承性,即父类有一个同步方法,那么他的子类如果有一个多态方法,那么子类中的方法想要同步必须加上synchronized关键字,不能继承;
3.synchronized能够锁住一个方法之中的部分代码吗?
可以看到,前面说的都是给整个方法上锁,但是想一下, 如果这个方法的执行时间会很久,A线程先拿到了锁,B线程如果要执行有锁的方法只能等待它执行完再执行,那么效率会很低,比如下面的例子:
twoNum.java:模拟一个任务
package 第二章;
public class twoNum {
private String data;
synchronized public void addNum(){
try{
System.out.println("开始");
Thread.sleep();
data = Thread.currentThread().getName();
System.out.println("结束");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
MyThread1.类,线程类:记录执行时间
class MyThread1 extends Thread {
long time1;
long time2;
private twoNum twonum;
public MyThread1(twoNum temp){
super();
this.twonum=temp;
}
public void run(){
super.run();
time1 = System.currentTimeMillis();
twonum.addNum();
time2=System.currentTimeMillis();
}
}
test.java:主函数
package 第二章;
public class test {
public static void main(String[] args){
twoNum twonum = new twoNum();
MyThread1 threadA = new MyThread1(twonum);
threadA.setName("a");
MyThread1 threadB = new MyThread1(twonum);
threadB.setName("b");
threadA.start();
threadB.start();
try{
Thread.sleep();
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("A线程花费时间:"+(threadA.time2-threadA.time1));
System.out.println("B线程花费时间:"+(threadB.time2-threadB.time1));
}
}
运行结果如下:

可以看到B线程等待了3秒才执行,相当于多花费了3秒的时间。
那么怎么解决呢?使用synchronized同步代码块来解决
首先synchronized同步代码块就是说现在不给整个方法上锁,只给方法之中的部分关键代码上锁,这样当A线程拿到一个对象的锁时,B线程仍然可以访问相同对象之中没有上锁的代码块,但是不能访问上锁的代码块。简单来说,代码块锁synchronized锁的是一个对象的局部代码块,其他线程仍然可以在没有锁的情况下访问非同步代码块。
改变上述twoNum.java的代码,如下:
package 第二章;
public class twoNum {
private String data;
public void addNum(){
try{
System.out.println("开始");
Thread.sleep(3000);
String temp = Thread.currentThread().getName();
synchronized(this) {
System.out.println("线程"+temp+"赋值当中");
data=temp;
System.out.println("线程"+temp+"赋值结束");
}
System.out.println("结束");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
只锁住关键的data赋值代码,其他代码块并不用锁,运行如下:

可以看到,开始结束时异步执行的,但是赋值却是同步的。证明了我们上面的结论,只同步执行synchronized锁住的代码块。
4.synchronized能够锁住除了this以外的其他对象吗?有什么用?有什么需要注意的?
你可能注意到了,上面synchronized(this) 里面锁住了this,这个意思就是说取到当前对象的锁,那么这个this能不能换成其他的对象呢?可以,他可以是任何对象,这个对象我们就叫做对象监听器。那么不同的对象监听器y有什么区别呢?
首先对象监听器总体分为两类:
1.this,即自身
2.非this对象,一般是实例变量或者方法的参数
那么第二种有什么用处呢?假设这种情况,现在有一个类,里面有很多个synchronized方法,执行起来确实是同步的,但是会受到阻塞。不过如果我们使用synchronized(非this对象)同步代码块来锁住一些代码块,这些代码块和其他被锁住的方法就是异步的了,因为他们锁的是不同对象,这样就提升了效率。
简单一句话,synchronized(非this对象)可以让锁住的代码块和其他方法异步执行,下面用程序进行演示:
twoNum.java:两个方法,一个上锁,另一个是synchronized(非this对象)同步代码块
package 第二章;
public class twoNum {
private String anything = new String();
public void addNum(){
try{
synchronized(anything) {
System.out.println("线程"+Thread.currentThread().getName()+"开始");
Thread.sleep();
System.out.println("线程"+Thread.currentThread().getName()+"结束");
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
synchronized public void addNum2(){
try{
System.out.println("线程"+Thread.currentThread().getName()+"开始");
Thread.sleep();
System.out.println("线程"+Thread.currentThread().getName()+"结束");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
MyThread.java:定义了两个线程类,一个运行addNum()另一个运行addNum2()
package 第二章;
import 第二章.twoNum; class MyThread1 extends Thread {
long time1;
long time2;
private twoNum twonum;
public MyThread1(twoNum temp){
super();
this.twonum=temp;
}
public void run(){
super.run();
twonum.addNum();
}
}
class MyThread2 extends Thread {
private twoNum twonum;
public MyThread2(twoNum temp){
super();
this.twonum=temp;
}
public void run(){
super.run();
twonum.addNum2();
}
}
test.java:
public class test {
public static void main(String[] args){
twoNum twonum = new twoNum();
MyThread1 threadA = new MyThread1(twonum);
threadA.setName("a");
MyThread2 threadB = new MyThread2(twonum);
threadB.setName("b");
threadA.start();
threadB.start();
}
}
运行结果如下:

可以看到他们是异步执行的,就是因为他们锁的是不同的对象。
不过要注意两点
1.java有字符串常量池,也就是如果有两个String对象,但是他们的值是相同的,那么当他们作为对象监听器时,他们是被看做同一个锁的。
2.synchronized如果加到一个静态方法上,那么它锁的就不是一个对象,而是整个类了。这时候可以理解为只锁了静态方法,该类的实例对象的锁还是可以正常拿到的。
下面看看java多线程死锁:
简单理解就是多个线程都在互相等待对方释放锁然后执行,双方互相持有对方的锁,这一般是程序bug,这块简单理解一下就行。
(二)对象以及变量的并发访问--synchronized的使用细节,用法的更多相关文章
- Java多线程编程核心技术(二)对象及变量的并发访问
本文主要介绍Java多线程中的同步,也就是如何在Java语言中写出线程安全的程序,如何在Java语言中解决非线程安全的相关问题.阅读本文应该着重掌握如下技术点: synchronized对象监视器为O ...
- 对象和变量的并发访问synchronized解析以及死锁分析排查
一.synchronized java并发编程中存在“非线程安全"问题.“非线程安全"是指发生在多个线程对同一个对象中的实例变量并发访问时,产生的”脏读“现象,使用synchron ...
- Java多线程编程核心技术-第2章-对象及变量的并发访问-读书笔记
第 2 章 对象及变量的并发访问 本章主要内容 synchronized 对象监视器为 Object 时的使用. synchronized 对象监视器为 Class 时的使用. 非线程安全是如何出现的 ...
- Java多线程——对象及变量的并发访问
Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...
- Java——多线程之对象及变量的并发访问
Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...
- Java多线程编程核心技术---对象及变量的并发访问(一)
synchronized同步方法 "非线程安全"其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是"脏读",也就是渠道的数据其实是被更改 ...
- 对象及变量的并发访问(同步方法、同步代码块、对class进行加锁、线程死锁)&内部类的基本用法
主要学习多线程的并发访问,也就是使得线程安全. 同步的单词为synchronized,异步的单词为asynchronized 同步主要就是通过锁的方式实现,一种就是隐式锁,另一种是显示锁Lock,本节 ...
- Java多线程编程核心 - 对象及变量的并发访问
1.什么是“线程安全”与“非线程安全”? “非线程安全”会在多个线程对同一对象总的实例变量进行并发访问时发生,产生的后果是“脏读”,也就是取到的数据其实是被更改过的. “线程安全”是以获得的实例变量的 ...
- Java多线程编程(二)对象及变量的并发访问
一.synchronized同步方法 1.方法内的变量为线程安全 “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了. 示例: ...
随机推荐
- Altium Designer设计PCB--如何增大电源地的线宽
笑话: 看见楼下老大爷在下棋,我看了一会儿,跟大爷说:大爷,你che没了. 大爷一脸不屑:小朋友,那叫ju. 然后我静静地在那看了两个小时. 对完棋,大爷起身要走. 我说:大爷,我刚才说的不是你的棋, ...
- Android 开发你需要了解的那些事
本文微信公众号「AndroidTraveler」首发. 背景 最近部门有新入职员工,作为规划技术路线的导师,这边给新员工安排了学习路线. 除了基本的学习路线之外,每次沟通,我都留了一个小问题,让小伙伴 ...
- Nginx代理和负载均衡实验
一.构建两个tomcat容器并启动 [root@localhost bin]# ps -ef|grep tomcat root : pts/ :: /usr/bin/java -Djava.util. ...
- Linux 终端连接工具 XShell v6.0.01 企业便携版
NetSarang Xshell – 知名终端连接工具,非常强大的SSH远程终端客户端 ,非常好用的SSH终端管理器.Xshell功能超级强大,性能非常优秀,其特色功能支持多标签会话管理主机,支持远程 ...
- Java中什么是type,它和class有什么关系?
看代码总能遇到关键字--type,对于type的概念不明白.翻译水平也有限,所以贴几个地址.自己先看着. https://stackoverflow.com/questions/16600750/di ...
- ZIP:GZIP
GZIPInputStream: GZIPInputStream(InputStream in) :使用默认缓冲区大小创建新的输入流. GZIPInputStream(InputStream in, ...
- 整型,布尔值,字符串详解,for语句 练习
2019 年 7 月 8 日 1.将今天的课上的代码敲一遍,然后整理笔记 已完成 2.有变量name = "aleX leNb" 完成如下操作: 移除 name 变量对应的值两边的 ...
- 开源一个好用的nodejs访问mysql类库
一.背景问题 自nodejs诞生以来出现了一大批的web框架如express koa2 egg等等,前端可以不再依赖后端可以自己控制服务端的逻辑.原来的后端开发同学的阵地前端如今同样也写的风生水起,撸 ...
- 个人永久性免费-Excel催化剂功能第77波-专业图表制作辅助之批量维护序列点颜色及数据标签
2018年最后一天工作日完成第77波,7是代表完美,2个7,双重的完美,Excel催化剂的2018年从始至终共77波都充满着完美接近极致的功能体验.感谢各位一路相随,陪伴成长.最后一波,再次让数据分析 ...
- [leetcode] 929. Unique Email Addresses (easy)
统计有几种邮箱地址. 邮箱名类型:local@domain 规则:1. local中出现"."的,忽略. a.bc=abc 2. local中出现"+"的,+以 ...