Java线程协作与共享变量保护代码示例
Java 多线程开发中,既要解决多个线程交替执行的协作问题(如 wait/notify),又要保证共享状态修改的安全性。以下通过两段 Java 代码示例,分别展示如何控制线程顺序以及如何使用 synchronized 防止竞态条件。
场景一:主线程与子线程交替执行
该示例要求主线程先执行10次,紧接着子线程执行10次,如此往复循环50次。这展示了如何通过 synchronized 方法和 Object.wait()/notify() 机制实现线程间的有序调度。
第1步:定义业务逻辑类并封装同步锁
创建一个内部类 Business 用于处理线程交互逻辑。该类包含一个布尔标志位 tag 作为控制状态:
tag = true:表示当前应允许主线程执行,子线程需等待。tag = false:表示当前应允许子线程执行,主线程需等待。
第2步:实现子线程的交替执行逻辑
在 Business 类中定义 synchronized void sub(int num) 方法供子线程调用:
1. 检查状态:若 tag 为 false(即轮到子线程时不满足条件,或上一轮子线程未释放),则调用 this.wait() 进入阻塞等待。
2. 执行任务:打印10次日志信息,表明子线程正在工作。
3. 切换状态并唤醒:将 tag 设为 true,并调用 notify() 唤醒处于等待队列中的主线程。
第3步:实现主线程的交替执行逻辑
定义 synchronized void main(int num) 方法供主线程调用,逻辑与子线程对称:
1. 检查状态:若 tag 为 true,说明此时轮到子线程,主线程调用 this.wait() 挂起。
2. 执行任务:打印10次日志信息。
3. 切换状态并唤醒:将 tag 设为 false,并调用 notify() 唤醒子线程。
第4步:启动线程并测试验证
在构造函数中启动一个新线程运行子线程的逻辑50次。随后在主线程中循环调用主线程逻辑50次。由于采用了精细的状态锁管理,两个线程能够严格交替输出结果。
package com.Thread.test;
/*
* function:主线程执行10次,子线程执行10次,
* 此过程重复50次
*/
public class ThreadProblem {
public ThreadProblem() {
final Business bus = new Business();
new Thread(new Runnable() {
public void run() {
for(int j=0;j<50;j++) {
bus.sub(j);
}
}
}).start();
for(int j=0;j<50;j++) {
bus.main(j);
}
}
class Business {
private boolean tag=true;
public synchronized void sub(int num) {
if(!tag) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int i=0;i<10;i++)
{
System.out.println("sub thread "+i+",loop "+num+".");
}
tag=false;
notify();
}
public synchronized void main(int num) {
if(tag) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int i=0;i<10;i++) {
System.out.println("main thread "+i+",loop "+num+".");
}
tag=true;
notify();
}
}
public static void main(String[] args) {
ThreadProblem problem = new ThreadProblem();
}
}
场景二:多线程同时修改共享变量的安全性
该示例创建了4个线程,共享一个静态整型变量 j。其中两个线程负责对 j 进行加1操作,另外两个线程负责减1。本段代码展示了使用 synchronized 关键字修饰实例方法来避免数据竞争(Race Condition)。
第1步:定义共享资源与线程结构
声明一个静态变量 private static int j=0 作为共享计数器。为了保证线程安全,我们将自增和自减的操作封装在一个内部类 A 的方法中。
第2步:确保操作的原子性
在类 A 中定义了 synchronized void add1() 和 synchronized void delete1()。
1. synchronized 保证了同一时刻只有一个线程能访问这两个方法。
2. 在执行 j++ 或 j-- 期间,其他试图访问相同锁的线程会被阻塞,直到当前操作结束并打印最新值。
3. 这样可以确保输出的计数结果是准确且连贯的,不会出现因并发更新导致的脏读或丢失更新。
第3步:启动多线程序列测试
在 Demo1 构造函数中进行初始化:
- 开启两个线程,每个线程循环调用 a.add1() 5次。
- 开启另外两个线程,每个线程循环调用 a.delete1() 5次。
由于总共有 2 × 5 = 10 次加法,以及 2 × 5 = 10 次减法,最终预期结果为 0。尽管执行过程中线程会穿插执行,但由于互斥锁的存在,每个步骤都是原子的。
package com.Thread.test;
//实现4个线程,两个线程加1,两个线程减1
public class Demo1 {
private static int j=0;
private A a = new A();
//构造函数
public Demo1() {
System.out.println("j的初始值为:"+j);
for(int i=0;i<2;i++) {
new Thread(new Runnable(){
public void run() {
for(int k=0;k<5;k++){
a.add1();
}
}
}).start();
new Thread(new Runnable(){
public void run() {
for(int k=0;k<5;k++)
{
a.delete1();
}
}
}).start();
}
}
class A {
public synchronized void add1() {
j++;
System.out.println(Thread.currentThread().getName()+"对j加1,目前j="+Demo1.j);
}
public synchronized void delete1() {
j--;
System.out.println(Thread.currentThread().getName()+"对j减1,目前j="+Demo1.j);
}
}
//用于测试的主函数
public static void main(String[] args) {
Demo1 demo = new Demo1();
}
}
以上就是Java线程重复执行以及操作共享变量的详细内容,更多关于Java并发编程的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









