怎么解决线程的安全问题呢?java
基本上全部解决线程安全问题的方式都是采用“序列化临界资源访问”的方式,即在同一时刻只有一个线程操做临界资源,操做完了才能让其余线程进行操做,也称做同步互斥访问。安全
在Java中通常采用synchronized
和Lock
来实现同步互斥访问。多线程
首先咱们先来了解一下互斥锁,互斥锁:就是能达到互斥访问目的的锁。ide
若是对一个变量加上互斥锁,那么在同一时刻,该变量只能有一个线程能访问,即当一个线程访问临界资源时,其余线程只能等待。优化
在Java中,每个对象都有一个锁标记(monitor),也被称为监视器,当多个线程访问对象时,只有获取了对象的锁才能访问。this
在咱们编写代码的时候,可使用synchronized
修饰对象的方法或者代码块,当某个线程访问这个对象synchronized
方法或者代码块时,就获取到了这个对象的锁,这个时候其余对象是不能访问的,只能等待获取到锁的这个线程执行完该方法或者代码块以后,才能执行该对象的方法。spa
咱们来看个示例进一步理解synchronized
关键字:线程
public class Example { public static void main(String[] args) { final InsertData insertData = new InsertData(); new Thread() { public void run() { insertData.insert(Thread.currentThread()); }; }.start(); new Thread() { public void run() { insertData.insert(Thread.currentThread()); }; }.start(); } } class InsertData { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); public void insert(Thread thread){ for(int i=0;i<5;i++){ System.out.println(thread.getName()+"在插入数据"+i); arrayList.add(i); } } }
这段代码的执行是随机的(每次结果都不同):code
Thread-0在插入数据0` `Thread-1在插入数据0` `Thread-1在插入数据1` `Thread-1在插入数据2` `Thread-1在插入数据3` `Thread-1在插入数据4` `Thread-0在插入数据1` `Thread-0在插入数据2` `Thread-0在插入数据3` `Thread-0在插入数据4
如今咱们加上synchronized
关键字来看看执行结果:对象
public synchronized void insert(Thread thread){ for(int i=0;i<5;i++){ System.out.println(thread.getName()+"在插入数据"+i); arrayList.add(i); } }
输出:
Thread-0在插入数据0` `Thread-0在插入数据1` `Thread-0在插入数据2` `Thread-0在插入数据3` `Thread-0在插入数据4` `Thread-1在插入数据0` `Thread-1在插入数据1` `Thread-1在插入数据2` `Thread-1在插入数据3` `Thread-1在插入数据4
能够发现,线程1
会等待线程0
插入完数据以后再执行,说明线程0
和线程1
是顺序执行的。
从这两个示例中,咱们能够知道synchronized
关键字能够实现方法同步互斥访问。
在使用synchronized
关键字的时候有几个问题须要咱们注意:
synchronized
的方法时,其余synchronized
的方法是不能被访问的,道理很简单,一个对象只有一把锁;synchronized
方法时,其余线程能够访问该对象的非synchronized
方法,由于访问非synchronized
不须要获取锁,是能够随意访问的;object1
的synchronized
方法fun1
,另一个线程B须要访问对象object2
的synchronized
方法fun1
,即便object1
和object2
是同一类型),也不会产生线程安全问题,由于他们访问的是不一样的对象,因此不存在互斥问题。synchronized
代码块对于咱们优化多线程的代码颇有帮助,首先咱们来看看它长啥样:
synchronized(synObject) {}
当在某个线程中执行该段代码时,该线程会获取到该对象的synObject
锁,此时其余线程没法访问这段代码块,synchronized
的值能够是this
表明当前对象,也能够是对象的属性,用对象的属性时,表示的是对象属性的锁。
有了synchronized
代码块,咱们能够将上述添加数据的例子修改为以下两种形式:
class InsertData { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); public void insert(Thread thread){ synchronized (this) { for(int i=0;i<100;i++){ System.out.println(thread.getName()+"在插入数据"+i); arrayList.add(i); } } } }
上述代码就是synchronized
代码块添加锁的两种方式,能够发现添加synchronized
代码块,要比直接在方法上添加synchronized
关键字更加灵活。
当咱们用sychronized
关键字修饰方法时,这个方法只能同时让一个线程访问,可是有时候极可能只有一部分代码须要同步,而这个时候使用sychronized
关键字修饰的方法是作不到的,可是使用sychronized
代码块就能够实现这个功能。
而且若是一个线程执行一个对象的非static synchronized
方法,另一个线程须要执行这个对象所属类的static synchronized
方法,此时不会发生互斥现象,由于访问static synchronized
方法占用的是类锁,而访问非static synchronized
方法占用的是对象锁,因此不存在互斥现象。
来看一段代码:
class InsertData { private ArrayList<Integer> arrayList = new ArrayList<Integer>(); private Object object = new Object(); public void insert(Thread thread){ synchronized (object) { for(int i=0;i<100;i++){ System.out.println(thread.getName()+"在插入数据"+i); arrayList.add(i); } } } }
执行结果:
执行insert` `执行insert1` `执行insert1完毕` `执行insert完毕