1、volatile关键字
变量值的同步:
volatile可以强制从公共内存中读取变量的值,再同步到私有内存中。多个线程可以操作同一个对象内的变量,一个线程做出更改后,可以被其他线程感知。
public class SelfMain {
private static volatile boolean flag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
for(int i = 0; i < 1000; i++) {
if(flag) {
System.out.println("flag is true----" + i);
}else {
System.out.println("flag is false----" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}).start();
new Thread(() -> {
flag = true;
}).start();
}
}
输出:
flag is false----0
flag is true----1
flag is true----2
flag is true----3
flag is true----4
flag is true----5
flag is true----6
flag is true----7
flag is true----8
禁止代码重排序:
java程序在运行时,jit(即使编译器)会对代码进行优化,此时可能会对代码进行重排序,调整代码中方法执行的顺序(将耗时轻的代码先执行)。
volatile之前的代码不能放到volatile之后,同样volatile之后的代码不能放到volatile之前。但是volatile之前或者之后的代码可以进行重新排序,但是不能越过volatile。
同样synchronized同样具有禁止代码重排序的功能,与volatile禁止代码重排序的规则一样。
2、synchronized 关键字
synchronized是给当前对象加锁,synchronized包围的代码称为临界区
2.1 synchronized加在代码块或普通方法上
对于所有带有synchronized关键字的非静态方法和synchronized(this)的代码块,它们都是同一把锁,只允许一个线程执行。
class DoSomeThingA {
synchronized public void printSomeThingA() throws InterruptedException {
System.out.println("正在执行---" + Thread.currentThread().getName());
System.out.println("正在退出---" + Thread.currentThread().getName());
}
}
2.2 synchronized关键字如果加在静态方法上
同一个class的所有对象,所有的静态类方法都是一把锁,同时只允许一个线程调用。
2.3 对于对象加锁
如果不是同一个对象,那么就不是同一把锁。例如同一个对象内的 synchronized(“AA”)、 synchronized(“BB”)、可以由多个线程并行执行,它们是对不同字符串对象进行了加锁。
对合理的代码块进行加锁,不仅能够保证多线程操作的安全性,还能够提高程序执行的效率。
synchronized 一般不对String对象进行加锁,因为字符串常量一般都是存放在常量池的,给字符串常量加锁可能会导致其它的一些问题,可以实例化一个对象 new Object() 给对象加锁。或者new String() 创建一个不同的字符串对象。
class DoSomeThingA {
public void printSomeThingA() throws InterruptedException {
synchronized("AA"){
System.out.println("正在执行printSomeThingA---" + Thread.currentThread().getName());
System.out.println("正在退出printSomeThingA---" + Thread.currentThread().getName());
}
}
public void printSomeThingB() throws InterruptedException {
synchronized("BB"){
System.out.println("正在执行printSomeThingB---" + Thread.currentThread().getName());
System.out.println("正在退出printSomeThingB---" + Thread.currentThread().getName());
}
}
}
2.4 synchronized会使线程私有内存变量与公有内存变量同步
public class SelfMain {
public static void main(String[] args) throws InterruptedException {
DoSomeThingB doSomeThing = new DoSomeThingB();
new Thread(() -> {
try {
doSomeThing.printSomeThingA();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
Thread.sleep(3000);
new Thread(() -> {
try {
doSomeThing.printSomeThingB();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
}
}
class DoSomeThingB {
private boolean running = true;
public void printSomeThingA() throws InterruptedException {
System.out.println("正在执行---" + Thread.currentThread().getName());
while (running){
synchronized ("AA"){
}
}
System.out.println("正在退出---" + Thread.currentThread().getName());
}
public void printSomeThingB() throws InterruptedException {
System.out.println("修改---" + Thread.currentThread().getName());
running = false;
}
}
如果不加synchronized (“AA”){ },即使另外一个线程调用printSomeThingB方法,修改running也不会使第一个线程停止。
同样如果将synchronized (“AA”){ },替换成Thread.sleep(1000)也会使,私有变量在内存中具有可见性。
3、锁重入
当一个线程在执行一个对象加了synchronized关键字的方法时,它就获得了这个对象的锁,此时它可以同时执行该对象其它加了synchronized关键字的方法和代码块。同时它也可以调用父类加了synchronized关键字的方法。