源无极

导航

 

一、Volatile关键字

主要作用是使变量在多个线程间可见。

(一)、普通死循环

package com.it.po.thread07;

public class PrintString {
private boolean isContinuePrint=true;
public boolean isContinuePrint(){
    return isContinuePrint;
}
public  void setContinuePrint(boolean isContinuePrint){
    this.isContinuePrint=isContinuePrint;
}

public void printString(){
    String threadName=Thread.currentThread().getName();
    try {
        while (isContinuePrint==true){
       System.out.println("线程: "+threadName+" 进入printString方法。。。");
            Thread.sleep(1000);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
}

 

package com.it.po.thread07;
public class PrintRun {
    public static void main(String[] args){
        PrintString po = new PrintString();
        po.printString();
        System.out.println("我是主线程,我要停止printString方法");
        po.setContinuePrint(false);
    }
}

 

 

线程: main 进入printString方法。。。
线程: main 进入printString方法。。。
线程: main 进入printString方法。。。
线程: main 进入printString方法。。。
线程: main 进入printString方法。。。
线程: main 进入printString方法。。。
。。。。

 

 线程一直在处理while循环无法到达

po.setContinuePrint(false);

 (二)、解决同步死循环

package com.it.po.thread07;
public class PrintThread implements Runnable {
    private boolean isContinuePrint=true;
    public boolean isContinuePrint(){
        return isContinuePrint;
    }
    public  void setContinuePrint(boolean isContinuePrint){
        this.isContinuePrint=isContinuePrint;
    }
    public void printString(){
        String threadName=Thread.currentThread().getName();
        try {
            while (isContinuePrint==true){
                System.out.println("线程: "+threadName+" 进入printString方法。。。");
                Thread.sleep(1000);//当前类是线程即可切换到main线程
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        printString();
    }
}

 

package com.it.po.thread07;

public class PrintRun2 {

    public static void main(String[] args){
        try {
            PrintThread printThread = new PrintThread();
            new Thread(printThread).start();//自动执行run方法
            Thread.sleep(5000);
            System.out.println("我是主线程,我要停止printString方法");
            printThread.setContinuePrint(false);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

 

线程: Thread-0 进入printString方法。。。
线程: Thread-0 进入printString方法。。。
线程: Thread-0 进入printString方法。。。
线程: Thread-0 进入printString方法。。。
线程: Thread-0 进入printString方法。。。
我是主线程,我要停止printString方法

 

       当上面代码的格式,一旦运行在-server服务器模式中64bitd JVM上时,会出现四循环解决办法是使用volatile关键字

该关键字的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中获取变量的值。

(二)、解决异步死循环

先看一个小实验

package com.it.po.thread07;

public class RunThread extends Thread {
    private boolean isRunning=true;
    public boolean isRunning(){
        return isRunning;
    }
    public  void setIsRunning(boolean isRunning){
        this.isRunning=isRunning;
    }
    @Override
    public void run() {
        super.run();
        String threadName=Thread.currentThread().getName();
        while (isRunning==true){
            System.out.println("线程: "+threadName+" 进入printString方法。。。");
        }
        System.out.println("run方法被停止了。。。");
    }



}

 

package com.it.po.thread07;
import com.sun.org.apache.bcel.internal.generic.NEW;
public class Run3 {
    public static void main(String[] args) throws InterruptedException {
        RunThread runThread = new RunThread();
        new Thread(runThread,"A").start();
        Thread.sleep(100);
        runThread.setIsRunning(false);
        System.out.println("main 线程停止了 runThread");
    }
}

 

 

 注意这样的代码,在开发工具JVM设置为Server服务器中的环境,是会出现四循环的

 

       由于 变量 private boolean isRunning=true; 存在于公共堆栈及线程的私有堆栈中

在JVM设置为-server模式中,为了线程运行的效率,线程一直在私有栈中取得

  isRunning=true  ,而代码  runThread.setIsRunning(false) 虽然被执行更新的确是

公共堆栈中的isRunning  变量值变成false ,所以一直死循环。

     问题原因:

     私有堆栈中和公共堆栈中变量不同步造成的,解决这个问题就得用volatile了

其作用:当线程访问isRunning这个变量时,强制性从公共堆栈中取值。

 

 

 优化如下

package com.it.po.thread07;

public class RunThread extends Thread {
     volatile   private boolean isRunning=true;
    public boolean isRunning(){
        return isRunning;
    }
    public  void setIsRunning(boolean isRunning){
        this.isRunning=isRunning;
    }
    @Override
    public void run() {
        super.run();
        String threadName=Thread.currentThread().getName();
        while (isRunning==true){
            System.out.println("线程: "+threadName+" 进入printString方法。。。");
        }
        System.out.println("run方法被停止了。。。");
    }



}

 

volatile  关键字强制从公共堆栈读取值如下图

 

 总结volatile最致命的缺点是不支持原子性

volattile和  synchronized 的比较

1)volattile是线程同步轻量级实现,所以性能比synchronized  要好,但是volattile只能修饰于变量,

随着jdk的更新,synchronized  性能在提升。

2)多线程访问volattile不会发生阻塞,synchronized 会。

 3)volattile保证数据的可见性,但是不能保证原子性,synchronized 是可以保证数据原子性的,

synchronized 也可以间接保证可见性,因为它可将私有内存和公共内存的数据做同步。

4)注意:volattile解决的是变量在多个线程之间的可见性;

synchronized 关键字解决的是多个线程之间访问资源的同步性

 注:线程安全包含原子性和可见性,java的同步机制都是围绕着两个方面来确保线程安全。

 

 二、volatile非原子的特性

该关键字增加了实例变量在多线程之间的可见性,但是不具备同步性,也就不具备原子性,

 

package com.it.po.thread07;

public class MyThread4_1 extends Thread{
    volatile public static int count;//多线程这里需要共享变量
    private static void addCount(){//用static修饰的count 方法必须是static
        for(int i=1;i<=100;i++){
            count++;
        }
        System.out.println("count= "+count);
    }
    @Override
    public void run() {
        super.run();
        addCount();
    }
}

 

 

package com.it.po.thread07;
public class Run4 {
    public static void main(String[] args){
      MyThread4_1[] arr=new  MyThread4_1[100];
     for(int i=0;i<100;i++){
         arr[i]=  new MyThread4_1();
     }
        for(int i=0;i<100;i++){
            arr[i].start();
        }
    }

}

 

 

 

 

优化如下

package com.it.po.thread07;

public class MyThread4_1 extends Thread{
    public static int count;//多线程这里需要共享变量
   synchronized private static void addCount(){
       //方法必须有static这样多线程锁的是类,实现同步效果
        for(int i=1;i<=100;i++){
            count++;
        }
        System.out.println("count= "+count);
    }
    @Override
    public void run() {
        super.run();
        addCount();
    }
}

 

 总结 :

volatile主要使用场合是在多线程中可以感知实例变量被更改了,而且可以获得最新的值使用。

(一)、图演示volatile非线程安全的原因(变量在内存中的工作过程)

 

 1)read和load阶段:从主内存变量到当前线程工作内存

2)use和assign阶段:执行代码,改变共享变量值

3)storeh和write阶段:用工作内存数据刷新主存对应变量的值

        多线程环境中,useh和assign是多次出现的,但是这一操作不熟原子性,

也就是read和load之后,如果主内存变量发生修改,线程工作内存的值由于已经

加载,不会发生对应变化,也就是私有内存和主内存不同步。

       对于volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存

的值是最新的,例如线程1和线程2在进行read和load的操作中,发现主内存中

count的值都是5,那么都会加载到5,所以volatile解决的是变量读取时的可见性问题。

(二)、使用原子类进行i++

      操作i++,除了使用  synchronized实现同步之外,还可以使用 AtomicInteger 原子类实现

     原子类是不能分割的整体,没有其他线程可以中断或检查正在原子操作的变量。

它可以在没有锁的情况下做到线程安全

 

package com.it.po.thread07;
import java.util.concurrent.atomic.AtomicInteger;
public class AddCountThread extends Thread {
private AtomicInteger atomicInteger =new AtomicInteger();
    @Override
    public void run() {
        super.run();
        for(int i=0;i<100;i++){
            System.out.println(atomicInteger.incrementAndGet());
        }

    }
}

 

 

 

 

 

package com.it.po.thread07;

public class Run5 {

    public static void main(String[] args){
        AddCountThread countThread = new AddCountThread();
        Thread t1 = new Thread(countThread);
        Thread t2 = new Thread(countThread);
        Thread t3 = new Thread(countThread);
        Thread t4 = new Thread(countThread);
        Thread t5 = new Thread(countThread);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }
}

 

 (三)、原子类并不完全安全

 

package com.it.po.thread07;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class MyService3 {
public static AtomicLong po =new AtomicLong();
   public  void addNum(){
   String name= Thread.currentThread().getName();
   System.out.println("线程 "+name+" 加了100之后: "+po.addAndGet(100));
   po.addAndGet(1);
   }

}

 

  

package com.it.po.thread07;
public class MyThread05 extends Thread {
private  MyService3 myService3;
    public MyThread05(MyService3 myService3) {
        this.myService3 = myService3;
    }
    @Override
    public void run() {
        super.run();
        myService3.addNum();
    }
}

 

package com.it.po.thread07;
public class Run6 {
    public static void main(String[] args) throws InterruptedException {
        MyService3 myService3 = new MyService3();
        MyThread05[] myThread05s=new MyThread05[5];
        for(int i=0;i<5;i++){
            myThread05s[i]= new MyThread05(myService3);
        }
        for(int i=0;i<5;i++){
            myThread05s[i].start();
        }
        Thread.sleep(4000);
        System.out.println("myService3= "+MyService3.po.get());
    }

}

 

线程 Thread-0 加了100之后: 100
线程 Thread-3 加了100之后: 200
线程 Thread-4 加了100之后: 302
线程 Thread-1 加了100之后: 402
线程 Thread-2 加了100之后: 504
myService3= 505

 

,修改

package com.it.po.thread07;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class MyService3 {
public static AtomicLong po =new AtomicLong();
   synchronized public  void addNum(){
   String name= Thread.currentThread().getName();
   System.out.println("线程 "+name+" 加了100之后: "+po.addAndGet(100));
   po.addAndGet(1);
   }

}

 

线程 Thread-2 加了100之后: 100
线程 Thread-1 加了100之后: 201
线程 Thread-0 加了100之后: 302
线程 Thread-4 加了100之后: 403
线程 Thread-3 加了100之后: 504
myService3= 505

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted on 2019-11-17 18:38  源无极  阅读(318)  评论(0)    收藏  举报