读书笔记——《Java多线程编程核心技术》第二章

synchronized同步方法

  1. 方法内的变量线程安全,不需要做同步处理,每个线程持有一个该变量;
  2. 实例变量非线程安全;
  3. 多个对象多个锁;
  4. 同步不具有继承性;
    父类方法为同步方法,子类继承父类该方法,如果不声明为同步方法,那么子类方法为非同步方法。
  5. synchronized锁可重入,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁;
    (1). 父子类继承

    class Parent{

    synchronized public void method1(){
    
    }
    

    }

    class Child{

    synchronized public void method1(){
    
        super.method1();
    }
    

    }

当我们调用child的method1()时,可以进入Parent的method1(),如果不支持可重入,那么会陷入死锁。
(2). 同一个类不同synchronized方法之间的调用。

  1. 在同步方法中出现异常时,其所持有的锁会自动释放。
  2. synchronized同步方法

(1).对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态;
(2).同一时间只有一个线程可以执行synchronized同步方法中的代码。

synchronized同步语句块

同步代码块可以解决同步方法的一些问题,我们可以将比较耗时且不需要同步的代码放到同步代码块外面,来提高效率。

synchronized(this)同步代码块
(1).对其他synchronized同步方法或synchronized(this)同步代码块调用呈阻塞状态。
(2).同一时间只有一个线程可以执行synchronized(this)同步代码块中的代码。

  1. String的常量池特性

    public class Service8 {

    public static void print(String stringParam){
    
        try{
            synchronized (stringParam) {
                while(true){
                    System.out.println(Thread.currentThread().getName());
                    Thread.sleep(1000);
                }
            }
        }catch(InterruptedException e){
    
        }
    }
    

    }

    下面有两个线程A,B执行上面的任务。
    线程A:
    public class ThreadA20 extends Thread {

    private Service8 service;
    
    public ThreadA20(Service8 service){
        super();
        this.service = service;
    }
    
    @Override
    public void run(){
        service.print("AA");
    }
    

    }
    线程B:
    public class ThreadB20 extends Thread {

    private Service8 service;
    
    public ThreadB20(Service8 service){
        super();
        this.service = service;
    }
    
    @Override
    public void run(){
        service.print("AA");
    }
    

    }
    在主线程里面同时开始两个任务,就会发现只执行线程A,原因是因为String常量池的原因,导致A,B持有的锁相同,所以B被阻塞。

  2. 锁对象的改变
    public class MyService1 {

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

    }
    }

    我们启动线程A,B会发现异步执行,因为锁对象发生了改变。如果锁为一个对象,对象的属性改变,多线程依旧会同步执行。

  3. 在多线程中容易造成死锁,可以用JDK自带的工具来监测是否有死锁的现象。进入jdk的安装文件夹中的bin目录,执行jps命令。

jps命令

再运行jstack命令查看结果。jstack -l 3244

检测结果

volatile关键字

volatile的主要作用是使变量在多个线程间可见,强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。

主内存与线程私有内存

而用了volatile后变量的读取与写入方式变化为:

volatile工作方式

volatile与synchronized的区别如下:
  1. volatile是线程同步的轻量级实现,所以volatile性能比synchronized要好。volatile只能作用于变量,而synchronized可以作用于方法,代码块。
  2. 多线程访问volatile不会发生阻塞,但是synchronized会出现阻塞。
  3. volatile能保证数据的可见性,但是不能保证原子性。synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存的数据做同步。

    public class MyThread2 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();
    }
    

    }

    然后我们启动100个线程:

    public class Run37 {

    public static void main(String[] args) {
    
        MyThread2[] myThreadArray = new MyThread2[100];
        for(int i = 0; i < 100; i++){
            myThreadArray[i] = new MyThread2();
        }
    
        for(int i = 0; i < 100; i++){
            myThreadArray[i].start();
        }
    }
    

    }

    运行结果如下:

    volatile不保证同步

    并不是预期的10000.所以说明volatile不能保证同步,只能保证可见性。

变量在内存中的工作过程

  1. 使用原子类进行i++操作(AtomicInteger)。
  2. synchronized代码块有volatile同步的功能

    public class Service {

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

    }

    线程A,B如下:
    public class ThreadA extends Thread {

    private Service service;
    
    public ThreadA(Service service){
        super();
        this.service = service;
    }
    
    @Override
    public void run(){
        service.runMethod();
    }
    

    }

    在线程B中,我们运行stopMethod方法。

    public class ThreadB extends Thread {

    private Service service;
    
    public ThreadB(Service service){
        super();
        this.service = service;
    }
    
    @Override
    public void run(){
        service.stopMethod();
    }
    

    }

    public class Run {

    public static void main(String[] args) {
    
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    
        ThreadB b = new ThreadB(service);
        b.start();
        System.out.println("已经发起停止的命令了!");
    }
    

    }

运行后就会发现根本停不下来。造成这个结果的原因是各线程间的数据值没有可见性造成的,我们可以将isContinueRun变量改为volatile类型,或者通过如下的方式实现。

将runMethod()方法改为如下后,就会发现线程A会停止下来。

public void runMethod(){

        String anyString = new String();
        while(isContinueRun){
            synchronized (anyString) {

            }
        }
        System.out.println("停下来了!");
    }