亲爱的观众朋友们,你们好!这是多线程笔记的第二篇文章,这一章主要是学习一下对象以及变量的并发访问。
学习完本章主要掌握以下技术点:
- [ ] synchronized对象监视器Object时的使用。
- [ ] synchronized对象监视器Class时的使用。
- [ ] 非线程安全是如何出现的。
- [ ] 关键字volatile的主要作用。
- [ ] 关键字volatile与 synchronized的区别以及使用情况。
本章的类容较多,计划分三次更新,每次更新一部分。这是第三部分。
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关键字解决的是多个线程之间访问资源的同步性。
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的主要作用。
保证多个线程之间变量的可见性。 - [ ] 关键字volatile与 synchronized的区别以及使用情况。
关键字 | 性能 | 修饰对象 | 应用程度 | 是否阻塞 | 原子性 | 可见性 |
---|---|---|---|---|---|---|
volatile | 优 | 变量 | 大 | 否 | 不保证 | 保证 |
synchronized | 劣 | 方法/代码块 | 小 | 是 | 保证 | 保证 |