多线程(三):synchronized解决线程不安全

发布于:2023-01-05 ⋅ 阅读:(307) ⋅ 点赞:(0)

往期回顾:
❀❀多线程(一):认识和创建一个线程❀❀
❀❀多线程(二):Thread类的常见方法❀❀


一. 为什么会出现线程不安全

1. 什么是线程不安全

简单来说:如果多线程环境下代码运行的结果总是符合我们预期的,就说明这个程序是线程安全的.

2. 演示线程不安全

public class SafeThread {
    private static int i = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread thread0 = new Thread(){
            @Override
            public void run() {
                for (; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+ ": i = " + i);
                }
            }
        };
        Thread thread1 = new Thread(){
            @Override
            public void run() {
                for (; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+ ": i = " + i);
                }
            }
        };
        thread0.start();
        thread1.start();
        thread0.join();
        thread1.join();
        System.out.println("i = " + i);
    }
}

输出结果:

在这里插入图片描述
可以发现,程序的输出与我们预期的输出结果不符合,当i < 10时,程序应该结束循环,但是输出的i值却为11,说明程序出现问题,这就是多线程并行运行中所带来的问题.

3. 线程不安全的原因

1.线程之间抢占式执行
2.修改共享数据
3.某些操作不满足原子性
4.内存可见性(可见性: 一个线程对共享变量值的修改,能够及时地被其他线程看到)
5.指令重排序

三. 利用synchronized解决线程不安全

1.synchronized的特性

1.互斥

使用synchronized加锁的代码块会互斥的访问,当一个线程获得锁🔒时,其他线程运行到synchronized会进入阻塞等待状态,直到该线程释放锁🔒资源,其他线程才能被OS来唤醒.

2.内存刷新

synchronized的工作流程:
1.获得🔒
2.将变量从主内存拷贝到工作内存
3.执行代码
4.将变量从工作内存拷贝到主内存
5.释放🔒
所以,使用synchronized关键字可以解决内存可见性问题,除此之外,volatile关键字也可以解决内存可见性问题

3.可重入

synchronized保证线程不会把自己锁死,即是可重入的.

public class ReentrantLock {
    private static synchronized void fun1(){
        System.out.println("fun1");
        fun2();
    }

    private static synchronized void fun2() {
        System.out.println("fun2");
    }

    public static void main(String[] args) {
        fun1();
    } 
}

在这里插入图片描述

这是因为,在synchronized的的内部, 包含了 “线程持有者” 和 “计数器” 两个信息.
如果线程加锁时,发现已经被占用,而占用着是自己的时候,就可以继续使用锁,并且使计数器加1,解锁的时候计数器依次递减,等于0时释放锁资源

2.synchronized解决线程不安全

对于上面的例子,可以对i++的操作进行加锁

public class SafeThread {
    private static int i = 0;
    private static final Object Lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread thread0 = new Thread() {
            @Override
            public void run() {
                for (int j = 0 ; j < 5 ; ++j) {
                    synchronized (Lock){
                        i++;
                        System.out.println(Thread.currentThread().getName() + ": i = " + i);
                    }

                }
            }
        };
        Thread thread1 = new Thread() {
            @Override
            public void run() {
                for (int j = 0 ; j < 5 ; ++j) {
                    synchronized (Lock){
                        i++;
                        System.out.println(Thread.currentThread().getName() + ": i = " + i);
                    }

                }
            }
        };
        thread0.start();
        thread1.start();
        thread0.join();
        thread1.join();
        System.out.println("i = " + i);
    }
}

在这里插入图片描述

3.synchronized的使用

1.直接修饰普通方法

public class Synchronized {
    public synchronized void methond() {
    
   }
}

2.修饰静态方法

public class Synchronized {
    public synchronized static void method() {
    
   }
}

3.修饰代码块

1.锁当前对象

public class Synchronized {
    public void method() {
        synchronized (this) {
            // 代码块
       }
   }
}

2.锁类对象

public class Synchronized {
    public void method() {
        synchronized (Synchronized.class) {
        // 任意类对象
        // 代码块
       }
   }
}

3.任意对象加锁

public class Synchronized {
	private final static Object Lock = new Object();
    public void method() {
        synchronized (Lock) {
       		//任意对象
       		// 代码块
       }
   }
}
系列文章
❀❀❀多线程(一):认识和创建一个线程❀❀❀
❀❀❀多线程(二):Thread类的常见方法❀❀❀

网站公告

今日签到

点亮在社区的每一天
去签到