• Java多线程编程核心


    1、什么是“线程安全”与“非线程安全”?

    “非线程安全”会在多个线程对同一对象总的实例变量进行并发访问时发生,产生的后果是“脏读”,也就是取到的数据其实是被更改过的。

    “线程安全”是以获得的实例变量的值是经过同步处理的,不会出现脏读的现象。

    2、非线程安全例子?怎么解决?

    非线程安全

    package com.jvm.thread;

    public class HasSelfPrivateNum {

       private int num =  0;

       public void add(String username){

          try{

              if(username.equals("a")){

                 num = 100;

                 System.out.println("a set over!");

                 Thread.sleep(2000);

              }else{

                 num = 200;

                 System.out.println("b set over!");

              }

              System.out.println(username + " num = " + num);

          }catch(InterruptedException e){

              e.printStackTrace();

          }

       }

    }

    package com.jvm.thread;

    public class MyThreadA extends Thread {

       private HasSelfPrivateNum obj;

       public MyThreadA(HasSelfPrivateNum obj){

          this.obj = obj;

       }

      

       @Override

       public void run() {

          super.run();

          obj.add("a");

       }

    }

    package com.jvm.thread;

    public class MyThreadB extends Thread {

       private HasSelfPrivateNum obj;

       public MyThreadB(HasSelfPrivateNum obj){

          this.obj = obj;

       }

      

       @Override

       public void run() {

          super.run();

          obj.add("b");

       }

    }

    package com.jvm.thread;

    public class MyThread06 {

       public static void main(String[] args) {

          HasSelfPrivateNum obj = new HasSelfPrivateNum();

          MyThreadA a = new MyThreadA(obj);

          a.start();

          MyThreadB b = new MyThreadB(obj);

          b.start();

       }

    }

    a set over!

    b set over!

    b num = 200

    a num = 200

    解决方法:在 add() 前加关键字synchronized

    a set over!

    a num = 100

    b set over!

    b num = 200

    3、synchronized 关键字

    当A线程调用anyObject 对象加入synchronized关键字的X方法时,A线程就获得了X方法锁,更准确地讲,获得的是对象的锁,所以,其他线程必须等待A线程执行完毕才能调用X方法,但线程B可以随意调用其他的非synchronized同步方法。

    当A线程调用anyObject 对象加入synchronized关键字的X方法时,A线程就获得了X方法所在的对象的锁,所以其他线程必须等待A线程执行完毕才可以调用X方法,而B如果调用声明了synchronized关键字的非X方法时,必须等待A线程将X方法执行完,也就是释放对象锁后才可以调用。

    4、synchronized锁重入

    “可重入锁”:自己可以再次获得自己的内部锁。可重入锁也支持在父子继承的环境中。

    package com.jvm.thread;

    public class Service extends Thread {

       synchronized public void service1(){

          System.out.println("service1");

          service2();

       }

       synchronized public void service2(){

          System.out.println("service2");

          service3();

       }

       synchronized public void service3(){

          System.out.println("service3");

       }

      

       @Override

       public void run() {

          super.run();

          Service service = new Service();

          service.service1();

       }

      

       public static void main(String[] args) {

          Service service = new Service();

          service.start();

       }

    }

    service1

    service2

    service3

    5、锁自动释放——出现异常

    当一个线程执行的代码出现异常时,其所持有的锁会自动释放。

    6、synchronized同步方法的缺点?解决办法?

    Synchronized同步方法在某些情况下是有弊端的,比如A线程调用同步方法执行一个长时间的任务,那么B线程则必须等待比较长时间。

    在这样的情况下可以使用synchronized同步语句块来解决。

    7、synchronized同步语句块的使用?

    当两个并发线程访问同一个对象object中的synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另外一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。

    package com.jvm.thread;

    public class MyTask {

       public void taskMethod(){

          try {

              synchronized (this) {

                 System.out.println("begin time=" + System.currentTimeMillis());

                 Thread.sleep(2000);

                 System.out.println("end time=" + System.currentTimeMillis());

              }

          } catch (InterruptedException e) {

              e.printStackTrace();

          }

       }

    }

    package com.jvm.thread;

    public class MyThreadA extends Thread {

       private MyTask task;

       public MyThreadA(MyTask task){

          this.task = task;

       }

      

       @Override

       public void run() {

          task.taskMethod();

       }

    }

    package com.jvm.thread;

    public class MyThreadB extends Thread {

       private MyTask task;

       public MyThreadB(MyTask task){

          this.task = task;

       }

      

       @Override

       public void run() {

          task.taskMethod();

       }

    }

    package com.jvm.thread;

    public class Run {

       public static void main(String[] args) {

          MyTask task = new MyTask();

          MyThreadA a = new MyThreadA(task);

          a.setName("a");

          a.start();

          MyThreadB b = new MyThreadB(task);

          b.setName("b");

          b.start();

       }

    }

    begin time=1498358631251

    end time=1498358633252

    begin time=1498358633252

    end time=1498358635252

    8、synchronized代码块间的同步性?

    在使用不同synchronized(this)代码块时需要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的“对象监视器”是一个。

    9、将任意对象作为对象监视器?优点?

    使用synchronized(this)格式来同步代码块,其实Java还支持对“任意对象”作为“对象监视器”来实现同步的功能。这个“任意对象”大多数是实例变量及方法的参数,使用格式为synchronized(非this对象)。

    优点:如果在一个类中有很多个synchronized方法,这时虽然能实现同步,但是会受到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象,则synchronized(非this)代码块中程序与同步方法是异步的,不与其他锁this同步方法争抢this锁,则可大大提高效率。

    package com.jvm.thread;

    public class Service extends Thread {

       private String username;

       private String password;

       private String anyString = new String();

      

       public void setUsernamePassword(String username, String pwssword){

          try {

              synchronized (anyString) {

                 System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in to synchronized block");

                 username = username;

                 Thread.sleep(3000);

                 pwssword = pwssword;

                 System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave synchronized block");

              }

          } catch (InterruptedException e) {

              e.printStackTrace();

          }

       }

    }

    package com.jvm.thread;

    public class MyThreadA extends Thread {

       private Service service;

       public MyThreadA(Service service){

          this.service = service;

       }

      

       @Override

       public void run() {

          service.setUsernamePassword("a", "aa");

       }

    }

    package com.jvm.thread;

    public class MyThreadB extends Thread {

       private Service service;

       public MyThreadB(Service service){

          this.service = service;

       }

      

       @Override

       public void run() {

          service.setUsernamePassword("b", "bb");

       }

    }

    package com.jvm.thread;

    public class Run {

       public static void main(String[] args) {

          Service service = new Service();

          MyThreadA a = new MyThreadA(service);

          a.setName("A");

          a.start();

          MyThreadB b = new MyThreadB(service);

          b.setName("B");

          b.start();

       }

    }

    Thread name:A at 1498744309904 go in to synchronized block

    Thread name:A at 1498744312906 leave synchronized block

    Thread name:B at 1498744312906 go in to synchronized block

    Thread name:B at 1498744315906 leave synchronized block

    10、3个结论

    “synchronized(非this对象x)”格式的写法是将x对象本身作为“对象监视器”,这样就可以得出以下3个结论:

    1)当多个线程同时执行synchronized(x){}同步代码块时呈同步效果。

    2)当其他线程执行x对象中synchronized同步方法时呈同步效果。

    3)当其他线程执行x对象方法里面的synchronized(this)代码块时也呈同步效果。

    原因:使用同一个“对象监视器”。

    11、静态同步synchronized方法与synchronized(class)代码块

    关键字synchronized还可以应用在static静态方法上,如果这样写,那是对当前的 *.java文件对应的Class类进行持锁。

    package com.jvm.thread;

    public class Service extends Thread {

       synchronized public static void printA() {

          try {

              System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printA()");

              Thread.sleep(3000);

              System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printA()");

          } catch (InterruptedException e) {

              e.printStackTrace();

          }

       }

      

       synchronized public static void printB() {

              System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printB()");

              System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printB()");

       }

    }

    package com.jvm.thread;

    public class MyThreadA extends Thread {

       private Service service;

       public MyThreadA(Service service){

          this.service = service;

       }

      

       @Override

       public void run() {

          service.printA();

       }

    }

    package com.jvm.thread;

    public class MyThreadB extends Thread {

       private Service service;

       public MyThreadB(Service service){

          this.service = service;

       }

      

       @Override

       public void run() {

          service.printB();

       }

    }

    package com.jvm.thread;

    public class Run {

       public static void main(String[] args) {

          Service service = new Service();

          MyThreadA a = new MyThreadA(service);

          a.setName("A");

          a.start();

          MyThreadB b = new MyThreadB(service);

          b.setName("B");

          b.start();

       }

    }

    Thread name:A at 1498746369790 go in printA()

    Thread name:A at 1498746372791 leave printA()

    Thread name:B at 1498746372792 go in printB()

    Thread name:B at 1498746372792 leave printB()

    分析:从运行结果来看,和synchronized加到非static方法上使用效果一样。其实有本质上的不同,synchronized加到static静态方法上是给Class类加锁,而synchronized加到非static方法上是给对象加锁。

    package com.jvm.thread;

    public class Service extends Thread {

       synchronized public static void printA() {

          try {

              System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printA()");

              Thread.sleep(3000);

              System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printA()");

          } catch (InterruptedException e) {

              e.printStackTrace();

          }

       }

      

       synchronized public static void printB() {

              System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printB()");

              System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printB()");

       }

      

       synchronized public void printC() {

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printC()");

          System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printC()");

       }

    }

    package com.jvm.thread;

    public class MyThreadC extends Thread {

       private Service service;

       public MyThreadC(Service service){

          this.service = service;

       }

      

       @Override

       public void run() {

          service.printC();

       }

    }

    package com.jvm.thread;

    public class Run {

       public static void main(String[] args) {

          Service service = new Service();

          MyThreadA a = new MyThreadA(service);

          a.setName("A");

          a.start();

          MyThreadB b = new MyThreadB(service);

          b.setName("B");

          b.start();

          MyThreadC myThreadC = new MyThreadC(service);

          myThreadC.setName("C");

          myThreadC.start();

       }

    }

     

    Thread name:A at 1498746943314 go in printA()

    Thread name:C at 1498746943315 go in printC()

    Thread name:C at 1498746943315 leave printC()

    Thread name:A at 1498746946315 leave printA()

    Thread name:B at 1498746946315 go in printB()

    Thread name:B at 1498746946315 leave printB()

     

    分析:异步的原因是持有不同的锁,一个是对象锁,另外一个是Class锁,而Class锁可以对类的所有对象实例起作用。

    package com.jvm.thread;

    public class Service extends Thread {

       synchronized public static void printA() {

          try {

              System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printA()");

              Thread.sleep(3000);

              System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printA()");

          } catch (InterruptedException e) {

              e.printStackTrace();

          }

       }

      

       synchronized public static void printB() {

              System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " go in printB()");

              System.out.println("Thread name:" + Thread.currentThread().getName() + " at " + System.currentTimeMillis() + " leave printB()");

       }

    }

    package com.jvm.thread;

    public class Run {

       public static void main(String[] args) {

          Service service1 = new Service();

          Service service2 = new Service();

          MyThreadA a = new MyThreadA(service1);

          a.setName("A");

          a.start();

          MyThreadB b = new MyThreadB(service2);

          b.setName("B");

          b.start();

       }

    }

    Thread name:A at 1498747202057 go in printA()

    Thread name:A at 1498747205057 leave printA()

    Thread name:B at 1498747205057 go in printB()

    Thread name:B at 1498747205057 leave printB()

    同步代码块synchronized(class)代码块的作用和synchronized static方法的作用一样。

    12、数据类型String的常量池特性

    在JVM中具有String常量池缓冲的功能

    package com.jvm.thread;

    public class Test {

       public static void main(String[] args) {

          String a = "a";

          String b = "a";

          System.out.println(a == b); //true

       }

    }

    将synchronized(string)同步块与String联合使用时,要注意常量池带来的一些例外。

    package com.jvm.thread;

    public class Service extends Thread {

       public static void print(String str) {

          try {

              synchronized (str) {

                 while(true){

                    System.out.println(Thread.currentThread().getName());

                    Thread.sleep(1000);

                 }

              }

          } catch (InterruptedException e) {

              e.printStackTrace();

          }

       }

    }

    package com.jvm.thread;

    public class MyThreadA extends Thread {

       private Service service;

       public MyThreadA(Service service){

          this.service = service;

       }

      

       @Override

       public void run() {

          service.print("AA");

       }

    }

    package com.jvm.thread;

    public class MyThreadB extends Thread {

       private Service service;

       public MyThreadB(Service service){

          this.service = service;

       }

      

       @Override

       public void run() {

          service.print("AA");

       }

    }

    package com.jvm.thread;

    public class Run {

       public static void main(String[] args) {

          Service service = new Service();

          MyThreadA a = new MyThreadA(service);

          a.setName("A");

          a.start();

          MyThreadB b = new MyThreadB(service);

          b.setName("B");

          b.start();

       }

    }

    A

    A

    A

    A

    A

    A

    A

    分析:死循环,原因是两个值都是AA两个线程持有相同的锁,所以造成线程B不能执行。这就是String常量池所带来的问题。因此,在大多数情况下,同步synchronized代码块都不适用String作为锁对象,而改用其他,比如 new Object()实例化一个Oject对象。

    13、同步synchronized方法无限等待与解决

    package com.jvm.thread;

    public class Service extends Thread {

       synchronized public void methodA() {

          System.out.println("methodA begin");

          boolean isContinueRun = true;

          while (isContinueRun) {

          }

          System.out.println("methodA end");

       }

       synchronized public void methodB() {

          System.out.println("methodB begin");

          System.out.println("methodB end");

       }

    }

    package com.jvm.thread;

    public class MyThreadA extends Thread {

       private Service service;

       public MyThreadA(Service service){

          this.service = service;

       }

      

       @Override

       public void run() {

          service.methodA();

       }

    }

    package com.jvm.thread;

    public class MyThreadB extends Thread {

       private Service service;

       public MyThreadB(Service service){

          this.service = service;

       }

      

       @Override

       public void run() {

          service.methodB();

       }

    }

    package com.jvm.thread;

    public class Run {

       public static void main(String[] args) {

          Service service = new Service();

          MyThreadA a = new MyThreadA(service);

          a.start();

          MyThreadB b = new MyThreadB(service);

          b.start();

       }

    }

    methodA begin

    分析:线程A不释放锁,线程B永远得不到运行的机会,锁死了。

    解决:同步代码块

    package com.jvm.thread;

    public class Service extends Thread {

       Object object1 = new Object();

       public void methodA() {

          synchronized (object1) {

              System.out.println("methodA begin");

              boolean isContinueRun = true;

              while (isContinueRun) {

                

              }

              System.out.println("methodA end");

          }

       }

       Object object2 = new Object();

       public void methodB() {

          synchronized (object2) {

              System.out.println("methodB begin");

              System.out.println("methodB end");

          }

       }

    }

    methodA begin

    methodB begin

    methodB end

    14、多线程的死锁

    Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待根本不可能释放的锁,从而导致所有的任务都无法继续完成。在多线程技术中心,“死锁”是必需避免的,因为这会造成线程的“假死”。

    死锁例子:

    package com.jvm.thread;

    public class DeadThread implements Runnable {

       public String username;

       public Object lock1 = new Object();

       public Object lock2 = new Object();

      

       public void setFlag(String username){

          this.username = username;

       }

      

       @Override

       public void run() {

          if(username.equals("a")){

              synchronized (lock1) {

                 try {

                    System.out.println("username = " + username);

                    Thread.sleep(3000);

                 } catch (InterruptedException e) {

                    e.printStackTrace();

                 }

                 synchronized (lock2) {

                    System.out.println("lock1 -> lock2");

                 }

              }

          }

         

          if(username.equals("b")){

              synchronized (lock2) {

                 try {

                    System.out.println("username = " + username);

                    Thread.sleep(3000);

                 } catch (InterruptedException e) {

                    e.printStackTrace();

                 }

                 synchronized (lock1) {

                    System.out.println("lock2 -> lock1");

                 }

              }

          }

       }

      

       public static void main(String[] args) throws InterruptedException {

          DeadThread deadThread = new DeadThread();

          deadThread.setFlag("a");

          Thread thread1 = new Thread(deadThread);

          thread1.start();

          Thread.sleep(1000);

         

          deadThread.setFlag("b");

          Thread thread2 = new Thread(deadThread);

          thread2.start();

       }

    }

    username = a

    username = b

    注意:死锁的实现与嵌套不嵌套没有关系。

    1/ valotile 关键字的作用是什么?缺点是什么?

    使变量在多个线程间可见。但valotile关键字最致命的缺点是不支持原子性。

    package com.jvm.thread;

    public class PrintString {

           private boolean isContinuePrint = true;

          

           public void setContinuePrint(boolean isContinuePrint) {

                  this.isContinuePrint = isContinuePrint;

           }

          

           public boolean isContinuePrint() {

                  return isContinuePrint;

           }

          

           public void printStringMethod(){

                  try {

                         while (isContinuePrint) {

                               System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName());

                               Thread.sleep(1000);

                         }

                  } catch (InterruptedException e) {

                         e.printStackTrace();

                  }

           }

          

           public static void main(String[] args) {

                  PrintString printString = new PrintString();

                  printString.printStringMethod();

                  System.out.println("I will stop it! stopThread=" + Thread.currentThread().getName());

                  printString.setContinuePrint(false);

           }

    }

    run printStringMethod threadName=main

    run printStringMethod threadName=main

    run printStringMethod threadName=main

    run printStringMethod threadName=main

    run printStringMethod threadName=main

    run printStringMethod threadName=main

    run printStringMethod threadName=main

    run printStringMethod threadName=main

    run printStringMethod threadName=main

    run printStringMethod threadName=main

    分析:main线程一直在处理while循环,没办法执行后面的代码。

    解决:使用多线程

    package com.jvm.thread;

    public class PrintString implements Runnable {

           private boolean isContinuePrint = true;

          

           public void setContinuePrint(boolean isContinuePrint) {

                  this.isContinuePrint = isContinuePrint;

           }

          

           public boolean isContinuePrint() {

                  return isContinuePrint;

           }

          

           public void printStringMethod(){

                  try {

                         while (isContinuePrint) {

                               System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName());

                               Thread.sleep(1000);

                         }

                  } catch (InterruptedException e) {

                         e.printStackTrace();

                  }

           }

          

           @Override

           public void run() {

                  printStringMethod();

           }

          

           public static void main(String[] args) {

                  PrintString printString = new PrintString();

                  new Thread(printString).start();

                  System.out.println("I will stop it! stopThread=" + Thread.currentThread().getName());

                  printString.setContinuePrint(false);

           }

    }

    I will stop it! stopThread=main

    run printStringMethod threadName=Thread-0

     

    package com.jvm.thread;

    public class PrintString implements Runnable {

           volatile private boolean isContinuePrint = true;

          

           public void setContinuePrint(boolean isContinuePrint) {

                  this.isContinuePrint = isContinuePrint;

           }

          

           public boolean isContinuePrint() {

                  return isContinuePrint;

           }

          

           public void printStringMethod(){

                  try {

                         while (isContinuePrint) {

                               System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName());

                               Thread.sleep(1000);

                         }

                  } catch (InterruptedException e) {

                         e.printStackTrace();

                  }

           }

          

           @Override

           public void run() {

                  printStringMethod();

           }

          

           public static void main(String[] args) {

                  PrintString printString = new PrintString();

                  new Thread(printString).start();

                  System.out.println("I will stop it! stopThread=" + Thread.currentThread().getName());

                  printString.setContinuePrint(false);

           }

    }

    I will stop it! stopThread=main

    run printStringMethod threadName=Thread-0

    使用volatile关键字,强制从公共内存中读取变量的值。

     

    2/ 线程安全包含哪些方面?

    原子性和可见性。Java的同步机制都是围绕这两个方面来确保线程安全的。

    3/关键字synchronized和valotile比较?

    a/关键字valotile是线程同步的轻量级实现,因此valotile性能更好。Valotile只能修饰变量,synchronized修饰方法和代码块。

    b/多线程访问valotile不会发生阻塞,而synchronized会。

    c/valotile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。

    d/valotile解决的是变量在多个线程之间的可见性,而synchronized解决的是多个线程之间访问资源的同步性。

    3/valotile非原子性?怎么解决?

    package com.jvm.thread;

    public class MyThread extends Thread {

           volatile public static int count;

           private static void addCount(){

                  for(int i = 0; i < 100; i++){

                         count++;

                  }

                  System.out.println("count=" + count);

           }

          

           @Override

           public void run() {

                  addCount();

           }

          

           public static void main(String[] args) {

                  MyThread[] myThreadArr = new MyThread[100];

                  for(int i = 0; i < 100; i++){

                         myThreadArr[i] = new MyThread();

                  }

                  for(int i = 0; i < 100; i++){

                         myThreadArr[i].start();

                  }

           }

    }

    count=100

    count=300

    count=200

    count=400

    count=600

    count=600

    count=700

    count=800

    count=900

    count=1000

    count=1100

    count=1200

    count=1400

    count=1300

    count=1500

    count=1600

    count=1700

    count=1800

    count=2000

    count=1900

    count=2200

    count=2300

    count=2200

    count=2400

    count=2500

    count=2600

    count=2900

    count=2800

    count=2700

    count=3000

    count=3100

    count=3200

    count=3300

    count=3400

    count=3600

    count=3700

    count=3500

    count=4100

    count=4000

    count=3900

    count=4300

    count=3800

    count=4400

    count=4600

    count=4200

    count=4500

    count=4800

    count=4700

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6605

    count=6705

    count=6805

    count=7005

    count=7005

    count=7205

    count=7305

    count=7405

    count=7105

    count=7505

    count=7605

    count=7705

    count=7805

    count=7905

    count=8005

    count=8105

    count=8205

    count=8305

    count=8405

    count=8505

    count=8605

    count=8705

    count=8805

    count=8905

    count=9005

    count=9105

    count=9205

    count=9305

    count=9405

    count=9505

    count=9605

    count=9705

    count=9805

    count=9905

    分析:用图来演示使用关键字valotile时出现非线程安全的原因。

    a/ read和load阶段:从主内存复制变量到当前线程工作内存;

    b/ use和assign阶段:执行代码,改变共享变量值;

    c/ store和write阶段:用工作内存数据刷新主存对应变量的值。

    在多线程环境中,use和assign是多次出现的,但这一操作并不是原子性,也就是在read和assign之后,如果主内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,导致私有内存和公共内存中的变量不同步,因此,计算出来的结果和预期不一样,也就出现了非线程安全问题。

     

    解决:valotile关键字解决的是变量读取时的可见性问题,但无法保证原子性,因此,对于多个线程访问同一实例变量还是需要加锁同步。

    package com.jvm.thread;

    public class MyThread extends Thread {

           public static int count;

           private synchronized static void addCount(){

                  for(int i = 0; i < 100; i++){

                         count++;

                  }

                  System.out.println("count=" + count);

           }

          

           @Override

           public void run() {

                  addCount();

           }

          

           public static void main(String[] args) {

                  MyThread[] myThreadArr = new MyThread[100];

                  for(int i = 0; i < 100; i++){

                         myThreadArr[i] = new MyThread();

                  }

                  for(int i = 0; i < 100; i++){

                         myThreadArr[i].start();

                  }

           }

    }

    count=100

    count=200

    count=300

    count=400

    count=500

    count=600

    count=700

    count=800

    count=900

    count=1000

    count=1100

    count=1200

    count=1300

    count=1400

    count=1500

    count=1600

    count=1700

    count=1800

    count=1900

    count=2000

    count=2100

    count=2200

    count=2300

    count=2400

    count=2500

    count=2600

    count=2700

    count=2800

    count=2900

    count=3000

    count=3100

    count=3200

    count=3300

    count=3400

    count=3500

    count=3600

    count=3700

    count=3800

    count=3900

    count=4000

    count=4100

    count=4200

    count=4300

    count=4400

    count=4500

    count=4600

    count=4700

    count=4800

    count=4900

    count=5000

    count=5100

    count=5200

    count=5300

    count=5400

    count=5500

    count=5600

    count=5700

    count=5800

    count=5900

    count=6000

    count=6100

    count=6200

    count=6300

    count=6400

    count=6500

    count=6600

    count=6700

    count=6800

    count=6900

    count=7000

    count=7100

    count=7200

    count=7300

    count=7400

    count=7500

    count=7600

    count=7700

    count=7800

    count=7900

    count=8000

    count=8100

    count=8200

    count=8300

    count=8400

    count=8500

    count=8600

    count=8700

    count=8800

    count=8900

    count=9000

    count=9100

    count=9200

    count=9300

    count=9400

    count=9500

    count=9600

    count=9700

    count=9800

    count=9900

    count=10000

    4/验证synchronized具有将线程工作内存的私有变量与公共内存中的变量同步的功能?

    关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或者代码快。它包含两个特性:互斥性和可见性。同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,都能看到由同一个锁保护之前所有的修改效果。

    package com.jvm.thread;

    public class Service {

           private boolean isCoutinueRun = true;

          

           public void runMethod(){

                  while(isCoutinueRun){

                        

                  }

                  System.out.println("have stoped!");

           }

          

           public void stopMethod(){

                  isCoutinueRun = false;

           }

    }

    package com.jvm.thread;

    public class ThreadA extends Thread{

           private Service service;

           public ThreadA(Service service) {

                  this.service = service;

           }

           @Override

           public void run() {

                  service.runMethod();

           }

    }

    package com.jvm.thread;

    public class ThreadB extends Thread{

           private Service service;

           public ThreadB(Service service) {

                  this.service = service;

           }

           @Override

           public void run() {

                  service.stopMethod();

           }

    }

    package com.jvm.thread;

    public class Run {

           public static void main(String[] args) throws InterruptedException {

                  Service service = new Service();

                  ThreadA threadA = new ThreadA(service);

                  threadA.start();

                 

                  Thread.sleep(1000);

                 

                  ThreadB threadB = new ThreadB(service);

                  threadB.start();

                  System.out.println("have start stop commad");

           }

    }

    have start stop commad

    分析:出现死循环,各线程间的数据值没有可视性造成的

    解决:synchronized可以具有可视性

    package com.jvm.thread;

    public class Service {

           private boolean isCoutinueRun = true;

          

           public void runMethod(){

                  String anyString = new String();

                  while(isCoutinueRun){

                         synchronized (anyString) {

                              

                         }

                  }

                  System.out.println("have stoped!");

           }

          

           public void stopMethod(){

                  isCoutinueRun = false;

           }

    }

    5/总结?

    着重“外练互斥,内修可见”,是掌握多线程并发的重要技术。

  • 相关阅读:
    算法经典文章收藏
    Python 学习文章收藏
    Leetcode 刷题计划
    CLR via C# 学习计划
    算法导论 学习计划
    算法导论学习笔记 一 分治算法
    Mongodb 学习笔记
    Python模拟HttpRequest的方法总结
    在Github上搭建自己的博客(Windows平台)
    Git Shell 基本命令(官网脱水版)
  • 原文地址:https://www.cnblogs.com/skorzeny/p/7094427.html
Copyright © 2020-2023  润新知