(二)对象以及变量的并发访问--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.方法内的变量为线程安全 “非线程安全”问题存在于“实例变量”中,如果是方法内部的私有变量,则不存在“非线程安全”问题,所得结果也就是“线程安全”的了. 示例: ...
随机推荐
- 分布式事务(3)---RocketMQ实现分布式事务原理
分布式事务(3)-RocketMQ实现分布式事务原理 之前讲过有关分布式事务2PC.3PC.TCC的理论知识,博客地址: 1.分布式事务(1)---2PC和3PC原理 2.分布式事务(2)---TCC ...
- 每日一问:View.getContext() 的返回一定是 Activity 么?
坚持原创日更,短平快的 Android 进阶系列,敬请直接在微信公众号搜索:nanchen,直接关注并设为星标,精彩不容错过. 一般我们被问到这样的问题,通常来说,答案都是否定的,但一定得知道其中的原 ...
- Lucene03--字段属性
Lucene03--字段属性 1.Field 1.1 Field相当于Javabean的属性. 1.2 不同的Field的构造方法参数不一样: 大多数Field的构造函数有三个参数: a)第一个参 ...
- 自定义new和delete
#include "stdafx.h" #include <stdlib.h> #include <malloc.h> #include <iostr ...
- Node.js socket 双向通信
使用场景: 聊天室:大量数据常驻交互: 技术栈: Node.js, Vue.js || 原生JS 服务端代码: const app = require('http').createServe ...
- python3.x 与 python2.x 差别记录
从2.x过渡到3.x的时候,遇到了大大小小的坑,于是便记录下来- 1.print: 3.x 所有print都要加 "( )",print更像(就是)一个函数了. 2.x 可以加& ...
- Windows 设置自启动计划任务(非登录启动)
原因:服务器会不定期重启,且重启后无人看管,不会有人去登录系统.导致我们做的一些开机启动程序失效,进而系统瘫痪. 解决方法: 自己理解,想要达到目的有两种方式:系统服务 & 计划任务配置. 计 ...
- WGS84坐标与web墨卡托投影坐标转换
许久没有使用坐标转换,记忆有些模糊了,以后还是会用到,先将WGS84与web墨卡托转换复习一下: 1.84转web墨卡托 //核心公式 平面坐标x = 经度*20037508.34/108 平面坐标y ...
- Linux中的update和upgrade的作用
update 是同步 /etc/apt/sources.list 和 /etc/apt/sources.list.d 中列出的源的索引,这样才能获取到最新的软件包.update是下载源里面的metad ...
- CIDR的介绍
CIDR的介绍: CIDR(Classless Inter-Domain Routing,无类域间路由选择)它消除了传统的A类.B类和C类地址以及划分子网的概念,因而可以更加有效地分配IPv4的地址空 ...