同步静态方法

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关键字及其他(八)的更多相关文章

  1. Java多线程6:synchronized锁定类方法、volatile关键字及其他

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

  2. Java 并发:volatile 关键字解析

    摘要: 在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保 ...

  3. java并发系列(六)-----Java并发:volatile关键字解析

    在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保证可见性 ...

  4. Java并发编程:volatile关键字解析

    Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...

  5. 【转】Java并发编程:volatile关键字解析

    转自:http://www.importnew.com/18126.html#comment-487304 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备 ...

  6. (转)Java并发编程:volatile关键字解析

    转:http://www.cnblogs.com/dolphin0520/p/3920373.html Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或 ...

  7. volatile关键字解析

    转载:http://www.cnblogs.com/dolphin0520/p/3920373.html volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受 ...

  8. 从根源上解析 Java volatile 关键字的实现

    1.解析概览 内存模型的相关概念 并发编程中的三个概念 Java内存模型 深入剖析Volatile关键字 使用volatile关键字的场景 2.内存模型的相关概念 缓存一致性问题.通常称这种被多个线程 ...

  9. 《转》JAVA并发编程:volatile关键字解析

    volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以 ...

随机推荐

  1. oracle表ddl审计

    ============= 表ddl 审计============== 1.table信息 SQL> select * from test; ID CUST_CREDIT_LIMIT TIME ...

  2. 最新版robotframework4.0.3版本安装

    python最新版本已经到了3.9了.对应的最新版本也更新到robotframework4.0.3. robotframework支持的自动化测试的类型以及需要的库: 1.ui自动化测试:Seleni ...

  3. NX二次开发-将3X3矩阵修正为正交且长度为单位长度的矩阵

    函数:UF_MTX3_ortho_normalize() 函数说明:将矩阵修正为正交且xyz长度为单位长度的矩阵.下图中输入的矩阵为三条线段的端点,经过修正后,生成一个坐标系. 1 #include ...

  4. 面试官:一个TCP连接可以发多少个HTTP请求?

    曾经有这么一道面试题:从 URL 在浏览器被被输入到页面展现的过程中发生了什么? 相信大多数准备过的同学都能回答出来,但是如果继续问:收到的 HTML 如果包含几十个图片标签,这些图片是以什么方式.什 ...

  5. Kubernetes集群安装

    一.环境介绍 主机名 IP地址 master 192.168.0.100 node1 192.168.0.101 node2 192.168.0.102 1.1.操作系统: CensOS8.4.210 ...

  6. Java并发编程中的锁

    synchronized 使用synchronized实现同步有2种方式: 同步方法(静态与非静态) 同步代码块 任何Java对象均可作为锁使用,其中,使用的锁对象有以下3种: 静态同步方法中,锁是当 ...

  7. 『无为则无心』Python序列 — 21、Python字典及其常用操作

    目录 1.字典的应用场景 2.字典的概念 3.创建字典的语法 4.字典常见操作 (1)字典的增加操作 (2)字典的删除操作 (3)字典的修改 (4)字典的查找 (5)copy()复制 1.字典的应用场 ...

  8. 3、oracle表空间及索引操作

    3.1.创建表空间和用户授权: 1.创建表空间: CREATE TABLESPACE <表空间名> LOGGING DATAFILE '<存放路径>' SIZE 50M AUT ...

  9. Gym 100283F Bakkar In The Army

    数学公式: n^2的前n项和n(n+1)(2*n+1)/6,用二分进行查找: 算出层数后继续二分查找位于这一层的哪一位,也可以推出相应公式 #include <iostream> #inc ...

  10. Springboot整合shardingsphere和druid进行读写分离

    最近在使用springboot整合shardingsphere和druid实现mysql数据库读写分离时遇到了一些问题,特此记录一下. 依赖版本 Springboot 2.1.6.RElEASE sh ...