由一个多线程共享Integer类变量问题引起的。。。

Posted 郑兴鹏

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了由一个多线程共享Integer类变量问题引起的。。。相关的知识,希望对你有一定的参考价值。

 最近看到一个多线程面试题,有三个线程分别打印A、B、C,请用多线程编程实现,在屏幕上循环打印10次ABCABC… 

  看到这个题目,首先想到的是解决方法是定义一个Integer类对象,初始化为0,由3个线程共享,如果Integer对象取余3之后等于0,则打印A,同时进行加1操作;如果Integer对象取3之后等于1,则打印B,同时进行加1操作;如果Integer对象取3之后等于1,则打印C,如果循环打印了10次的话,就退出线程。

复制代码
/**
 * ThreeThread
 * 3个线程测试
 */
public class ThreeThread {

    public static void main(String[] args) throws InterruptedException {
        Integer gData   = 0;
        Thread  thread1 = new MyTask(gData, 0, "A");
        Thread  thread2 = new MyTask(gData, 1, "B");
        Thread  thread3 = new MyTask(gData, 2, "C");

        thread1.start();
        thread2.start();
        thread3.start();

        thread1.join();
        thread2.join();
        thread3.join();
    }

}

class MyTask extends Thread {

    private Integer gData;
    private int     n;
    private String  info;

    public MyTask(Integer gData, int n, String info) {
        super("thread " + info);
        this.gData = gData;
        this.n     = n;
        this.info  = info;
    }

    public void run() {
        int i = 0;

        while (true) {
            synchronized (gData) {
                if (gData % 3 == n) {
                    System.out.print(info + " ");
                    gData++;
                    i++;
                }
            }

            if (i == 10) {
                break;
            }
            else {
                Thread.yield();
                try {
                    sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
复制代码

运行程序结果如下:

  发现只有A线程打印了"A",并没有发现B线程和C线程打印字符串:(。难道是A线程更改了Integer对象的值,而B线程和C线程并没有“看到”更新后的值?于是,在线程类的run方法的while循环中增加代码如下:

复制代码
while (true) {
    System.out.println(Thread.currentThread().getName() + " " + gData);
    synchronized (gData) {
        if (gData % 3 == n) {
            System.out.print(info + " ");
            gData++;
            i++;
        }
    }

    ...
}
复制代码

运行程序结果如下:

  由运行结果可知,刚开始A、B、C线程都拥有Integer类变量,并且初值为0。当A线程更改Integer类变量为1时,但是B和C线程中的Integer类变量的值仍然为0,因此,结果肯定不会打印出ABCABC....

  通过阅读Integer类源码,可知Integer类中存放int值的变量类型是final的:

/**
 * The value of the {@code Integer}.
 *
 * @serial
 */
private final int value;

  也就是说,Integer类对象的值每更新一次,就会创建一个新的Integer对象。运行程序结果只打印出了"A",表示刚开始A、B、C线程都拥有同一个Integer类变量,并且初值为0,但是当A线程更新Integer对象的值后,A线程中的Integer对象和B/C线程中的Integer对象已经不是同一个对象了。

  为了能够正常打印出ABCABC字符串,可以把Integer对象类型改为AtomicInteger,代码如下:

复制代码
/**
 * ThreeThread
 * 3个线程测试
 */
public class ThreeThread {

    public static void main(String[] args) throws InterruptedException {
        AtomicInteger gData = new AtomicInteger(0);
        Thread  thread1 = new MyTask(gData, 0, "A");
        Thread  thread2 = new MyTask(gData, 1, "B");
        Thread  thread3 = new MyTask(gData, 2, "C");

        thread1.start();
        thread2.start();
        thread3.start();

        thread1.join();
        thread2.join();
        thread3.join();
    }

}

class MyTask extends Thread {

    private AtomicInteger gData;
    private int     n;
    private String  info;

    public MyTask(AtomicInteger gData, int n, String info) {
        super("thread " + info);
        this.gData = gData;
        this.n = n;
        this.info = info;
    }

    public void run() {
        int i = 0;

        while (true) {
            synchronized (gData) {
                if (gData.get() % 3 == n) {
                    System.out.print(info + " ");
                    gData.incrementAndGet();
                    i++;
                }
            }

            if (i == 10) {
                break;
            }
            else {
                Thread.yield();
                try {
                    sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

}
复制代码

 

  第二种打印ABCABC...字符串的解决方法是使用wait/notify函数,示例代码如下:

复制代码
/**
 * ThreeThread2
 * 三个线程依次输出A B C,使用线程同步方式
 */
public class ThreeThread2 {

    public static void main(String[] args) throws InterruptedException {
        Object A = new Object();
        Object B = new Object();
        Object C = new Object();

        MyThread myThread1 = new MyThread(C, A, "A");
        MyThread myThread2 = new MyThread(A, B, "B");
        MyThread myThread3 = new MyThread(B, C, "C");

        myThread1.start();
        Thread.sleep(10);
        myThread2.start();
        Thread.sleep(10);
        myThread3.start();

        try {
            myThread1.join();
            myThread2.join();
            myThread3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class MyThread extends Thread {
    private Object prev;
    private Object curr;
    private String info;

    public MyThread(Object prev, Object curr, String info) {
        this.prev = prev;
        this.curr = curr;
        this.info = info;
    }

    public void run() {
        int cnt = 10;

        while (cnt-- > 0) {
            synchronized (prev) {
                synchronized (curr) {
                    System.out.print(info + " ");
                    curr.notify();
                }

                try {
                    prev.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
复制代码

以上是关于由一个多线程共享Integer类变量问题引起的。。。的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程 —— 线程安全线程同步线程间通信(含面试题集)

Java线程与并发库高级应用-线程范围内共享数据ThreadLocal类

(黑马Java多线程与并发库高级应用)05 线程范围内共享变量的概念与作用

线程安全

线程安全

Java多线程之Atomic:原子变量与原子类