关闭

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

标签: java多线程线程
122人阅读 评论(0) 收藏 举报
分类:

同步静态方法

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)和aaiRef.addAndGet(1)这两个操作是可分割导致的。

解决方案,就是给addNum方法加上synchronized即可。

0
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

Java并发——线程同步Volatile与Synchronized详解

Java面试——线程同步volatile与synchronized详解 0. 前言面试时很可能遇到这样一个问题:使用volatile修饰int型变量i,多个线程同时进行i++操作,这样可以实现线程安全...
  • SEU_Calvin
  • SEU_Calvin
  • 2016-08-30 19:09
  • 13428

Java对象锁和类锁全面解析(多线程synchronized关键字)

最近工作有用到一些多线程的东西,之前吧,有用到synchronized同步块,不过是别人怎么用就跟着用,并没有搞清楚锁的概念。最近也是遇到一些问题,不搞清楚锁的概念,很容易碰壁,甚至有些时候自己连用没...
  • u013142781
  • u013142781
  • 2016-06-17 15:51
  • 12869

Kotlin中的并发原语

Concurrency Primitives in Kotlin (Kotlin中的并发原语) 我最近开始阅读G. Blake Meike的“Android Concurrency”,到目前为止,...
  • sergeycao
  • sergeycao
  • 2016-12-27 13:42
  • 5560

初学Java多线程:使用Synchronized关键字同步类方法

初学Java多线程:使用Synchronized关键字同步类方法 本文介绍使用Synchronized关键字同步类方法。要达成Java多线程的run方法同步,需要在void和public之间加上...
  • ihrthk
  • ihrthk
  • 2012-03-05 10:54
  • 620

java关键字volatile和synchronized在多线程中的应用

以前没有弄懂validate和synchronized,借此新开博之际记录下。 volatile(不稳定):java关键字用来修饰变量,不可用来修饰方法。 在java语法中对于变量值读写的操作...
  • u010670757
  • u010670757
  • 2013-05-16 17:15
  • 775

Java——多线程总结、ThreadLocal/Volatile/synchronized/Atomic关键字

当线程被创建并启动之后,它既不是一启动就进入执行状态,也不是一直处于执行状态,在其生命周期中,要经过”新建(New)”、”就绪(Runnable)”、”运行(Running’)”、”阻塞(Blocke...
  • wuseyukui
  • wuseyukui
  • 2015-10-08 10:54
  • 928

Java单例模式及创建单例模式的多线程问题 volatile synchronized 关键字

to do!!!
  • d_good
  • d_good
  • 2017-01-06 16:32
  • 293

6. 初学Java多线程:慎重使用volatile关键字

学习Java多线程中会遇到使用volatile关键字的情况。volatile关键字用于声明简单类型变量,如int、float、boolean等数据类型。使用它有一定的限制。volatile关键字相信了...
  • johnson106
  • johnson106
  • 2011-07-08 17:20
  • 219

java多线程之synchronized和volatile关键字

synchronized同步方法脏读在多个线程对同一个对象中的实例变量进行并发访问的时候,取到的数据可能是被更改过的,称之为“脏读”,这就是非线程安全的。解决的方法为synchronized关键字进行...
  • sunpeng_sp
  • sunpeng_sp
  • 2016-11-04 22:46
  • 1912

多线程:线程状态、synchronized关键字、读写锁、条件对象、Volatile、阻塞队列等小结

多线程:线程状态、synchronized关键字、读写锁、条件对象、Volatile、阻塞队列等小结 关于多线程,在这里做下总结,也方便以后自己查阅。线程和进程的关系应该都知道了,这里不细说。在线程出...
  • nieyanshun_me
  • nieyanshun_me
  • 2015-08-12 22:45
  • 961
    个人资料
    • 访问:41124次
    • 积分:775
    • 等级:
    • 排名:千里之外
    • 原创:19篇
    • 转载:112篇
    • 译文:0篇
    • 评论:3条
    文章分类
    最新评论