Java多线程编程-线程实例变量

  • Java内存区域
  • 线程实例变量不共享
  • 线程实例变量共享
  • 线程安全问题
  • 解决count--不是原子性操作的问题
  • 同步synchronized
  • 原子类操作



自定义线程类中的实例变量针对其他的线程可以是共享,也可以不是共享的。

Java内存区域

Java虚拟机在执行Java程序的过程中会把虚拟机所管理的内存划分为若干个不同的数据区,有些数据区是线程共享的,有些数据区不是线程共享。

Java 变量存储 线程切换上下文 java线程变量怎么实现的_Java 变量存储 线程切换上下文


方法区和堆是所有线程共享的

Java栈、本地方法栈和程序计数器是线程私有的

线程实例变量不共享

Java 变量存储 线程切换上下文 java线程变量怎么实现的_Java_02


创建一个实例变量,创建多个线程去使用,代码如下:

public class NoShareVariableThread extends Thread {
	//创建实例变量
    private int count = 5;
    //设置线程名称
    public NoShareVariableThread(String name) {
        this.setName(name);
    }
    @Override
    public void run() {
        while (count > 0) {
            count--;
            System.out.println("Current thread:" + Thread.currentThread().getName() + " count:" + count);
        }
    }
}

运行代码:

public class NoShareVariableThreadMain {
    public static void main(String[] args) {
    	//创建三个线线程
        Stream.of("thread1", "thread2", "thread3").forEach(threadName -> {
            NoShareVariableThread noShareVariableThread = new NoShareVariableThread(threadName);
            noShareVariableThread.start();
        });
    }
}

运行结果如图:

Java 变量存储 线程切换上下文 java线程变量怎么实现的_多线程编程_03


每个线程都会操作count的值,都在各自的线程操作。count这个时候就不是共享的。如果我们需要将多个线程改为共享的,继续往下看

线程实例变量共享

Java 变量存储 线程切换上下文 java线程变量怎么实现的_Java_04


上面图说明了多个线程操作同一个实例变量,上代码

public class ShareVariablesThread extends Thread {
	
    private int count = 5;
    @Override
    public void run() {
        count--; //此处会存在一个线程安全问题,因为i--不是一个原子性操作
        System.out.println("Current thread:" + Thread.currentThread().getName() + " count:" + count);
    }
}

运行代码:

public class ShareVariablesThreadMain {

    public static void main(String[] args) {
            ShareVariablesThread shareVariablesThread = new ShareVariablesThread();
            Thread thread1 = new Thread(shareVariablesThread, "thread1");
            Thread thread2 = new Thread(shareVariablesThread, "thread2");
            Thread thread3 = new Thread(shareVariablesThread, "thread3");
            Thread thread4 = new Thread(shareVariablesThread, "thread4");
            Thread thread5 = new Thread(shareVariablesThread, "thread5");
            thread1.start();
            thread2.start();
            thread3.start();
            thread4.start();
            thread5.start();
    }
}

Java 变量存储 线程切换上下文 java线程变量怎么实现的_Java 变量存储 线程切换上下文_05


如果你的代码不是上面的结果,可以多运行几次看看?可以看出这里的线程2和线程1的值为一样,这个是不是有问题?为什么会这样呢?

线程安全问题

非线程安全:是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被改变,值不同步的情况,进而影响程序的执行流程。

看上面的代码,c–这行代码会出现非线程安全的问题,在jvm中执行c–的操作会分为如下操作:

1.取得原有的c值

2.计算c-1的值

3.对c进行赋值操作

原子性操作为不可分操作,上面的c–操作会分为三步进行,所以不是原子性操作。

JVM中提供了8中原子操作,如下:

Java 变量存储 线程切换上下文 java线程变量怎么实现的_Java 变量存储 线程切换上下文_06


怎么解决?

解决count–不是原子性操作的问题

我们可以通过同步的方式进行处理,或者使用锁的当时,也可以使用原子类进行等,我们这里使用同步的方式进行和原子类进行操作,直接上代码:

同步synchronized

public class ShareVariablesThreadSync extends Thread {
    private int count = 5;
    @Override
    public synchronized void run() {
        //这里不要使用for语句,因为使用同步后其他线程就得不到运行的机会了,一直由一个线程进行运算
        count--;
        System.out.println("Current thread name:" + Thread.currentThread().getName() + "计算, count:" + count);
    }
}

运行的代码不需要更改,不管你运行多少次,都不会出现上面未实现同步的问题,结果如下:

Java 变量存储 线程切换上下文 java线程变量怎么实现的_多线程编程_07

原子类操作

public class ShareVariablesThreadAtomic extends Thread {

    private AtomicInteger count = new AtomicInteger(5);

    @Override
    public void run() {
        count.getAndDecrement();
        System.out.println("Current thread name:" + Thread.currentThread().getName() + " count:" + count.get());
    }
}

一样可以达到上面的效果,后面会说明原子类实现机制。原子的操作都会调用unsafe的操作。

运行结果如下:

Java 变量存储 线程切换上下文 java线程变量怎么实现的_thread_08