Java multithreading 6: synchronized locking class method, volatile keyword and others

Reprinted from: http://www.cnblogs.com/xrq730/p/4853578.html


Synchronized static method

Synchronized can also be applied to static methods. If written like this, it means locking the Class class corresponding to the current .java file . Take a look at the example and notice that printC() is not a static method:

copy code
public class ThreadDomain25
{
    public synchronized static void printA()
    {
        try
        {
            System.out.println( "Thread name is: " + Thread.currentThread().getName() +
                    "In" + System.currentTimeMillis() + "Enter the printA() method" );
            Thread.sleep(3000);
            System.out.println( "Thread name is: " + Thread.currentThread().getName() +
                    "In" + System.currentTimeMillis() + "Leave printA() method" );
        }
        catch (InterruptedException e)
        {
            e.printStackTrace ();
        }
    }
    
    public synchronized static void printB()
    {
        System.out.println( "Thread name is: " + Thread.currentThread().getName() +
                "In" + System.currentTimeMillis() + "Enter the printB() method" );
        System.out.println( "Thread name is: " + Thread.currentThread().getName() +
                "In" + System.currentTimeMillis() + "Leave printB() method" );

    }
    
    public synchronized void printC()
    {
        System.out.println( "Thread name is: " + Thread.currentThread().getName() +
                "In" + System.currentTimeMillis() + "Enter the printC() method" ); 
        System.out.println( "Thread name is: " + Thread.currentThread().getName() +
                "In" + System.currentTimeMillis() + "Leave printC() method" );
    }
}
copy code

Write three threads to call these three methods respectively:

copy code
public class MyThread25_0 extends Thread
{
    public void run()
    {
        ThreadDomain25.printA();
    }
}
copy code
copy code
public class MyThread25_1 extends Thread
{
    public void run()
    {
        ThreadDomain25.printB();
    }
}
copy code
copy code
public class MyThread25_2 extends Thread
{
    private ThreadDomain25 td;
    
    public MyThread25_2(ThreadDomain25 td)
    {
        this.td = td;
    }
    
    public void run()
    {
        td.printC();
    }
}
copy code

Write a main function to start these three threads:

copy code
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();
}
copy code

看一下运行结果:

线程名称为: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关键字

直接先举一个例子:

copy code
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("线程被停止了");
    }
}
copy code
copy code
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();
    }
}
copy code

看一下运行结果:

进入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无论是同步的方法还是同步的代码块,都会先把主内存的数据拷贝到工作内存中,同步代码块结束,会把工作内存中的数据更新到主内存中,这样主内存中的数据一定是最新的。

 

原子类也无法保证线程安全

原子操作表示一段操作是不可分割的,没有其他线程能够中断或检查正在原子操作中的变量。一个原子类就是一个原子操作可用的类,它可以在没有锁的情况下保证线程安全。

但是这种线程安全不是绝对的,在有逻辑的情况下输出结果也具有随机性,比如

copy code
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);
    }
}
copy code
copy code
public class MyThread29 extends Thread
{
    private ThreadDomain29 td;
    
    public MyThread29(ThreadDomain29 td)
    {
        this.td = td;
    }
    
    public void run()
    {
        td.addNum();
    }
}
copy code
copy code
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 ();
    }
}
copy code

Here is an atomic class of Integer, AtomicInteger, to see the results:

The result of adding 100 to Thread-1: 200 The result of adding 100 to 
Thread -4: 500 The result of adding 100 to 
Thread -3: 400 The result of adding 100 to 
Thread -2: 300 The result of adding 100 to 
Thread - 0 Results after: 100
505

Obviously, the result is correct, but it is not what we want, because we definitely want to output the result after adding in order, and now it is output like 200, 500, 400, 300, 100. The reason for this problem is that the two operations aiRef.addAndGet(100) and aiRef.addAndGet(1) are separable.

The solution is to add synchronized to the addNum method.


Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326886224&siteId=291194637