Java多线程编程核心技术(第3版)
上QQ阅读APP看书,第一时间看更新

2.1.2 实例变量“非线程安全”问题及解决方案

如果多个线程共同访问一个对象中的实例变量,则有可能出现非线程安全问题。

线程访问的对象中如果有多个实例变量,则运行的结果有可能出现交叉的情况。此情况已经在第1章的非线程安全的案例演示过。

如果对象仅有一个实例变量,则有可能出现覆盖的情况。创建t2项目进行测试,HasSelf-PrivateNum.java文件代码如下:


package service;

public class HasSelfPrivateNum {

private int num = 0;

public void addI(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) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

}

文件ThreadA.java代码如下:


package extthread;

import service.HasSelfPrivateNum;

public class ThreadA extends Thread {

private HasSelfPrivateNum numRef;

public ThreadA(HasSelfPrivateNum numRef) {
    super();
    this.numRef = numRef;
}

@Override
public void run() {
    super.run();
    numRef.addI("a");
}

}

文件ThreadB.java代码如下:


package extthread;

import service.HasSelfPrivateNum;

public class ThreadB extends Thread {

private HasSelfPrivateNum numRef;

public ThreadB(HasSelfPrivateNum numRef) {
    super();
    this.numRef = numRef;
}

@Override
public void run() {
    super.run();
    numRef.addI("b");
}

}

文件Run.java代码如下:


package test;

import service.HasSelfPrivateNum;
import extthread.ThreadA;
import extthread.ThreadB;

public class Run {

public static void main(String[] args) {

    HasSelfPrivateNum numRef = new HasSelfPrivateNum();

    ThreadA athread = new ThreadA(numRef);
    athread.start();

    ThreadB bthread = new ThreadB(numRef);
    bthread.start();

}

}

程序运行结果如图2-2所示。

图2-2 单例模式中的实例变量呈非线程安全状态

上面的实验是两个线程同时访问同一个业务对象中的一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则有可能会出现非线程安全问题,此示例的知识点在前面已经介绍过,只需要在public void addI(String username)方法前加关键字synchronized即可,更改后的代码如下:


package service;

public class HasSelfPrivateNum {

private int num = 0;

synchronized public void addI(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) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}

}

程序再次运行的结果如图2-3所示。

图2-3 同步了,线程安全了

综上所述,两个线程同时访问同一个对象中的同步方法时一定是线程安全的。在本实验,由于线程是同步访问,并且a线程先执行,所以先输出a,然后输出b,但是完全有可能出现b线程先运行,那么就先输出b再输出a,不管哪个线程先运行,这个线程进入用synchronized声明的方法时就上锁,方法执行完成后自动解锁,之后下一个线程才会进入用synchronized声明的方法里,如果不解锁,其他线程将无法执行用synchronized声明的方法。

注意

一直在讨论锁,到底谁是锁呢?用synchronized声明的方法所在类的对象就是锁。在Java中没有“锁方法”这样的概念,锁是对象。