【Java多线程编程核心技术】2.对象及变量的并发访问(下)-笔记总结

相关链接:
【Java多线程编程核心技术】1.Java多线程技能-笔记总结
【Java多线程编程核心技术】2.对象及变量的并发访问(上)-笔记总结
【Java多线程编程核心技术】2.对象及变量的并发访问(下)-笔记总结
【Java多线程编程核心技术】3.线程间通信 -笔记总结
【Java多线程编程核心技术】4.Lock的使用-笔记总结
【Java多线程编程核心技术】5.定时器Timer-笔记总结
【Java多线程编程核心技术】6.单例模式与多线程-笔记总结
【Java多线程编程核心技术】7.拾遗增补-笔记总结

synchronized同步语句块

数据类型String的常量池特性

在JVM中具有String常量池缓存的功能,将synchronized(String )同步块与String联合使用时,需要注意常量池带来的一些例外。
当用传入的参数对象当做对象锁时,传入的字符串如果是一样的,则视为是同一个对象//常量池特性,详细过程百度,后面做《深入了解Java虚拟机》笔记总结时也会更详细的提出来
所以通常情况,不建议使用String作为锁对象。

同步synchronized方法无线等待与解决

package service;
public class Service {
     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");
     }
}

同步代码容易造成死循环,例如代码中methodA()存在死循环,导致methodB也无法被其他线程访问

package service;
public class Service {
     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与methodB 锁对象不同

多线程的死锁

Java线程时一个经典的多线程问题,因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续完成,造成线程的“假死”。

package test;
public class DealThread 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) {
                        // TODO Auto-generated catch block
                        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) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                   }
                   synchronized (lock1) {
                        System.out.println("按lock2->lock1代码顺序执行了");
                   }
              }
          }
     }
}
package test;
public class Run {
     public static void main(String[] args) {
          try {
              DealThread t1 = new DealThread();
              t1.setFlag("a");
              Thread thread1 = new Thread(t1);
              thread1.start();
              Thread.sleep(100);
              t1.setFlag("b");
              Thread thread2 = new Thread(t1);
              thread2.start();
          } catch (InterruptedException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
          }
     }

}
输出结果:
username = a
username = b

通过JDK自带工具检测死锁现象。
【Java多线程编程核心技术】2.对象及变量的并发访问(下)-笔记总结_第1张图片
【Java多线程编程核心技术】2.对象及变量的并发访问(下)-笔记总结_第2张图片
死锁是程序设计的Bug,在设计时应避免双方互相持有对方锁的情况
只要互相等待对方释放锁就有可能出现死锁。

内置类与静态内置类

package test;
public class PublicClass {
     private String username;
     private String password;
     class PrivateClass {//内置类
          private String age;
          private String address;
          public String getAge() {
              return age;
          }
          public void setAge(String age) {
              this.age = age;
          }
          public String getAddress() {
              return address;
          }
          public void setAddress(String address) {
              this.address = address;
          }
          public void printPublicProperty() {
              System.out.println(username + " " + password);
          }
     }
     public String getUsername() {
          return username;
     }
     public void setUsername(String username) {
          this.username = username;
     }
     public String getPassword() {
          return password;
     }
     public void setPassword(String password) {
          this.password = password;
     }
}
public class Run {
     public static void main(String[] args) {
          PublicClass publicClass = new PublicClass();
          publicClass.setUsername("usernameValue");
          publicClass.setPassword("passwordValue");
          System.out.println(publicClass.getUsername() + " "
                   + publicClass.getPassword());
          PrivateClass privateClass = publicClass.new PrivateClass();
          privateClass.setAge("ageValue");
          privateClass.setAddress("addressValue");
          System.out.println(privateClass.getAge() + " "
                   + privateClass.getAddress());
     }

}
     static class PrivateClass { //静态内置类:
           ......
    }
PublicClass publicClass = new PublicClass();
          publicClass.setUsername("usernameValue");
          publicClass.setPassword("passwordValue");
          System.out.println(publicClass.getUsername() + " "
                   + publicClass.getPassword());
          PrivateClass privateClass = new PrivateClass();  //两者创建时方式不同
          privateClass.setAge("ageValue");

          privateClass.setAddress("addressValue");

内置类与同步

package test;
public class OutClass {
     static class Inner {
          public void method1() {
              synchronized ("其它的锁") {
                   for (int i = 1; i <= 10; i++) {
                        System.out.println(Thread.currentThread().getName() + " i="
                                  + i);
                        try {
                             Thread.sleep(100);
                        } catch (InterruptedException e) {
                        }
                   }
              }
          }
          public synchronized void method2() {
              for (int i = 11; i <= 20; i++) {
                   System.out
                             .println(Thread.currentThread().getName() + " i=" + i);
                   try {
                        Thread.sleep(100);
                   } catch (InterruptedException e) {
                   }
              }
          }
     }
}

由于持有不同的“对象监视器”,使用的锁不同,所以是异步执行。

package test;
public class OutClass {
     static class InnerClass1 {
          public void method1(InnerClass2 class2) {
              String threadName = Thread.currentThread().getName();
              synchronized (class2) {
                   System.out.println(threadName + " 进入InnerClass1类中的method1方法");
                   for (int i = 0; i < 10; i++) {
                        System.out.println("i=" + i);
                        try {
                             Thread.sleep(100);
                        } catch (InterruptedException e) {
                        }
                   }
                   System.out.println(threadName + " 离开InnerClass1类中的method1方法");
              }
          }
          public synchronized void method2() {
              String threadName = Thread.currentThread().getName();
              System.out.println(threadName + " 进入InnerClass1类中的method2方法");
              for (int j = 0; j < 10; j++) {
                   System.out.println("j=" + j);
                   try {
                        Thread.sleep(100);
                   } catch (InterruptedException e) {
                   }
              }
              System.out.println(threadName + " 离开InnerClass1类中的method2方法");
          }
     }
     static class InnerClass2 {
          public synchronized void method1() {
              String threadName = Thread.currentThread().getName();
              System.out.println(threadName + " 进入InnerClass2类中的method1方法");
              for (int k = 0; k < 10; k++) {
                   System.out.println("k=" + k);
                   try {
                        Thread.sleep(100);
                   } catch (InterruptedException e) {
                   }
              }
              System.out.println(threadName + " 离开InnerClass2类中的method1方法");
          }
     }
}
InnerClass1 里的method1里的同步代码块获取的对象锁 与InnerClass2 method1 需要获取的锁 相同,所以 两者需要同步执行,而InnerClass1里的method2 获取的锁与他俩不同,所以与他俩呈异步执行。


锁对象的改变
------


package myservice;
public class MyService {
     private String lock = "123";
     public void testMethod() {
          try {
              synchronized (lock) {
                   System.out.println(Thread.currentThread().getName() + " begin "
                             + System.currentTimeMillis());
                   lock = "456";
                   Thread.sleep(2000);
                   System.out.println(Thread.currentThread().getName() + "   end "
                             + System.currentTimeMillis());
              }
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
     }
}
package extthread;
import myservice.MyService;
public class ThreadA extends Thread {
     private MyService service;
     public ThreadA(MyService service) {
          super();
          this.service = service;
     }
     @Override
     public void run() {
          service.testMethod();
     }

}
package extthread;
import myservice.MyService;
public class ThreadB extends Thread {
     private MyService service;
     public ThreadB(MyService service) {
          super();
          this.service = service;
     }
     @Override
     public void run() {
          service.testMethod();
     }

}
、package test.run;
import myservice.MyService;
import extthread.ThreadA;
import extthread.ThreadB;
public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        ThreadB b = new ThreadB(service);
        b.setName("B");
        a.start();
        Thread.sleep(50);// 存在50毫秒
        b.start();
    }
}

输出结果(异步):
A begin 1510985942456
B begin 1510985942518
A   end 1510985944468
B   end 1510985944532

当注释掉main方法里的等待50毫秒时,输出结果(同步):
A begin 1510985859730
A   end 1510985861731
B begin 1510985861731
B   end 1510985863747

1.异步的原因是因为在main线程等待50毫秒的期间,线程A已经完成了 lock=“456”,对象改变,以至于两个线程获取的对象锁也就不再相同。
2.注释掉后50毫秒同步的原因:main线程直接调用了b.start(),而此时线程A并未完成对lock的赋值改变,所以线程A与线程B此时都是争取的同一个对象锁。

注意:只要对象不变,即使对象的属性被改变,对象锁也不会发生改变。例如对XXX.setXXX()操作,并不会产生影响

volatile关键字

关键字volatile的主要作用:使变量在多线程间可见。

解决异步死循环

package extthread;
public class RunThread extends Thread {

//   volatile private boolean isRunning = true;
     private boolean isRunning = true;
     public boolean isRunning() {
          return isRunning;
     }
     public void setRunning(boolean isRunning) {
          this.isRunning = isRunning;
     }
     @Override
     public void run() {
          System.out.println("进入run了");
          while (isRunning == true) {
          }
          System.out.println("线程被停止了!");
     }
}
package test;
import extthread.RunThread;
public class Run {
     public static void main(String[] args) {
          try {
              RunThread thread = new RunThread();
              thread.start();
              Thread.sleep(1000);
              v
              System.out.println("已经赋值为false");
          } catch (InterruptedException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
          }
     }

}
输出结果:
进入run了
已经赋值为false
线程被停止了!
-server服务器模式中64bit的JVM,输出结果:
进入run了
已经赋值为false
-server服务器模式中64bit的JVM,但加上volatile关键字后,输出结果:
进入run了
已经赋值为false
线程被停止了!

在-server服务器模式中64bit的JVM上时,会出现死循环。
【Java多线程编程核心技术】2.对象及变量的并发访问(下)-笔记总结_第3张图片
原因:在JVM被设置为-server模式时,为了线程运行的效率,线程一直在私有堆栈中取得isRunning的值是true。而代码thread.setRunning(false);虽然被执行,更新的却是公共堆栈中的isRunning变量值false,所以一直就是死循环的状态。
加入volatile关键字后,使线程访问isRunning这个变量时,强制性从公共堆栈中进行取值

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

使用volatile关键字增加了实例变量在多线程之间的可见性,但其最致命的缺点是不支持原子性!!!

关键字synchronized与关键字volatile关键字进行比较:
1.关键字volatile是线程同步的轻量级实现,性能肯定比synchronized要好,volatile修饰于变量,synchronized可以修饰方法,随着jdk新版本的发布,synchronized在执行效率得到了很大提升,在开发使用比率还是比较大。
2.多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
3.volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,也可以间接保证可见性,因为他会将私有内存和功能内存中的数据进行同步。(后面会证明这一点)
4.关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的是多个线程之间访问资源的同步性。

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

volatile非原子的特性

package extthread;
public class MyThread extends Thread {
     volatile public static int count;
//   synchronized private static void addCount() {
     private static void addCount() {  
          for (int i = 0; i < 100; i++) {
              count++;
          }
          System.out.println("count=" + count);
     }
     @Override
     public void run() {
          addCount();
     }
}
package test.run;
import extthread.MyThread;
public class Run {
     public static void main(String[] args) {
          MyThread[] mythreadArray = new MyThread[100];
          for (int i = 0; i < 100; i++) {
              mythreadArray[i] = new MyThread();
          }
          for (int i = 0; i < 100; i++) {
              mythreadArray[i].start();
          }
     }

}
输出结果:(较新版本的jdk能无序得计算出count=10000)
.......
count=9930
count=9930
count=9930
count=9930
.......

加入synchronized关键字后:
......
count=9400
count=9500
count=9600
count=9700
count=9800
count=9900
count=10000

在加入synchronize关键字后,也就没必要再使用volatile关键字来申明count变量了。
关键字volatile关键字主要使用的场合:在多个线程中可以感知实例变量被更改了,并且可以获得最新的值使用,也就是用多线程读取共享变量时可以获取最新值使用
volatile本身并不处理数据的原子性,而是强制对数据的读写及时影响到主内存的。
在修改实例变量中的数据,比如i++等这类非原子操作的操作时,容易发生非线程安全(i++可以分为三步。1.从内存取出i值;2.计算i值;3.将i的值写到内存中)
volatile关键字解决的是变量读时的可见性问题,但无法保证原子性,对于多个线程访问同一实例时还需要加锁同步。

使用原子类进行i++操作

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

package extthread;
import java.util.concurrent.atomic.AtomicInteger;
public class AddCountThread extends Thread {
     private AtomicInteger count = new AtomicInteger(0);
     @Override
     public void run() {
          for (int i = 0; i < 10000; i++) {
               System.out.println(count.incrementAndGet());
          }
     }
}

原子类也并不完全安全

原子类在具有逻辑性的情况下输出结果也具有随机性

package extthread;
import service.MyService;
public class MyThread extends Thread {
     private MyService mySerivce;
     public MyThread(MyService mySerivce) {
          super();
          this.mySerivce = mySerivce;
     }
     @Override
     public void run() {
          mySerivce.addNum();
     }
}
package service;
import java.util.concurrent.atomic.AtomicLong;
public class MyService {
     public static AtomicLong aiRef = new AtomicLong();
//   synchronized public void addNum() {
     public void addNum() {
          System.out.println(Thread.currentThread().getName() + "加了100之后的值是:"
                   + aiRef.addAndGet(100));
          aiRef.addAndGet(1);
     }

}
package test.run;
import service.MyService;
import extthread.MyThread;
public class Run {
    public static void main(String[] args) {
        try {
            MyService service = new MyService();
            MyThread[] array = new MyThread[5];
            for (int i = 0; i < array.length; i++) {
                array[i] = new MyThread(service);
            }
            for (int i = 0; i < array.length; i++) {
                array[i].start();
            }
            Thread.sleep(1000);
            System.out.println(service.aiRef.get());
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
未+关键字synchronized前,输出结果:
Thread-0加了100之后的值是:100
Thread-4加了100之后的值是:500
Thread-2加了100之后的值是:400
Thread-3加了100之后的值是:300
Thread-1加了100之后的值是:200
505
加入后,输出结果:
Thread-0加了100之后的值是:100
Thread-2加了100之后的值是:201
Thread-3加了100之后的值是:302
Thread-1加了100之后的值是:403
Thread-4加了100之后的值是:504
505

造成前者打印顺序乱序得原因:addAndGet()方法是原子的,但方法和方法之间的调用却不是原子的。(但最后输出的总结果是正确的!)

synchronized代码块有volatile同步的功能

关键字synchronized可以使多个线程访问同一个资源具有同步性,而且它还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能。

public class Service {
     private boolean isContinueRun = true;
     public void runMethod() {
          String anyString = new String();
          while (isContinueRun == true) {
              synchronized (anyString) {
              }
          }
          System.out.println("停下来了!");
     }
     public void stopMethod() {
          isContinueRun = false;
     }
}

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

“外练互斥,内修可见!!!”

你可能感兴趣的:(Java多线程,Java多线程编程核心技术)