线程的安全性

线程的安全性

什么是线程的安全性?

当多个线程访问某个类,不管运行时环境采用何种调度方式或者如何交替执行,并且在主调代码中不需要任何的同步或者协同,这个类都能表现出正确的行为,那么这个类就是线程安全的。

什么是线程不安全?

​ 多线程访问某个类时,得不到正确的结果,就称为线程不安全

原子性操作

什么是原子性?简单来说就是要么成功,要么失败,不存在第三种情况,这样,就可以在一定程度上避免线程同步出现的问题,这样就可以减少线程安全问题。

具体怎么操作呢?

synchronized关键字能够帮助我们实现线程的原子性操作

	private static int num = 0;
	//并发10
	private static CountDownLatch countDownLatch = new CountDownLatch(10);
	//让num自增,加上关键字synchronized
	public static synchronized void inCreate() {
		num++;
	}
	//让线程循环10次,每次一百下,调用上面的inCreate()方法,最终结果应该是1000,但是如果不加上synchronized关键字不进行原子性操作的话,最终的结果会引发安全性问题,结果总不是1000
	public static void main(String[] args) {
		for (int i = 0; i < 10; i++) {
			new Thread(()->{
				for (int j = 0; j < 100; j++) {
					inCreate();
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				countDownLatch.countDown();
			}).start(); ;
		}
		while(true) {
			if(countDownLatch.getCount() == 0) {
				System.out.println(num);
				break;
			}
		}
		
	}

那我们又可以抛出一个问题,这个synchronized关键字到底是干啥的,他为什么能实现原子性呢?

了解到这些就要先知道什么是内置锁和互斥锁

​ 内置锁:每个java对象都可以用作一个实现同步的锁,这些锁称为内置锁,线程进入同步代码块或方法的时候就会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。

​ 互斥锁:内置锁就是一个互斥锁,这就意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到B释放这个锁,如果不释放,那么A线程就将继续等待下去。

synchronized不能修饰类,但是可以修饰一个方法,当synchronized修饰一个方法时,这个方法就已经使用了一个内置锁。

​ 当修饰一个普通方法时,synchronized关键字只会锁住对象的实例,以下代码输出的结果是。

Thread-0
Thread-1

只锁住对象的实例,不会影响多个线程同时执行一个对象中的方法,那么现在只有这个实例被内置锁锁住了。

public synchronized void out() {
		try {
			Thread.sleep(5000L);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName());
	}
	
	public static void main(String[] args) {
		
		Thread04 thread=new Thread04();
		Thread04 thread2=new Thread04();
		
		new Thread(()->{
			thread.out();
		}).start();
		
		new Thread(()->{
			thread2.out();
		}).start();
	}

当synchronized修饰静态方法就会锁住整个类,当将上面普通方法修改为静态方法输出的结果如下

Thread-0
    
Thread-1

结果中有一处明显的停顿,这是当Thread-0执行后释放资源才让Thread-1开始执行,就是当synchronized修饰静态方法时,就只能有一个线程占用资源,其他的线程只能等待,那么现在整个类就被一个内置锁锁住了

当修饰一个代码块时,就锁住了传入的对象的实例,和锁住普通方法效果相同

volatile关键字

​ 它是干什么的?

​ 能且仅能修饰变量

​ 保证该变量的可见性,但仅保证可见性,不保证原子性

​ 禁止指令重排序

​ A、B两个线程同时读取volatile关键字修饰的对象,A读取之后修改了变量的值,修改之后,对B线程来说,是可见的

​ 在哪里用的?

​ 可以用作线程开关

​ 修饰对象实例,将实例转化为一个单例,进制指令重排序

那什么是单例呢?

​ 学过Spring的都知道,当一个对象加载进Spring时就将这个对象默认修改为单例,那单例是啥?单例就是每次使用这个对象时,永远使用的是一个实例。

​ 单例有什么种类吗?

​ 单例分为懒汉和饿汉模式

​ 懒汉模式:在需要的时候再实例化,当实例化比较耗时而且有多个线程访问时,就会成为多例状态,所以懒汉模式并不安全

​ 饿汉模式:在类加载的时候就已经实例化,无论之后能不能用到,所以如果用不到就会比较占资源,但是线程较安全

​ 那么怎么将懒汉模式变成线程安全或者相对安全呢,用最有效的一个方法,就是上面的volatile关键字,用它来修饰当前对象,禁止指令重排序,让所有的线程使用对当前对象时用的都是一个实例,以达到单例的效果。

​ 线程安全性问题的成因:

​ 1.多线程环境

​ 2.多个线程操作同一共享资源

​ 3.对该共享资源进行非原子性操作

​ 如何避免:

​ 打破成因中任意一点

​ 1.多线程环境–将多线程改成单线程

​ 2.多个线程操作同一共享资源–不共享资源(ThreadLocal、不共享、操作无状态化、不可变)

​ 3.对该共享资源进行非原子性操作–将非原子性操作改为原子性操作(加锁、使用JDK自带的原子性操作的类、JUC提供的响应并发工具类)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值