什么是线程安全?
当多个线程访问一个对象时,如果不用考虑这些线程的访问方式和如何进行交替执行,主程序中也不需要进行额外的同步,调用这个对象都能获得正确的结果,那么这个对象是线程安全的。
进程与线程
在讲线程安全之前,我们要知道进程与线程的区别
进程
当我们使用adb 命令,shell进去输入ps,会看到如下的信息
这里的每一行信息都代表着一个进程
进程是系统中正在运行的一个程序,程序一旦运行就是进程。进程可以看成程序执行的一个实例。进程是系统资源分配的独立实体,每个进程都拥有独立的地址空间。一个进程无法访问另一个进程的变量和数据结构,如果想让一个进程访问另一个进程的资源,需要使用进程间通信,比如管道,文件,套接字等。
线程
一个进程可以拥有多个线程,每个线程使用其所属进程的栈空间。进程想要执行任务需要依赖线程,换句话说就是进程中的最小执行单位就是线程,并且一个进程中至少有一个线程。
多线程
提到多线程这里要说两个概念,就是串行和并行,搞清楚这个我们才能更好的理解多线程。
所谓串行其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子,我们下载多个文件,在串行中它是按照一定的顺序去进行下载的,也就是说必须等下载完A之后,才能开始下载B,它们在时间上是不可能发生重叠的。如果是并行下载多个文件,开启多条线程,多个文件同时进行下载,这里是严格意义上的在同一时刻发生的,并行在时间上是重叠的。就比如我们开启某个下载器,下载文件,可以同时下载多个文件,这个就可以理解为它启用了多线程
什么是线程安全
- 当多个线程访问一个对象时,如果不用考虑这些线程的访问方式和如何进行交替执行,主程序中也不需要进行额外的同步,调用这个对象都能获得正确的结果,那么这个对象是线程安全的。
我现在举一个例子,比如有个地方总共卖50张票票,有五个窗口在同时售卖
private int index=50;//总共50张票
public void startThread(int number){//这里会传入5
for (int i=0;i<number;i++){
new MyThread().start();
}
}
//售卖方法 每调用一次 总票数-1
private void subThread(){
if(index!=0){
Log.i("info","subThread =="+index);
index--;
}
}
// 代表一个售票员进行卖票
class MyThread extends Thread{
@Override
public void run() {
while (index>0){
subThread();
}
}
}
下面是输出结果
I/info: subThread ==50
I/info: subThread ==49
I/info: subThread ==48
I/info: subThread ==47
I/info: subThread ==46
I/info: subThread ==45
I/info: subThread ==44
I/info: subThread ==43
I/info: subThread ==42
I/info: subThread ==41
I/info: subThread ==40
I/info: subThread ==39
I/info: subThread ==38
I/info: subThread ==37
I/info: subThread ==36
I/info: subThread ==35
I/info: subThread ==34
I/info: subThread ==33
I/info: subThread ==32
I/info: subThread ==31
I/info: subThread ==30
I/info: subThread ==29
I/info: subThread ==28
I/info: subThread ==27
I/info: subThread ==26
I/info: subThread ==25
I/info: subThread ==24
I/info: subThread ==23
I/info: subThread ==22
I/info: subThread ==21
I/info: subThread ==20
I/info: subThread ==19
I/info: subThread ==18
I/info: subThread ==17
I/info: subThread ==16
I/info: subThread ==15
I/info: subThread ==14
I/info: subThread ==13
I/info: subThread ==12
I/info: subThread ==11
I/info: subThread ==10
I/info: subThread ==9
I/info: subThread ==8
I/info: subThread ==7
I/info: subThread ==6
I/info: subThread ==5
I/info: subThread ==4
I/info: subThread ==3
I/info: subThread ==2
I/info: subThread ==1
I/info: subThread ==4
从上面的结果我们可以看到最后一点,有两个窗口同时在卖第四张票,就掉代表了这个程序是不安全的
那么我们怎么避免这种事情让线程变得安全呢
同步锁
我们为了避免这种请求,通常会使用线程锁synchronized来保证线程安全
private synchronized void subThread(){
if(index!=0){
Log.i("info","subThread =="+index);
index--;
}
}
我们只需要在subThread的方法前面加上synchronized 关键字,就可以轻松解决这个问题
synchronized的介绍
synchronized关键字是java并发编程中必不可少的工具。它一次只允许一个线程进入特定代码段,从而避免多线程同时修改同一数据。就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。
synchronized的修饰范围
- 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
- 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
- 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
-
修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
具体使用环境如下
public class SynchorizedTest {
//静态方法,锁住的是类对象
public static synchronized void staticMethod() {
...
}
//实例方法,锁住的是类的实例对象
public synchronized void method() {
...
}
public void methodClass() {
//同步代码块,锁住的是类对象
synchronized (SynchorizedTest.class) {
...
}
}
public void methodThis() {
//同步代码块,锁住的是类的实例对象
synchronized (SynchorizedTest.this) {
...
}
}
public void methoObject(Object object) {
//同步代码块,锁住的是配置的实例对象
synchronized (object) {
...
}
}
}
synchronized是围绕一个被称为内部锁或监视锁的内部实体实现的(Api规范里经常将其简单的称之为“monitor”)。内部锁在同步的两个方面发挥作用:强制独占访问对象状态和建立对可见性必不可少的happens-before关系。
每个对象都有一个与之关联的内部锁。按照惯例,一个需要排他和持有对象字段的线程在访问对象之前获得该对象的内部锁,在使用该对象过后释放该内部锁。一个线程获得锁和释放锁的过程称之为该线程持有该内部锁,只要有一个线程持有一个内部锁,其他线程就不能获得该锁,当其试图获取该锁时,将被阻塞。
当一个线程释放一个内部锁后,在紧随该操作之后的任何获得该锁的操作将于该操作建立happens-before关系
Collections中的synchronized
众所周知ArrayList是非线程安全的,在多线程的情况下,向list插入数据的时候,可能会造成数据丢失的情况.并且一个线程在遍历List,另一个线程修改List,会报ConcurrentModificationException(并发修改异常)错误.
解决这个错误有一个方案是使用Collections.synchronizedList方法可以解决这个问题
/**
* Returns a synchronized (thread-safe) list backed by the specified
* list. In order to guarantee serial access, it is critical that
* <strong>all</strong> access to the backing list is accomplished
* through the returned list.<p>
*
* It is imperative that the user manually synchronize on the returned
* list when iterating over it:
* <pre>
* List list = Collections.synchronizedList(new ArrayList());
* ...
* synchronized (list) {
* Iterator i = list.iterator(); // Must be in synchronized block
* while (i.hasNext())
* foo(i.next());
* }
* </pre>
* Failure to follow this advice may result in non-deterministic behavior.
*
* <p>The returned list will be serializable if the specified list is
* serializable.
*
* @param <T> the class of the objects in the list
* @param list the list to be "wrapped" in a synchronized list.
* @return a synchronized view of the specified list.
*/
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
我们可以看到Collections类中有synchronizedList这个方法,里面调用了SynchronizedList这个类
接下来我们看这个类中的代码
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return list.equals(o);}
}
public int hashCode() {
synchronized (mutex) {return list.hashCode();}
}
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
public int indexOf(Object o) {
synchronized (mutex) {return list.indexOf(o);}
}
public int lastIndexOf(Object o) {
synchronized (mutex) {return list.lastIndexOf(o);}
}
public boolean addAll(int index, Collection<? extends E> c) {
synchronized (mutex) {return list.addAll(index, c);}
}
public ListIterator<E> listIterator() {
return list.listIterator(); // Must be manually synched by user
}
public ListIterator<E> listIterator(int index) {
return list.listIterator(index); // Must be manually synched by user
}
public List<E> subList(int fromIndex, int toIndex) {
synchronized (mutex) {
return new SynchronizedList<>(list.subList(fromIndex, toIndex),
mutex);
}
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
synchronized (mutex) {list.replaceAll(operator);}
}
@Override
public void sort(Comparator<? super E> c) {
synchronized (mutex) {list.sort(c);}
}
/**
* SynchronizedRandomAccessList instances are serialized as
* SynchronizedList instances to allow them to be deserialized
* in pre-1.4 JREs (which do not have SynchronizedRandomAccessList).
* This method inverts the transformation. As a beneficial
* side-effect, it also grafts the RandomAccess marker onto
* SynchronizedList instances that were serialized in pre-1.4 JREs.
*
* Note: Unfortunately, SynchronizedRandomAccessList instances
* serialized in 1.4.1 and deserialized in 1.4 will become
* SynchronizedList instances, as this method was missing in 1.4.
*/
private Object readResolve() {
return (list instanceof RandomAccess
? new SynchronizedRandomAccessList<>(list)
: this);
}
}
我们可以看到在这个类中,所有对List<>集合的操作都加上了synchronized 关键字
从上图中我们可以看到在Collections类中有很多类似synchronizedList的方法,也可以对Map、Set等集合做处理