Java多线程笔记(二)--volatile关键字

本文深入探讨了关键字volatile在并发编程中的应用,包括其如何确保变量的可见性、解决死循环问题,以及与synchronized的区别。并通过实例展示了volatile在多线程环境下的使用技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

亲爱的观众朋友们,你们好!这是多线程笔记的第二篇文章,这一章主要是学习一下对象以及变量的并发访问
学习完本章主要掌握以下技术点:

  • synchronized对象监视器Object时的使用。
  • synchronized对象监视器Class时的使用。
  • 非线程安全是如何出现的。
  • 关键字volatile的主要作用。
  • 关键字volatilesynchronized的区别以及使用情况。

本章的类容较多,计划分三次更新,每次更新一部分。这是第三部分

3 volatile关键字

关键字volatile的主要作用是使变量在多个线程间可见。

3.1 关键字volatile与死循环
public class PrintString {

	private boolean isContinuePrint = true;

	public boolean isContinuePrint() {
		return isContinuePrint;
	}

	public void setContinuePrint(boolean isContinuePrint) {
		this.isContinuePrint = isContinuePrint;
	}

	public void printStringMethod() {
		try {
			while (isContinuePrint == true) {
				System.out.println("run printStringMethod threadName="
						+ Thread.currentThread().getName());
				Thread.sleep(1000);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

public class Run {

	public static void main(String[] args) {
		PrintString printStringService = new PrintString();
		printStringService.printStringMethod();
		System.out.println("我要停止它stopThread="
				+ Thread.currentThread().getName());
		printStringService.setContinuePrint(false);
	}

}

运行结果:
run printStringMethod threadName=main
run printStringMethod threadName=main
run printStringMethod threadName=main

根本停不下来

3.2解决同步死循环
public class PrintString implements Runnable {

	private boolean isContinuePrint = true;

	public boolean isContinuePrint() {
		return isContinuePrint;
	}

	public void setContinuePrint(boolean isContinuePrint) {
		this.isContinuePrint = isContinuePrint;
	}

	public void printStringMethod() {
		try {
			while (isContinuePrint == true) {
				System.out.println("run printStringMethod threadName="
						+ Thread.currentThread().getName());
				Thread.sleep(1000);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		printStringMethod();
	}
}

public class Run {

	public static void main(String[] args) {
		PrintString printStringService = new PrintString();
		new Thread(printStringService).start();

		System.out.println("我要停止它stopThread="
				+ Thread.currentThread().getName());
		printStringService.setContinuePrint(false);
	}

}

运行结果:
我要停止它stopThread=mian
run printStringMethod threadName=main

关键字volatile的作用就是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈取得变量的值。

3.3解决异步死循环
public class RunThread extends Thread {

	volatile private boolean isRunning = true;

	public boolean isRunning() {
		return isRunning;
	}

	public void setRunning(boolean isRunning) {
		this.isRunning = isRunning;
	}

	@Override
	public void run() {
		System.out.println("进入run了“);
		while (isRunning == true) {
		}
		System.out.println("线程被停止了");
	}

}

public class Run {
	public static void main(String[] args) {
		try {
			RunThread thread = new RunThread();
			thread.start();
			Thread.sleep(1000);
			thread.setRunning(false);
			System.out.println("已经赋值为false");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}


运行结果:
进入run了
线程被停止了
已经赋值为false

JVM -server模式结果:
进入run了
已经赋值为false

变量isRunning存在于公共堆栈及线程的私有堆栈中。在JVM被设置为-server模式时为了线程运行的效率,线程一直在私有堆栈中取得isRunning的值是true。而代码thread.setRunning(false);虽然被执行,更新的却是公共堆栈中的isRunning变量值false,所以一直就是死循环的状态。

下图是未使用关键字的模型
未使用关键字

下图是使用关键字的模型

使用关键字
下面将关键字volatile和synchronized进行比较:

关键字性能修饰对象应用程度是否阻塞原子性可见性
volatile变量不保证保证
synchronized方法/代码块保证保证

注:
1、volatile是线程同步的轻量级实现,所以性能要好。
2、随着JDK升级,synchronized关键字应用较广泛。

volatile
synchronized
线程安全
可见性
原子性

关键字volatile解决的是变量在多个线程之间的可见性。synchronized关键字解决的是多个线程之间访问资源的同步性。

2.4 volatile非原子的特性

如果修改实例变量中的数据,比如i++,这样的操作就不是一个原子操作。
表达式i++的操作步骤分解如下:
1、从内存中取出i的值。
2、计算i的值。
3、将i的值写入内存。

原子特性

2.5 使用原子类进行i++操作
public class AddCountThread extends Thread {
	private AtomicInteger count = new AtomicInteger(0);

	@Override
	public void run() {
		for (int i = 0; i < 10000; i++) {
			System.out.println(count.incrementAndGet());
		}
	}
}

public class Run {

	public static void main(String[] args) {
		AddCountThread countService = new AddCountThread();

		Thread t1 = new Thread(countService);
		t1.start();

		Thread t2 = new Thread(countService);
		t2.start();

		Thread t3 = new Thread(countService);
		t3.start();

		Thread t4 = new Thread(countService);
		t4.start();

		Thread t5 = new Thread(countService);
		t5.start();

	}

}

运行结果:
4995
4996
4997
4998
4999
5000

原子操作是不能分割的整体,没有其他线程能够中断或者检查正在原子操作中的变量。

2.6 原子类也不完全安全
public class MyService {

	public static AtomicLong aiRef = new AtomicLong();

	synchronized public void addNum() {
		System.out.println(Thread.currentThread().getName() + "加了100之后的值:”
				+ aiRef.addAndGet(100));
		aiRef.addAndGet(1);
	}

}
public class MyThread extends Thread {
	private MyService mySerivce;

	public MyThread(MyService mySerivce) {
		super();
		this.mySerivce = mySerivce;
	}

	@Override
	public void run() {
		mySerivce.addNum();
	}

}
public class Run {

	public static void main(String[] args) {
		try {
			MyService service = new MyService();

			MyThread[] array = new MyThread[5];
			for (int i = 0; i < array.length; i++) {
				array[i] = new MyThread(service);
			}
			for (int i = 0; i < array.length; i++) {
				array[i].start();
			}
			Thread.sleep(1000);
			System.out.println(service.aiRef.get());
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}


addNum方法未同步前后运行结果:


Thread-0加了100之后的值:100
Thread-4加了100之后的值:501
Thread-1加了100之后的值:200
Thread-3加了100之后的值:400
Thread-2加了100之后的值:300
505


Thread-0加了100之后的值:100
Thread-4加了100之后的值:201
Thread-3加了100之后的值:302
Thread-2加了100之后的值:403
Thread-1加了100之后的值:504
505

打印错误主要是因为addAndGet()是原子的,但方法和方法间不是原子的。

2.7 synchronized代码块有volatile同步功能

关键字synchronized代码块可以使多个线程访问同一个资源就有同步性,而且它还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能。

本章小结:

  • synchronized对象监视器Object时的使用。
    synchronized方法,synchronized(this)代码块都是Object锁,synchronized(非this对象x)是对象x锁。
  • synchronized对象监视器Class时的使用。
    synchronized静态方法。
  • 非线程安全是如何出现的。
    脏读,变量的不同步。
  • 关键字volatile的主要作用。
    保证多个线程之间变量的可见性。
  • 关键字volatilesynchronized的区别以及使用情况。
关键字性能修饰对象应用程度是否阻塞原子性可见性
volatile变量不保证保证
synchronized方法/代码块保证保证

公众号

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值