同步静态方法

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多线程学习(三)volatile关键字

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79680693 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...

  2. JAVA多线程基础学习三:volatile关键字

    Java的volatile关键字在JDK源码中经常出现,但是对它的认识只是停留在共享变量上,今天来谈谈volatile关键字. volatile,从字面上说是易变的.不稳定的,事实上,也确实如此,这个 ...

  3. JAVA 多线程随笔 (一) 可见性和volatile关键字

    // 先上代码 1 public class NoVisibility { private static boolean ready; private static int number; priva ...

  4. 【Java并发编程】6、volatile关键字解析&内存模型&并发编程中三概念

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

  5. Java并发编程(六)volatile关键字解析

    由于volatile关键字是与Java的内存模型有关的,因此在讲述volatile关键之前,我们先来了解一下与内存模型相关的概念和知识. 一.内存模型的相关概念 Java内存模型规定所有的变量都是存在 ...

  6. Java多线程-同步:synchronized 和线程通信:生产者消费者模式

    大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同 ...

  7. synchronized锁定类方法、volatile关键字及其他(八)

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

  8. Java 多线程之 synchronized 和 volatile 的比較

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

  9. Java多线程同步 synchronized 关键字的使用

    代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A, ...

随机推荐

  1. android中将EditText改成不可编辑的状态

    今天在做项目的时候,要想实现一个将EditText变成不可编辑的状态,通过查找博客,发现一个好方法,对于单独的EditText控件我们可以单独设置 1.首先想到在xml中设置Android:edita ...

  2. 使用afinal下载文件并且在状态栏中显示下载的进度

    2013年10月23日,今天是在“我在找你信息服务有限公司”第一天上班,公司给提出了这样一个要求:下载本公司的app,并且在下载的过程中要在状态栏中显示下载的进度,并且,可以暂停和继续下载. 下面是我 ...

  3. qweb

    qweb 是 odoo的模板系统, 在 odoo系统中, 它有不同的用途和实现, 一个是 web client 的 widget 的渲染引擎, 它是通过 javascript实现的,也是 最早引入到 ...

  4. C# 反转字符串方法

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace 反转字符 ...

  5. ZOJ3774_Power of Fibonacci

    求fibonacci数列前N个数的K次方和. 通项公式:F[n]=((1+sqrt(5))/sqrt(5)-(1-sqrt(5))/sqrt(5))/sqrt(5). 有点乱,不过由于可以保证最后的结 ...

  6. kiosk-mode,免密码登陆, sideload Windows Store apps 等

    MVVM带来的性能问题及其解决方案  MVVM 和语言性能提示:https://msdn.microsoft.com/zh-cn/library/windows/apps/xaml/mt628050. ...

  7. 使用Application Insights 做分析

    Application Insights on Windows Desktop apps, services and worker roles : https://azure.microsoft.co ...

  8. HTML5中新添加事件

    HTML5中新添加了很多事件,但是由于他们的兼容问题不是很理想,应用实战性不是太强,所以在这里基本省略,咱们只分享应用广泛兼容不错的事件,日后随着兼容情况提升以后再陆续添加分享.今天为大家介绍的事件主 ...

  9. Android应用程序Monkey测试

    Monkey是Android SDK中附带的一个测试工具:Monkey用于进行压力测试,软件开发人员结合monkey打印日志和系统日志,解决测试中出现的问题. Monkey测试的特点:所有事件都是随机 ...

  10. DeliciousRetouch加强中文版v3.0-支持CS6--C鲁中C 2015.5 影楼磨破插件

    支持PS CC至最新版CC 2015.5下载地址: [点击下载] 支持系统Win/Mac 画像修版"美味修整3"扩展Photoshop 您需要使用的所有工具DR3扩展,这里的顶部面 ...