android 正常业务逻辑中,一般并发用到的地方不多,不像服务器那样,但并发也是存在的,如果没处理好,会引起线程安全的问题。为了解决这些问题,我们会用些线程安全的数据容器,或者使用并发工具类,再或者自己对方法进行些加锁操作等,这里简单介绍几种常用的方法。
synchronized 是关键字,它是作用在JVM上的,我们一般把它用于方法只上,也可以用在方法内,举个栗子
public class TestDemo {
public synchronized static void test1(){
}
private synchronized void test2(){
}
Object object = new Object();
private void test3(){
synchronized (object){
}
}
static Object object2 = new Object();
private void test4(){
synchronized (object2){
}
}
private void test5(){
synchronized (TestDemo.class){
}
}
}
test1() 方法是静态的,所以这个方法中是同步的,因为 TestDemo.test1() 可以直接调用。
test2() 这个方法非静态,它是针对对象的,此时,如果创建一个对象 TestDemo testDemo = new TestDemo(),然后开启了多条线程执行 test2() 方法,此时test2() 里面是线程安全的;但如果开启了多条线程,在每条线程里执行 new TestDemo().test2() 方法,则它不是线程安全的,因为此时针对的是对象而非是类。
test3() 此时 synchronized 关键字是在方法里,它锁定的是类的非静态成员变量 object ,此时 test3() 和 test2() 一样,是否安全根据调用者来判断。
test4() 它和test3()的区别就是此时锁的对象 object2 是静态成员变量,所有的同类型对象都共享静态值,所以此时锁定的是类,也就是说开启多条线程,每个线程都创建一个对象执行 test4() 方法,该方法仍然是线程安全的。
test5() 它锁的对象是 TestDemo.class ,我们知道java中同类型对象可以创建很多个,但他们共用同一个 .class ,所以它和 test4() 是一样的,线程安全。
lock 是类级别的锁,它可以锁定任何一段代码,它的作用和 synchronized 类似;它有个锁和释放锁的概念,记得一定要释放,否则可能造成死锁,它锁定的是针对 lock 对象,共用lock的代码,比如
Lock lock = new ReentrantLock();
private void test6(int i){
try {
lock.lock();
// ...
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
此时它的功能和 test3() 一样。假如把 lock 变为静态成员变量,那么它的功能就和 test4() 一样了。
以上两种就是常用的锁,还有一个 ReadWriteLock 是针对上面做了优化,说是优化,其实是从业务方法进行的。比如说数据的读和写,我们一般都是互斥的,防止数据错乱,但假如有些需求,读的话仅仅是展示而非有其他的操作,那么同时去读取是不是更节省时间,假如读数据需要50毫秒,有三条线程去读,如果使用 sychronized 或 Lock ,由于它们是互斥的,所以会耗时150毫秒,同时去读的话就可以提高效率;那么可能有人会说,读的方法不用同步锁就可以,那会引起一开始说的问题,读和写互斥,如果不加同步,正在读的时候,忽然有数据写入,那就糟了,所以还是要加锁,此时 ReadWriteLock 就登场了。
ReadWriteLock rwl = new ReentrantReadWriteLock();
private void set(){
try {
rwl.writeLock().lock();
System.out.println("write start " + Thread.currentThread().getName());
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("write end " + Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
} finally {
rwl.writeLock().unlock();
}
}
private void get(){
try {
rwl.readLock().lock();
System.out.println("read start " + Thread.currentThread().getName());
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("read end " + Thread.currentThread().getName());
} catch (Exception e) {
e.printStackTrace();
} finally {
rwl.readLock().unlock();
}
}
public static void main(String[] args){
final TestDemo testDemo = new TestDemo();
for (int i = 0; i < 5; i++){
final int index = i;
new Thread(new Runnable() {
@Override
public void run() {
if (index > 3){
testDemo.set();
} else {
testDemo.get();
}
}
}).start();
}
}
打印出来的值为
read start Thread-0
read end Thread-0
write start Thread-4
write end Thread-4
read start Thread-1
read start Thread-3
read start Thread-2
read end Thread-1
read end Thread-3
read end Thread-2
对于线程安全,如何选择使用的工具,这个根据我们的业务作出选择。