线程安全问题

当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。但是做读操作是不会发生数据冲突问题。

先了解一下java内存模型

上图描述了一个多线程执行场景。 线程 A 和线程 B 分别对主内存的变量进行读写操作。其中主内存中的变量共享变量,也就是说此变量只此一份,多个线程间共享。但是线程不能直接读写主内存的共享变量,每个线程都有自己的工作内存,线程需要读写主内存的共享变量时需要先将该变量拷贝一份副本到自己的工作内存,然后在自己的工作内存中对该变量进行所有操作,线程工作内存对变量副本完成操作之后需要将结果同步至主内存。

线程对变量赋值的流程

那么问题来了,线程工作内存怎么知道什么时候又是怎样将数据同步到主内存呢? 这里就轮到 JMM 出场了。 JMM 规定了何时以及如何做线程工作内存与主内存之间的数据同步。

对 JMM 有了初步的了解,简单总结一下原子性、可见性、有序性

原子性:

对共享内存的操作必须是要么全部执行直到执行结束,且中间过程不能被任何外部因素打断,要么就不执行。
可见性:

多线程操作共享内存时,执行结果能够及时的同步到共享内存,确保其他线程对此结果及时可见。
有序性:

程序的执行顺序按照代码顺序执行,在单线程环境下,程序的执行都是有序的,但是在多线程环境下,JMM 为了性能优化,编译器和处理器会对指令进行重排,程序的执行会变成无序。

解决办法:
使用多线程之间同步synchronized或使用锁(lock)。将可能会发生数据冲突问题,只能让当前一个线程进行执行。代码执行完成后释放锁,锁释放后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。

方式①:同步代码块

就是将可能会发生线程安全问题的代码,给包括起来。

synchronized(obj){

 //可能会发生线程冲突问题

}

对象如同锁,持有锁的线程可以在同步中执行 ;没持有锁的线程即使获取CPU的执行权,也进不去 
同步的前提: 
1,必须要有两个或者两个以上的线程 
2,必须是多个线程使用同一个锁 
必须保证同步中只能有一个线程在运行 
好处:解决了多线程的安全问题 
弊端:多个线程需要判断锁,较为消耗资源、抢锁的资源。

方式②:同步函数

在方法上修饰synchronized 称为同步函数。同步函数使用this锁;

public synchronized void sale() {
		// 前提 多线程进行使用、多个线程只能拿到一把锁。
		// 保证只能让一个线程 在执行 缺点效率降低
		// synchronized (oj) {
		if (count > 0) {
			try {
				Thread.sleep(50);
			} catch (Exception e) {
				// TODO: handle exception
			}
			System.out.println(Thread.currentThread().getName() + ",出售第" + (100 - count + 1) + "票");
			count--;
		}
		// }
	}

方式③:静态同步函数
方法上加上static关键字,使用synchronized 关键字修饰 或者使用类.class文件。
静态的同步函数使用的锁是该函数所属字节码文件对象;

synchronized (ThreadTrain.class) {
			System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
			trainCount--;
			try {
				Thread.sleep(100);
			} catch (Exception e) {
			}
}

总结:

synchronized 修饰方法使用锁是当前this锁。
synchronized 修饰静态方法使用锁是当前类的字节码文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值