synchronized锁定类方法、volatile关键字及其他(八)
同步静态方法
synchronized还可以应用在静态方法上,如果这么写,则代表的是对当前.java文件对应的Class类加锁。看一下例子,注意一下printC()并不是一个静态方法:

public class ThreadDomain25
{
public synchronized static void printA()
{
try
{
System.out.println("线程名称为:" + Thread.currentThread().getName() +
"在" + System.currentTimeMillis() + "进入printA()方法");
Thread.sleep(3000);
System.out.println("线程名称为:" + Thread.currentThread().getName() +
"在" + System.currentTimeMillis() + "离开printA()方法");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
} public synchronized static void printB()
{
System.out.println("线程名称为:" + Thread.currentThread().getName() +
"在" + System.currentTimeMillis() + "进入printB()方法");
System.out.println("线程名称为:" + Thread.currentThread().getName() +
"在" + System.currentTimeMillis() + "离开printB()方法"); } public synchronized void printC()
{
System.out.println("线程名称为:" + Thread.currentThread().getName() +
"在" + System.currentTimeMillis() + "进入printC()方法");
System.out.println("线程名称为:" + Thread.currentThread().getName() +
"在" + System.currentTimeMillis() + "离开printC()方法");
}
}

写三个线程分别调用这三个方法:

public class MyThread25_0 extends Thread
{
public void run()
{
ThreadDomain25.printA();
}
}


public class MyThread25_1 extends Thread
{
public void run()
{
ThreadDomain25.printB();
}
}


public class MyThread25_2 extends Thread
{
private ThreadDomain25 td; public MyThread25_2(ThreadDomain25 td)
{
this.td = td;
} public void run()
{
td.printC();
}
}

写个main函数启动这三个线程:

public static void main(String[] args)
{
ThreadDomain25 td = new ThreadDomain25();
MyThread25_0 mt0 = new MyThread25_0();
MyThread25_1 mt1 = new MyThread25_1();
MyThread25_2 mt2 = new MyThread25_2(td);
mt0.start();
mt1.start();
mt2.start();
}

看一下运行结果:
线程名称为:Thread-0在1443857019710进入printA()方法
线程名称为:Thread-2在1443857019710进入printC()方法
线程名称为:Thread-2在1443857019710离开printC()方法
线程名称为:Thread-0在1443857022710离开printA()方法
线程名称为:Thread-1在1443857022710进入printB()方法
线程名称为:Thread-1在1443857022710离开printB()方法
从运行结果来,对printC()方法的调用和对printA()方法、printB()方法的调用时异步的,这说明了静态同步方法和非静态同步方法持有的是不同的锁,前者是类锁,后者是对象锁。
所谓类锁,举个再具体的例子。假如一个类中有一个静态同步方法A,new出了两个类的实例B和实例C,线程D持有实例B,线程E持有实例C,只要线程D调用了A方法,那么线程E调用A方法必须等待线程D执行完A方法,尽管两个线程持有的是不同的对象。
volatile关键字
直接先举一个例子:

public class MyThread28 extends Thread
{
private boolean isRunning = true; public boolean isRunning()
{
return isRunning;
} public void setRunning(boolean isRunning)
{
this.isRunning = isRunning;
} public void run()
{
System.out.println("进入run了");
while (isRunning == true){}
System.out.println("线程被停止了");
}
}


public static void main(String[] args)
{
try
{
MyThread28 mt = new MyThread28();
mt.start();
Thread.sleep(1000);
mt.setRunning(false);
System.out.println("已赋值为false");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}

看一下运行结果:
进入run了
已赋值为false
也许这个结果有点奇怪,明明isRunning已经设置为false了, 线程还没停止呢?
这就要从Java内存模型(JMM)说起,这里先简单讲,虚拟机那块会详细讲的。根据JMM,Java中有一块主内存,不同的线程有自己的工作内存,同一个变量值在主内存中有一份,如果线程用到了这个变量的话,自己的工作内存中有一份一模一样的拷贝。每次进入线程从主内存中拿到变量值,每次执行完线程将变量从工作内存同步回主内存中。
出现打印结果现象的原因就是主内存和工作内存中数据的不同步造成的。因为执行run()方法的时候拿到一个主内存isRunning的拷贝,而设置isRunning是在main函数中做的,换句话说 ,设置的isRunning设置的是主内存中的isRunning,更新了主内存的isRunning,线程工作内存中的isRunning没有更新,当然一直死循环了,因为对于线程来说,它的isRunning依然是true。
解决这个问题很简单,给isRunning关键字加上volatile。加上了volatile的意思是,每次读取isRunning的值的时候,都先从主内存中把isRunning同步到线程的工作内存中,再当前时刻最新的isRunning。看一下给isRunning加了volatile关键字的运行效果:
进入run了
已赋值为false
线程被停止了
看到这下线程停止了,因为从主内存中读取了最新的isRunning值,线程工作内存中的isRunning变成了false,自然while循环就结束了。
volatile的作用就是这样,被volatile修饰的变量,保证了每次读取到的都是最新的那个值。线程安全围绕的是可见性和原子性这两个特性展开的,volatile解决的是变量在多个线程之间的可见性,但是无法保证原子性。
多提一句,synchronized除了保障了原子性外,其实也保障了可见性。因为synchronized无论是同步的方法还是同步的代码块,都会先把主内存的数据拷贝到工作内存中,同步代码块结束,会把工作内存中的数据更新到主内存中,这样主内存中的数据一定是最新的。
原子类也无法保证线程安全
原子操作表示一段操作是不可分割的,没有其他线程能够中断或检查正在原子操作中的变量。一个原子类就是一个原子操作可用的类,它可以在没有锁的情况下保证线程安全。
但是这种线程安全不是绝对的,在有逻辑的情况下输出结果也具有随机性,比如

public class ThreadDomain29
{
public static AtomicInteger aiRef = new AtomicInteger(); public void addNum()
{
System.out.println(Thread.currentThread().getName() + "加了100之后的结果:" +
aiRef.addAndGet(100));
aiRef.getAndAdd(1);
}
}


public class MyThread29 extends Thread
{
private ThreadDomain29 td; public MyThread29(ThreadDomain29 td)
{
this.td = td;
} public void run()
{
td.addNum();
}
}


public static void main(String[] args)
{
try
{
ThreadDomain29 td = new ThreadDomain29();
MyThread29[] mt = new MyThread29[5];
for (int i = 0; i < mt.length; i++)
{
mt[i] = new MyThread29(td);
}
for (int i = 0; i < mt.length; i++)
{
mt[i].start();
}
Thread.sleep(1000);
System.out.println(ThreadDomain29.aiRef.get());
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}

这里用了一个Integer的原子类AtomicInteger,看一下运行结果:
Thread-1加了100之后的结果:200
Thread-4加了100之后的结果:500
Thread-3加了100之后的结果:400
Thread-2加了100之后的结果:300
Thread-0加了100之后的结果:100
505
显然,结果是正确的,但不是我们想要的,因为我们肯定希望按顺序输出加了之后的结果,现在却是200、500、400、300、100这么输出。导致这个问题产生的原因是aiRef.addAndGet(100)和aiRef.addAndGet(1)这两个操作是可分割导致的。
解决方案,就是给addNum方法加上synchronized即可。
参看链接:https://www.cnblogs.com/xrq730/p/4853578.html
synchronized锁定类方法、volatile关键字及其他(八)的更多相关文章
- Java多线程6:synchronized锁定类方法、volatile关键字及其他
同步静态方法 synchronized还可以应用在静态方法上,如果这么写,则代表的是对当前.java文件对应的Class类加锁.看一下例子,注意一下printC()并不是一个静态方法: public ...
- Java 并发:volatile 关键字解析
摘要: 在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保 ...
- java并发系列(六)-----Java并发:volatile关键字解析
在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保证可见性 ...
- Java并发编程:volatile关键字解析
Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...
- 【转】Java并发编程:volatile关键字解析
转自:http://www.importnew.com/18126.html#comment-487304 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备 ...
- (转)Java并发编程:volatile关键字解析
转:http://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或 ...
- volatile关键字解析
转载:http://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受 ...
- 从根源上解析 Java volatile 关键字的实现
1.解析概览 内存模型的相关概念 并发编程中的三个概念 Java内存模型 深入剖析Volatile关键字 使用volatile关键字的场景 2.内存模型的相关概念 缓存一致性问题.通常称这种被多个线程 ...
- 《转》JAVA并发编程:volatile关键字解析
volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以 ...
随机推荐
- Spring源码分析-从@ComponentScan注解配置包扫描路径到IoC容器中的BeanDefinition,经历了什么(一)?
阅前提醒 全文较长,建议沉下心来慢慢阅读,最好是打开Idea,点开Spring源码,跟着下文一步一步阅读,更加便于理解.由于笔者水平优先,编写时间仓促,文中难免会出现一些错误或者不准确的地方,恳请各位 ...
- 一、Nginx的安装
1.下载nginx软件 http://nginx.org/download/ 2.安装依赖包 [root@client ~]# useradd -s /sbin/nologin nginx 创建ngi ...
- Linux 的ftp服务未启用怎么办
1.检查是否安装ftp相关的rpm包(如出现下面提示证明是安装过相关rpm包的) [root@rac1 ~]# which vsftpd /usr/sbin/vsftpd [root@ra ...
- [源码解析] 深度学习分布式训练框架 horovod (7) --- DistributedOptimizer
[源码解析] 深度学习分布式训练框架 horovod (7) --- DistributedOptimizer 目录 [源码解析] 深度学习分布式训练框架 horovod (7) --- Distri ...
- 【春节歌曲回味 | STM32小音乐盒 】PWM+定时器驱动无源蜂鸣器(STM32 HAL库)
l STM32通过PWM与定时器方式控制无源蜂鸣器鸣响 l STM32小音乐盒,歌曲进度条图形显示与百分比显示,歌曲切换 l 编程使用STM32 HAL库 l IIC OLED界面编程,动画实 ...
- POJ 1775 Sum of Factorials 数论,基础题
输入一个小于1000000的正整数,是否能表达成式子:a1!+a2!+a3!+...+an (a1~an互不相等). 因为10!>1000000,所以先打1~10的阶乘表.从a[10]开始递减判 ...
- Algorithm:Java加密解密之MAC(消息认证码)
MD5 消息摘要(数字摘要) 它是把一个文本/文件 通过摘要函数(hash函数)计算出一个结果.然后把文本/文件和摘要结果一同发给接受者接受者接收到文件之后,也进行摘要,把两个摘要结果进行对比.如果一 ...
- 资源:jenkins下载路径
Jenkins版本下载路径 所有版本:http://mirrors.jenkins.io/war/
- Nginx:Nginx日志切割方法
Nginx的日志文件是没有切割(rotate)功能的,但是我们可以写一个脚本来自动切割日志文件. 首先我们要注意两点: 1.切割的日志文件是不重名的,所以需要我们自定义名称,一般就是时间日期做文件名. ...
- 『心善渊』Selenium3.0基础 — 24、Selenium的expected_conditions模块详细介绍
目录 1.EC模块介绍 2.EC模块常用类 3.EC模块的使用 4.EC模块综合使用 (1)title_is(title)示例 (2)presence_of_element_located(locat ...