1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > 多线程入门必须知道的基础知识

多线程入门必须知道的基础知识

时间:2020-01-28 13:04:58

相关推荐

多线程入门必须知道的基础知识

多线程给我们带来了更好的资源利用和更好的程序响应,不过也为我们带来了必须要解决的麻烦!

多线程要解决的麻烦

线程安全:当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的。

简单点理解就是对象在不用额外同步的情况下也能支持多线程访问并且能获得正确的结果,那么这个对象就是线程安全的。

那么对象要如何实现线程安全呢,最直接的方法就是对象没有共享的数据,也就是没有属性,也就是无状态对象,无状态对象一定是线程安全的。

但是无状态对象并不能支持所有程序,一个程序的运行必定会出现有状态的对象用来存储一些数据,那么要怎么解决呢?解决线程安全主要有三个方向:

1、访问共享数据时使用同步;

2、不再共享数据;

3、共享数据不可变;

解决方案一:访问共享数据时使用同步

那么我们不妨在无状态对象的基础上来进行分析,现在我们在无状态对象的基础上加一个状态,这个对象就不是线程安全的了,那么要如何保证它继续线程安全呢,只需要保证这个状态的更新是原子性就行了,可以利用java.util.concurrent中的对象去替换,比如这个状态时int,我们可以把他换成AtomicInteger。

但是如果继续在给对象加状态,即时这些状态都是原子性的,比如就算有两个AtomicInteger属性,虽然他们各自的操作都可以实现原子性,但是组合起来就不是原子性,也就不会是线程安全的,这种情况下就只能通过锁来解决了,所有要访问这些状态的线程就必须获取锁,并且这个锁必须是同一个锁,也就是必须是由同一个锁来保护这几个状态的一致性。

之前文章中有专门讲过Java的内存模型,其中最主要的模块是主内存与工作内存,主内存存储一些可以共享的变量比如实例字段、静态字段和构成数组对象的元素,每个线程都拥有有属于自己的工作内存,每次一个线程需要使用这些共享的变量时,都是先加载到工作内存中在进行计算,而在把共享变量加载到工作内存中时可能其他线程已经修改了这个数据,也就是刚刚加载到工作内存的变量是过时、失效的数据,而我们大多数造成线程不安全的原因是程序基于一种可能失效的数据做出判断或者执行某个计算。

而volatile关键字就是用来确保在变量更新后及时的通知到其他线程的,但是即使及时通知到其他线程,由于线程对变量的操作也有可能不是原子性的,比如i++,要读取i、再计算、更新三个操作,如果在这个过程中i的值被其他线程改变,那么这个计算结果最终会覆盖前面的值而造成错误的结果,所以这种操作还是要考锁来控制,在i++的整个过程中都不允许其他线程对i进行访问,这样就保证了线程的安全性,也就是volatile能保证变量的可见性,而加锁能保证变量的可见性和原子性。

那么volatile什么时候可以使用呢?主要有以下情况:

1、对变量写操作不依赖变量的当前值,或者确保只有一个线程更新变量;

2、该变量不会与其他状态变量一起纳入不变性条件者;

3、在访问变量时不需要加锁;

解决方案二:不再共享数据

如果我们能保证数据只能在单线程内访问,那么就不需要同步,这种技术叫线程封闭,线程封闭主要有以下几种方法:

Ad-hoc线程封闭:完全由程序实现,一般的方法是某些特定的模块实现为一个单线程操作。比如对一个volatile变量确保只有一个线程去修改,那么就保证了线程的安全。

栈封闭:变量全部是局部变量,局部变量全部都只能在方法内部使用,其他线程肯定无法访问。这种方式要小心对象逃逸。

ThreadLocal类:ThreadLocal对象通常用于防止对可变的单实例变量或全局变量进行共享,比如JDBC的连接在多线程情况下就不是线程安全的,如果线程调用多个方法更新数据库,如果JDBC的连接不是用一个对象就无法保证事务,所以在ThreadLocal中设置一个自己的连接,不管这个线程调用了多少个更新数据库的方法,他们都是用的同一个连接,就能够保证事务。

解决方案二:共享数据不可变

保证线程安全的第二个方法是共享数据不可变,如果我们能保证获取到的数据不可变,那么就不用担心在获取到数据后被其他线程修改的风险。Java中有一个关键字final可以保证数据不可变。不过如果只有一个字段好控制,如果有多个呢?我们可以把多个放到一个对象里,然后这多个字段都用final修饰,最后这个对象也就是不可变的对象了,也可以安全的使用,下面是一个例子,如下图:

一个对象可以变成不可变的对象,那么它肯定是线程安全的,不过有些虽然是可变的但是我们肯定不会去改变它,比如创建时间因为我们不会去改变他所以它事实上也是不可变的对象,也是线程安全的。

所以一个对象我们应该怎么保证它的线程安全呢?大概分4个方向:

1、线程封闭:对象只能被一个线程拥有和修改;

2、只读共享:所有线程只能读不能改,不可变对象和事实不可变对象;

3、线程安全共享:对象内部自己实现线程安全,所以线程通过指定方法调用;

4、保护对象:特定的锁才能访问对象;

总结

内容较多也有点乱,用导图整理下重点内容如下图:

Java程序员日常学习笔记,如理解有误欢迎各位交流讨论!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。