在学习线程的时候,肯定会遇到 线程同步 问题
多个并发线程之间按照某种机制协调先后次序执行,当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作
根据上面的定义我们可以知道,线程同步问题主要是多线程并发情况下如何互斥访问共享资源;在多个线程对同一变量进行读写操作时,如果没有原子性,就可能产生脏数据。实现线程同步有很多的方式,比如同步方法,锁,阻塞队列等。
那么java中的线程是如何访问资源的呢,在学习了一段时间的jvm后,对于线程访问共享资源有了更深的理解
Java虚拟机在运行Java程序时,会把它所管理的内存区域划分为若干个不同的区域,每个区域都有各自的用途;下图是经典的JVM内存布局
程序计数器 (PC): 当前线程所执行的字节码的行号指示器,用于线程切换后能恢复到正确的执行位置
Java虚拟机栈: 每个线程都有一个自己的Java栈, 描述了Java方法运行时的内存模型:每个方法在执行时都会创建一个栈帧用于储存局部变量表、操作数栈、动态链接、方法出口等信息;每个方法的从开始调用到执行完成的过程,就是栈帧从入栈到出栈的过程
本地方法栈:与JAVA栈类似,区别是使用的对象不一样,本地方法栈是给Native方法使用的,JAVA虚拟机栈是给JAVA方式使用的
在没有任何同步机制的情况下,多个线程共享一块内存区域,对其操作;
不使用共享内存,每个线程内存空间相互独立;
多线程共享一块内存区域,但是对这块共享区域加锁访问;
对静态变量sum进行操作,静态变量保存在线程共享的内存区域中,多个线程可以同时访问
package com.thread; /** * @Author: ranjun * @Date: 2019/7/22 14:13 */ public class AddTest { private static int sum = 0; /** * 对静态变量sum进行累加操作 * @param n * @return */ public static int sum(int n){ sum = 0; for(int i = 0; i <= n; i++){ sum += i; //让出cpu资源,让其他线程执行; //可以将下面这段注释掉,如果累加次数足够少时,仍然会得到正确结果(当前线程可以在当前时间片中完成累加操作,并返回值) try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } return sum; } } 复制代码
写个main函数,定义四个线程,每个线程都调用上面的静态方法,观察运行结果,基本都是错误的:
package com.thread; /** * @Author: ranjun * @Date: 2019/7/22 14:17 */ public class MainTest { public static void main(String[] args) { Runnable runnable = new Runnable() { @Override public void run() { while(true){ System.out.println(Thread.currentThread().getName() +":" +AddTest.sum(100)); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }; new Thread(runnable).start(); new Thread(runnable).start(); new Thread(runnable).start(); new Thread(runnable).start(); } } 复制代码
部分运行结果,都是错的
Thread-3:20153 Thread-1:19953 Thread-2:19953 Thread-0:20153 Thread-1:18510 Thread-3:18510 复制代码
原因:多个线程同时对静态全局变量s进行操作导致;
ps:这里的例子是静态全局变量s,其实有很多种情况会引起结果异常问题,如在main方法中new出了一个对象,new出来的对象是存放在堆中的,多个线程共享,此时如果多线程同时操作该对象的话,也是有可能产生错误结果;
修改静态sum方法,使用局部变量sum,如下:
public static int sum(int n){ int sum = 0; for(int i = 0; i <= n; i++){ sum += i; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } return sum; } 复制代码
运行程序,结果正确:
Thread-3:5050 Thread-1:5050 Thread-2:5050 Thread-0:5050 Thread-0:5050 Thread-1:5050 Thread-2:5050 Thread-3:5050 复制代码
public synchronized static int sum(int n){ sum = 0; for (int i = 1; i <= n; i++) { sum += i; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } return sum; } 复制代码
运行程序,结果正确:
Thread-1:5050 Thread-3:5050 Thread-2:5050 Thread-0:5050 Thread-2:5050 复制代码
从上面的例子可以看到,多线程对共享资源的操作结果是难以控制的;所以在多线程编程过程中,我们要知道哪些资源是线程共享的,哪些是线程本地私有的,然后合理的对共享资源进行协调。