Java多线程修改变量的可见性问题

运行以下Java代码:

public class Testjava6 {
private static boolean flag = false;
    static int i =0;
    public static void main(String[] args) {
       new Thread(() -> {
           try {
               Thread.sleep(1);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println("线程1  修改了flag  为  true");
           flag = true;
        }).start();

        while (!flag){
            i++;
        }
        System.out.println("循环了 "+i+"次");
        System.out.println(flag);
    }
}


运行结果:

img

当把线程休眠时间设置为10毫秒


public class Testjava6 {
private static boolean flag = false;
    static int i =0;
    public static void main(String[] args) {


       new Thread(() -> {
           try {
               Thread.sleep(10);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println("线程1  修改了flag  为  true");
           flag = true;
        }).start();

        while (!flag){
            i++;
        }
        System.out.println("循环了 "+i+"次");
        System.out.println(flag);
    }
}



运行结果 : 出现了死循环

img

请解释为什么会出现死循环
可以从变量的内存可见性,JIT及时编译器等方向进行分析

在第一个例子中,将线程休眠时间设置为1毫秒,线程有足够的时间去修改 flag 变量的值。因此,主线程最终能够检测到 flag 变为 true,并跳出循环。

然而,在第二个例子中,将线程休眠时间增加到10毫秒。这可能导致了一种情况,即主线程在检测 flag 变量的值时,由于指令重排或优化等原因,可能不会重新从主内存中读取 flag 变量的最新值。相反,它可能仅仅在线程的工作内存中进行读取,而该工作内存中的值仍然是 false。因此,主线程将陷入无限循环,因为它无法检测到 flag 变为 true

这是因为在多线程环境中,由于缓存和指令重排等原因,可能存在线程之间的数据不一致性。为了解决这个问题,可以使用同步机制,如 volatile 关键字或使用 synchronized 块,来确保变量的内存可见性和线程间的同步。

在这个例子中,可以将 flag 变量声明为 volatile,这样就能确保每次读取 flag 变量时,都从主内存中获取最新的值。修改代码如下:

public class Testjava6 {
    private static volatile boolean flag = false;
    static int i = 0;

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1  修改了flag  为  true");
            flag = true;
        }).start();

        while (!flag) {
            i++;
        }
        System.out.println("循环了 " + i + "次");
        System.out.println(flag);
    }
}

通过将 flag 声明为 volatile,可以确保变量的内存可见性,从而避免出现死循环的情况。

  • 你可以参考下这个问题的回答, 看看是否对你有帮助, 链接: https://ask.csdn.net/questions/7747470
  • 这篇博客也不错, 你可以看下【Java】数组逆序操作。定义长度为10的数组,将数组元素对调并输出对调后的结果。
  • 除此之外, 这篇博客: JAVA数据结构——利用图的广度优先遍历搜索算法确定无向连通图的连通分量中的 代码实现利用广度优先遍历搜索确定无向图的连通分量:  部分也许能够解决你的问题, 你可以仔细阅读以下内容或跳转源博客中阅读:
  • 1. 图类型:

    package com.usts.edu.graphic;
    
    // 图的种类 有向图、有向网、无向图、无向网
    public enum GraphKind {
    	UDG, // 无向图(UnDirected Graph)
    	DG, // 有向图(Directed Graph)
    	UDN, // 无向网(UnDirected Network)
    	DN; // 有向网(Directed Network)
    }
    

    2. 图的接口 

    package com.usts.edu.graphic;
    
    //图的接口
    public interface IGraph {
    	void createGraph();//创建一个图
    
    	int getVexNum(); // 返回顶点数
    
    	int getArcNum();// 返回边数
    
    	Object getVex(int v) throws Exception;// 返回v表示结点的值, 0 <= v < vexNum
    
    	int locateVex(Object vex);// 给定顶点的值vex,返回其在图中的位置,如果图中不包含此顶点,则返回-1
    
    	int firstAdjVex(int v) throws Exception; // 返回v的第一个邻接点,若v没有邻接点,则返回-1,其中0≤v<vexNum
    
    	int nextAdjVex(int v, int w) throws Exception;// 返回v相对于w的下一个邻接点,若w是v的最后一个邻接点,则返回-1,其中0≤v, w<vexNum
    
    }
    

     3. 创建图:

    package com.usts.edu.graphic;
    
    import java.util.Scanner;
    
    
    public class MGraph implements IGraph {
    	public final static int INFINITY = Integer.MAX_VALUE;
    
    	private GraphKind kind;
    
    	private int vexNum, arcNum;
    
    	private Object[] vexs;
    
    	private int[][] arcs;
    
    	public MGraph() {
    		this(null, 0, 0, null, null);
    	}
    
    	public MGraph(GraphKind kind, int vexNum, int arcNum, Object[] vexs,
    			int[][] arcs) {
    		this.kind = kind;
    		this.vexNum = vexNum;
    		this.arcNum = arcNum;
    		this.vexs = vexs;
    		this.arcs = arcs;
    	}
    
    	public void createGraph() {
    		Scanner sc = new Scanner(System.in);
    		System.out.println("请输入图的类型");
    		GraphKind kind = GraphKind.valueOf(sc.next());
    		switch (kind) {
    		case UDG:
    			createUDG();
    			return;
    		case DG:
    			createDG();
    			return;
    		case UDN:
    			createUDN();
    			return;
    		case DN:
    			createDN();
    			return;
    		}
    	}
    
    	private void createUDG() {
    	};
    
    	private void createDG() {
    	};
    
    	private void createUDN() {
    		Scanner sc = new Scanner(System.in);
    		vexNum = sc.nextInt();
    		arcNum = sc.nextInt();
    		vexs = new Object[vexNum];
    		for (int v = 0; v < vexNum; v++)
    			vexs[v] = sc.next();
    
    		arcs = new int[vexNum][vexNum];
    		for (int v = 0; v < vexNum; v++)
    			for (int u = 0; u < vexNum; u++)
    				arcs[v][u] = INFINITY;
    
    		for (int k = 0; k < arcNum; k++) {
    			int v = locateVex(sc.next());
    			int u = locateVex(sc.next());
    			arcs[v][u] = arcs[u][v] = sc.nextInt();
    		}
    	}
    
    	private void createDN() {
    		Scanner sc = new Scanner(System.in);
    		vexNum = sc.nextInt();
    		arcNum = sc.nextInt();
    		vexs = new Object[vexNum];
    		for (int v = 0; v < vexNum; v++)
    			vexs[v] = sc.next();
    
    		arcs = new int[vexNum][vexNum];
    		for (int v = 0; v < vexNum; v++)
    			for (int u = 0; u < vexNum; u++)
    				arcs[v][u] = INFINITY;
    
    		for (int k = 0; k < arcNum; k++) {
    			int v = locateVex(sc.next());
    			int u = locateVex(sc.next());
    			arcs[v][u] = sc.nextInt();
    		}
    
    	}
    
    	public int getVexNum() {
    		return vexNum;
    	}
    
    	public int getArcNum() {
    		return arcNum;
    	}
    
    	public int locateVex(Object vex) {
    		for (int v = 0; v < vexNum; v++)
    			if (vexs[v].equals(vex))
    				return v;
    		return -1;
    	}
    
    	public Object getVex(int v) throws Exception {
    		if (v < 0 && v >= vexNum)
    			throw new Exception("第" + v + "个顶点不存在!");
    		return vexs[v];
    	}
    
    	public int firstAdjVex(int v) throws Exception {
    		if (v < 0 && v >= vexNum)
    			throw new Exception("第" + v + "个顶点不存在!");
    
    		for (int j = 0; j < vexNum; j++)
    			if (arcs[v][j] != 0 && arcs[v][j] < INFINITY)
    				return j;
    
    		return -1;
    	}
    
    	public int nextAdjVex(int v, int w) throws Exception {
    		if (v < 0 && v >= vexNum)
    			throw new Exception("第" + v + "个顶点不存在!");
    
    		for (int j = w + 1; j < vexNum; j++)
    			if (arcs[v][j] != 0 && arcs[v][j] < INFINITY)
    				return j;
    
    		return -1;
    	}
    
    	public GraphKind getKind() {
    		return kind;
    	}
    
    	public int[][] getArcs() {
    		return arcs;
    	}
    
    	public Object[] getVexs() {
    		return vexs;
    	}
    
    	public void setArcNum(int arcNum) {
    		this.arcNum = arcNum;
    	}
    
    	public void setArcs(int[][] arcs) {
    		this.arcs = arcs;
    	}
    
    	public void setKind(GraphKind kind) {
    		this.kind = kind;
    	}
    
    	public void setVexNum(int vexNum) {
    		this.vexNum = vexNum;
    	}
    
    	public void setVexs(Object[] vexs) {
    		this.vexs = vexs;
    	}
    
    }
    

    4. 实现搜索:

     

    package com.usts.edu.graphic;
    
    import com.usts.edu.Queue.LinkQueue;
    
    /**
     * Created by Guanzhong Hu
     * Date :2020/3/30
     * Description :
     * Version :1.0
     */
    public class GDSeach {
            public final static int INFINITY = Integer.MAX_VALUE;
    
            public static void CC_BFS(IGraph G) throws Exception {
                boolean[] visited = new boolean[G.getVexNum()];
                for (int v = 0; v < G.getVexNum(); v++)
    
                    visited[v] = false;
                LinkQueue Q = new LinkQueue();
                LinkQueue P = new LinkQueue();
                int i = 0;
                for (int v = 0; v < G.getVexNum(); v++) {
                    P.clear();
                    if (!visited[v]) {
                        visited[v] = true;
                        P.offer(G.getVex(v));
                        Q.offer(v);
                        while (!Q.isEmpty()) {
                            int u = (Integer) Q.poll();
                            for (int w = G.firstAdjVex(u); w >= 0; w = G.nextAdjVex(u,
                                    w)) {
                                if (!visited[w]) {
                                    visited[w] = true;
                                    P.offer(G.getVex(w));
                                    Q.offer(w);
                                }
                            }
                        }
                        System.out.println("图的第" + ++i + "个连通分量为:");
                        while (!P.isEmpty())
                            System.out.print(P.poll().toString() + " ");
                        System.out.println();
                    }
                }
            }
    
            public static void main(String[] args) throws Exception {
                Object vexs[] = { "A", "B", "C", "D", "E", "F", "G" };
                int[][] arcs = { { 0, 1, INFINITY, 1, INFINITY, INFINITY, INFINITY },
                        { 1, 0, 1, INFINITY, INFINITY, INFINITY, INFINITY },
                        { INFINITY, 1, 0, 1, INFINITY, INFINITY, INFINITY },
                        { 1, INFINITY, 1, 0, INFINITY, INFINITY, INFINITY },
                        { INFINITY, INFINITY, INFINITY, INFINITY, 0, 1, INFINITY },
                        { INFINITY, INFINITY, INFINITY, INFINITY, 1, 0, 1 },
                        { INFINITY, INFINITY, INFINITY, INFINITY, INFINITY, 1, 0 }, };
                MGraph G = new MGraph(GraphKind.UDG, 7, 6, vexs, arcs);
                CC_BFS(G);
            }
    }
    

    无向图的连通分量搜索是一种广度优先遍历的应用,当然还有如Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。

    源码地址:https://gitee.com/jockhome/data_structure

  • 您还可以看一下 汪翠老师的java项目实战之欢乐斗地主游戏开发教程 毕业项目课程设计带源码课程中的 给扑克牌绑定鼠标事件实现单击可以选择出牌列表小节, 巩固相关知识点