一、synchronized的作用
1、线程不安全
在IDE中运行下面的多线程代码
public class Safety {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
count++;
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
count++;
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}
两个线程各加5000,最终得到的结果应该是10000
我们运行第一次,得到的结果是
并不是10000。我们再运行第二次,得到:
也不是10000
这是怎么回事呢?
2、线程不安全产生的原因
要弄清楚上述问题产生的原因,我们就得明白count++这个操作在计算机中实际是怎么执行的
count++在计算机中执行时实际分成三步
1、load. 把数据从内存读取到CPU寄存器中
2、add. 把寄存器中的数据进行+1
3、save. 把寄存器中的数据,保存到内存中
我们知道,线程的调度顺序是随机的。不同线程调度时,这三步可能是交叉进行的,那么一个线程调用一次得到的结果就不一定能加1
比如下面这种情况,调度完t1,t2之后只能得到一次加1
若t1的load与add之间有多个t2,那么最终结果会更小
3、synchronized的作用
Java中引入了synchronized关键字,对方法或者代码进行加锁,便有效避免了同一个数据对象被多个线程同时访问
注意用synchronized加锁时,必须针对同一对象进行加锁
二、synchronized修饰实例方法或实例方法中的代码块
1、修饰实例方法
synchronized可以直接在类对象中修饰实例方法,写法如下
public synchronized 返回类型 方法名(参数){
}
修饰实例方法来完成我们上面的代码
class Counter{
public int count;
public synchronized void increase(){
count++;
}
}
public class demo1 {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread thread1 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
counter.increase();
}
});
Thread thread2 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
counter.increase();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter.count);
}
}
打印结果是正确的10000
2、修饰实例方法中的代码块
修饰实例方法等同于下面这种修饰代码块的写法
public 返回值类型 方法名(参数){
synchronized (this){
代码
}
}
那么上面的代码就应该写成
class Counter{
public int count;
public void increase(){
synchronized (this){
count++;
}
}
}
public class demo1 {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread thread1 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
counter.increase();
}
});
Thread thread2 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
counter.increase();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter.count);
}
}
运行结果也是正确的10000
3、针对同一对象加锁
不管是修饰实例方法还是修饰代码块,如果不是对同一对象加锁,那么两个线程还是随机调度,加锁无效
三、synchronized修饰静态方法或静态方法中的代码块
1、synchronized修饰静态方法
public synchronized static 返回类型 方法名(参数){
}
完整代码如下
class Counter{
public static int count = 0;
public synchronized static void increased(){
count++;
}
}
public class demo2 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
Counter.increased();
}
});
Thread thread2 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
Counter.increased();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(Counter2.count);
}
}
打印结果为正确的10000
2、synchronized修饰静态方法中的代码块
修饰静态方法等同于下面这种修饰代码块的写法
public static 返回值类型 方法名(参数){
synchronized (类名.class){
代码
}
}
完整代码如下
class Counter{
public static int count = 0;
public static void increased(){
synchronized (Counter.class){
count++;
}
}
}
public class demo2 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
Counter.increased();
}
});
Thread thread2 = new Thread(()->{
for (int i = 0; i < 5000; i++) {
Counter.increased();
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(Counter2.count);
}
}
打印结果为正确的10000
3、作用域
对于修饰静态方法或代码块而言,synchronized可以对这个类创建的所有对象实例起作用
四、直接在线程中修饰代码块
上面的程序还可以不用单独定义类,而是在线程中直接修饰代码块
这里locker类是两段代码共同锁定的对象
public class Safety {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(()->{
synchronized (locker){
for (int i = 0; i < 5000; i++) {
count++;
}
}
});
Thread t2 = new Thread(()->{
synchronized (locker){
for (int i = 0; i < 5000; i++) {
count++;
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}
打印结果也是正确的10000