同步静态方法

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即可。

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

  1. Java多线程:synchronized | Volatile 和Lock和ReadWriteLock多方位剖析(一)

    前言 本文站在多线程初中级学习者的角度,较为全面系统的带你一起了解多线程与锁相关的知识点.带你一起解开与锁相关的各种概念.用法.利弊等.比如:synchronized.Volatile.Lock.Re ...

  2. Java 多线程之 synchronized 和 volatile 的比较

    概述 在做多线程并发处理时,经常需要对资源进行可见性访问和互斥同步操作.有时候,我们可能从前辈那里得知我们需要对资源进行 volatile 或是 synchronized 关键字修饰处理.可是,我们却 ...

  3. Java多线程学习三十七:volatile 的作用是什么?与 synchronized 有什么异同

    volatile 是什么 首先我们就来介绍一下 volatile,它是 Java 中的一个关键字,是一种同步机制.当某个变量是共享变量,且这个变量是被 volatile 修饰的,那么在修改了这个变量的 ...

  4. Java并发编程:JMM和volatile关键字

    Java内存模型 随着计算机的CPU的飞速发展,CPU的运算能力已经远远超出了从主内存(运行内存)中读取的数据的能力,为了解决这个问题,CPU厂商设计出了CPU内置高速缓存区.高速缓存区的加入使得CP ...

  5. 四、java多线程核心技术——synchronized同步方法与synchronized同步快

    一.synchronized同步方法 论:"线程安全"与"非线程安全"是多线程的经典问题.synchronized()方法就是解决非线程安全的. 1.方法内的变 ...

  6. Java多线程同步Synchronized使用分析

    同步的概念: 同步分为 同步方法 和 同步块 两种方式. 锁定的内容分为 锁定类的某个特定实例 和 锁定类对象(类的所有实例) 变量分为 实例变量(不带static的变量) 和 类变量(带static ...

  7. 【多线程高并发】深入浅出volatile关键字

    1.来自我的灵魂拷问 你是否经常碰到volatile关键字? volatile关键字的作用是什么? JMM层面如何理解volatile关键字? 你是否探究过volatile在汇编层面的实现? 如何看待 ...

  8. Java 多线程:synchronized 关键字用法(修饰类,方法,静态方法,代码块)

    前言 在 多线程生成的原因(Java内存模型与i++操作解析) 中,介绍了Java的内存模型,从而可能导致的多线程问题.synchronized就是避免这个问题的解决方法之一.除了 synchroni ...

  9. Java多线程:synchronized关键字和Lock

    一.synchronized synchronized关键字可以用于声明方法,也可以用来声明代码块,下面分别看一下具体的场景(摘抄自<大型网站系统与Java中间件实践>) 案例一:其中fo ...

最新文章

  1. 浅谈单图像三维重建算法
  2. 同一个网站,手机端跟电脑端显示不同是怎么实现的?...
  3. 综合应用WPF/WCF/WF/LINQ之三:采用用代码创建的方式实现CheckListBox的CustomControl
  4. stringbuilder class再反编译_JVM篇(02.class字节码解析)
  5. mysql设计经纬度表_MySQL经纬度表设置
  6. WHY MAKE ANOTHER APPLICATION AT ISSM?
  7. linux虚拟机ip地址更改
  8. Apache Fluo:填充Google搜索索引的Percolator的实现
  9. 微软请你学Linux,最后4节课,即将收官,错过了就没有了!
  10. w7如何调出计算机图标来,W7电脑系统怎么显示桌面图标
  11. Windows环境下安装React Native开发环境----记一次填坑过程
  12. 为什么出现股市二八现象?
  13. 微信小程序API之setInterval
  14. 通过keepalived搭建lvs高可用集群
  15. centos6安装mysql并远程连接_Linux中Mysql不支持远程连接解决办法
  16. Tomcat 7 下载地址
  17. 来诈金花嘛?Python实现的那种
  18. 概率空间--样本--事件--随机变量--
  19. 在iOS应用中跳转到淘宝或天猫客户端商品详情页
  20. linux绝育玩客云_玩客云实用指南(真·无痛绝育),附玩物下载对比

热门文章

  1. Linux多线程实践(一)线程基本概念和理论
  2. find命令详解(原创)
  3. Spring源代码解析(收藏)
  4. Nginx中worker_connections的问题
  5. linux命令——tar
  6. 物联网时代更要注意信息安全
  7. 3550配置DHCP
  8. 华为的创新——计划制定和调整水平
  9. QoS、IPv6、软交换和VoIP技术受质疑
  10. centos源码安装mysql5.7.25-boost