文章目录
volatile关键字
作用:volatile修饰的共享变量保证了不同线程对其进行操作时的可见性。
问题场景:主线程修改stop为true之后thread线程可能停不下来
public class VolatileDemo {
private static boolean stop = false;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("thread start");
while(!stop) {
}
System.out.println("thread exit");
}
};
thread.start();
Thread.sleep(100);
stop = true;
System.out.println("stop set true");
}
}
执行结果:thread结束不了 "thread exit"一直没有打印
thread start
stop set true
用volatile修饰stop之后:程序正常结束
public class VolatileDemo {
private static volatile boolean stop = false;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("thread start");
while(!stop) {
}
System.out.println("thread exit");
}
};
thread.start();
Thread.sleep(100);
stop = true;
System.out.println("stop set true");
}
}
执行结果:
thread start
stop set true
thread exit
Process finished with exit code 0
第一段程序为什么停止不了:
程序执行的时候,每个线程在运行过程中都有自己的工作内存,当thread线程运行的时候会将stop变量的值拷贝一份房在自己的内存中,当main线程更改了stop的值之后没有及时更新主存中的值。thread线程不知道stop的变量已经更改所以会一直循环下去。
volatile修饰之后:
- 1.使用volatile关键字会强制将修改的值立即写入主存;
- 2.当main线程进行修改时,会导致thread的工作内存中缓存变量stop的缓存行无效
- 3.由于thread中缓存变量stop的缓存行无效,所以当线程再次读取变量stop的值时会去主存重新读取。
volatile修饰之后保证了stop变量修改之后thread线程能够读取到新的值所以程序能够正常退出。
volatile关键字的两层语义
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
2)禁止进行指令重排序。
synchronized关键字
synchronized关键字可以用来对程序加锁。常用的三种形式:
1. 修饰一个代码块。
2. 修饰一个方法。
3. 修改一个静态的方法。
1. 修饰一个代码块
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象,锁(lock)可以是任一对象,同一把锁同一时间只能由一个线程可以获得。
synchronized(lock){
//do something
}
2. 修饰一个方法
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
public synchronized void method(){//do something}
效果等同于
public void method(){
synchronized(this) {
//do something
}
}
3. 修改一个静态的方法
修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。
public synchronized static void method(){}
效果等同于
public static void method(){
synchronized(类名.class) {
//do something
}
}
4. 不同锁对象的区别
synchronized(this){…}、synchronized(类名.class){…}与synchronized(任意对象){…} 的区别
- synchronized(this){…}
this关键字指向的是当前对象的引用,同一个实例内用同一把锁,用的是同一个对象就是同一把锁。
public class SynchronizedDemo {
public static void main(String[] args) {
ThisDemo thisDemo = new ThisDemo();
new Thread("t1"){
@Override
public void run() {
thisDemo.print();
}
}.start();
new Thread("t2"){
@Override
public void run() {
thisDemo.print();
}
}.start();
}
public static class ThisDemo {
private void print() {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + "start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "end");
}
}
}
}
输出结果 用的是同一把锁有序输出 第一个线程完成第二个才开始
t1start
t1end
t2start
t2end
Process finished with exit code 0
调整成每次new一个新对象
public class SynchronizedDemo {
public static void main(String[] args) {
new Thread("t1"){
@Override
public void run() {
new ThisDemo().print();
}
}.start();
new Thread("t2"){
@Override
public void run() {
new ThisDemo().print();
}
}.start();
}
public static class ThisDemo {
private void print() {
synchronized (this) {
System.out.println(Thread.currentThread().getName() + "start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "end");
}
}
}
}
输出变成无序了,两个线程同时进入代码块
t1start
t2start
t2end
t1end
Process finished with exit code 0
- synchronized(类名.class){…}
在程序运行时,当需要生成这个类的对象,JVM就会检查此类是否已经装载内存中。若是没有装载,则把.class文件装入到内存中。若是装载,则根据class文件生成实例对象。所以 在同一个jvm进程中只有一个,整个进程内用同一把锁,即使是每个线程都new一个新的对象,也是有序的,同时只有一个进程进入该代码块。
public class SynchronizedDemo {
public static void main(String[] args) {
new Thread("t1"){
@Override
public void run() {
new ThisDemo().print();
}
}.start();
new Thread("t2"){
@Override
public void run() {
new ThisDemo().print();
}
}.start();
}
public static class ThisDemo {
private void print() {
synchronized (ThisDemo.class) {
System.out.println(Thread.currentThread().getName() + "start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "end");
}
}
}
}
输出
t1start
t1end
t2start
t2end
Process finished with exit code 0
- synchronized(任意对象){…}
当lock对象为同一个时,才能保证同一时刻只有一个线程在执行
public class SynchronizedDemo {
private static Object lock = new Object();
public static void main(String[] args) {
new Thread("t1"){
@Override
public void run() {
new ThisDemo().print();
}
}.start();
new Thread("t2"){
@Override
public void run() {
new ThisDemo().print();
}
}.start();
}
public static class ThisDemo {
private void print() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + "start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "end");
}
}
}
}
锁对象是同一个,输出正常
t1start
t1end
t2start
t2end
Process finished with exit code 0
public class SynchronizedDemo {
public static void main(String[] args) {
new Thread("t1"){
@Override
public void run() {
new ThisDemo().print();
}
}.start();
new Thread("t2"){
@Override
public void run() {
new ThisDemo().print();
}
}.start();
}
public static class ThisDemo {
private void print() {
synchronized (new Object()) {
System.out.println(Thread.currentThread().getName() + "start");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "end");
}
}
}
}
锁每次都是新建的,输出是并行的,两条线程同时进入到了代码块
t1start
t2start
t1end
t2end
Process finished with exit code 0