导读:
非线程安全其实就是多个线程对同一对象中的实例变量进行访问发生,产生的后果就是出现脏读,也就是取到的数据其实是被更改过的。而线程安全就是获得的实例是通过同步处理的,不会出现脏读的现象。
1. 方法内的变量为线程安全,成员是非线程安全的
2. 多个对象多个锁
例如:
package com.example.zengjun.threadtest; import android.util.Log; public class Service { private static final String TAG = "Service"; private int num = 0; public static final String NAME_A = "A"; public static final String NAME_B = "B"; public synchronized void setName(String userName){ try { if(NAME_A.equals(userName)){ num = 100; Log.e(TAG,"name a set over"); Thread.sleep(1000); }else if(NAME_B.equals(userName)){ num = 200; Log.e(TAG,"name b set over"); } Log.e(TAG, userName + " num :" + num); } catch (InterruptedException e) { e.printStackTrace(); } } }
package com.example.zengjun.threadtest; public class ThreadA extends Thread { private Service mService; public ThreadA(Service service){ this.mService = service; } @Override public void run() { super.run(); mService.setName(Service.NAME_A); } }
package com.example.zengjun.threadtest; public class ThreadB extends Thread { private Service mService; public ThreadB(Service service){ this.mService = service; } @Override public void run() { super.run(); mService.setName(Service.NAME_B); } }
package com.example.zengjun.threadtest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 创建两个不同对象分别采用不同线程处理 Service serviceA = new Service(); Service serviceB = new Service(); new ThreadA(serviceA).start(); new ThreadB(serviceB).start(); } }
输出日志信息:
08-10 14:41:05.220 2093-2105/com.example.zengjun.threadtest E/Service: name a set over
08-10 14:41:05.230 2093-2106/com.example.zengjun.threadtest E/Service: name b set over
B num :200
08-10 14:41:06.220 2093-2105/com.example.zengjun.threadtest E/Service: A num :100
通过上面的输出结果可以是线程是异步的。这是因为多个线程访问多个对象的时候,JVM会创建多个锁。上面的示例就是创建了2个Servcie对象,所以就会产生两个锁。
3.synchronized锁重入
关键字synchronized具有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次获得该对象的锁。这也证明在一个synchronized方法/块的内容调用本类的其他synchronized方法/块时,是永远可以得到锁的。
可重入锁也支持在父子类继承的环境中。当存在父子类继承关系时,子类完全可以通过可重入锁调用父类的同步方法.
4. 同步不具有继承性
synchronized方法可以被重写但是必须加上synchronized关键字后才可以同步方法。
5. synchronized 同步语句块
用关键字synchronized申明方法在默写情况是由弊端的,比如线程A调用同步方法执行一个长时间的任务,那么线程B必须等待比较长的时间,在这种情况下采用同步代码块来解决。synchronized是对当前对象进行加锁,而synchronized代码块是对某一个对象进行加锁。
锁非this对象具有一定优点:如果一个类中有很多synchonized方法,这时虽然能实现同步,但会受到阻塞,所以影响运行效率。如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,不与其他this同步方法争夺this锁。则可大大提高效率
同步代码块可以实现一部分异步,一部分同步执行。这样也可以提到效率
6.静态同步方法synchronized方法和synchronized(class)代码块
synchronized关键字加到static静态方法上是给Class类上锁,而synchronized关键字加到非static静态方法上给对象上锁。
下面通过一个例子说明对象锁和Class锁是不同的锁。
package com.example.zengjun.threadtest; import android.util.Log; public class Service { private static final String TAG = "Service"; public synchronized static void methodA(){ try { Log.e(TAG,"thread name--->" + Thread.currentThread().getName() + ":" + System.currentTimeMillis()+ "enter methodA"); Thread.sleep(3000); Log.e(TAG,"thread name--->" + Thread.currentThread().getName() + ":" + System.currentTimeMillis()+ "leave methodA"); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized static void methodB(){ try { Log.e(TAG,"thread name--->" + Thread.currentThread().getName() + ":" + System.currentTimeMillis()+ "enter methodB"); Thread.sleep(3000); Log.e(TAG,"thread name--->" + Thread.currentThread().getName() + ":" + System.currentTimeMillis()+ "leave methodB"); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void methodC(){ try { Log.e(TAG,"thread name--->" + Thread.currentThread().getName() + ":" + System.currentTimeMillis()+ "enter methodC"); Thread.sleep(3000); Log.e(TAG,"thread name--->" + Thread.currentThread().getName() + ":" + System.currentTimeMillis()+ "leave methodC"); } catch (InterruptedException e) { e.printStackTrace(); } } }
package com.example.zengjun.threadtest; public class ThreadA extends Thread { private Service mService; public ThreadA(Service service){ this.mService = service; } @Override public void run() { super.run(); mService.methodA(); } }
package com.example.zengjun.threadtest; public class ThreadB extends Thread { private Service mService; public ThreadB(Service service){ this.mService = service; } @Override public void run() { super.run(); mService.methodB(); } }
package com.example.zengjun.threadtest; public class ThreadC extends Thread { private Service mService; public ThreadC(Service service){ this.mService = service; } @Override public void run() { super.run(); mService.methodC(); } }
package com.example.zengjun.threadtest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Service service = new Service(); new ThreadA(service).start(); new ThreadB(service).start(); new ThreadC(service).start(); } }
日志输出信息:
08-10 16:21:58.290 2377-2377/com.example.zengjun.threadtest E/dalvikvm: Could not find class 'android.support.v4.view.ViewCompat$OnUnhandledKeyEventListenerWrapper', referenced from method android.support.v4.view.ViewCompat.addOnUnhandledKeyEventListener
08-10 16:21:58.300 2377-2377/com.example.zengjun.threadtest E/dalvikvm: Could not find class 'android.view.WindowInsets', referenced from method android.support.v4.view.ViewCompat.dispatchApplyWindowInsets
08-10 16:21:58.360 2377-2377/com.example.zengjun.threadtest E/dalvikvm: Could not find class 'android.view.WindowInsets', referenced from method android.support.v4.view.ViewCompat.onApplyWindowInsets
08-10 16:21:58.360 2377-2377/com.example.zengjun.threadtest E/dalvikvm: Could not find class 'android.view.View$OnUnhandledKeyEventListener', referenced from method android.support.v4.view.ViewCompat.removeOnUnhandledKeyEventListener
08-10 16:21:58.360 2377-2377/com.example.zengjun.threadtest E/dalvikvm: Could not find class 'android.support.v4.view.ViewCompat$1', referenced from method android.support.v4.view.ViewCompat.setOnApplyWindowInsetsListener
08-10 16:21:58.500 2377-2377/com.example.zengjun.threadtest E/dalvikvm: Could not find class 'android.graphics.drawable.RippleDrawable', referenced from method android.support.v7.widget.AppCompatImageHelper.hasOverlappingRendering
08-10 16:21:58.690 2377-2389/com.example.zengjun.threadtest E/Service: thread name--->Thread-74:1565425318709enter methodA
08-10 16:21:58.700 2377-2391/com.example.zengjun.threadtest E/Service: thread name--->Thread-76:1565425318717enter methodC
08-10 16:22:01.700 2377-2389/com.example.zengjun.threadtest E/Service: thread name--->Thread-74:1565425321710leave methodA
08-10 16:22:01.700 2377-2391/com.example.zengjun.threadtest E/Service: thread name--->Thread-76:1565425321717leave methodC
08-10 16:22:01.790 2377-2390/com.example.zengjun.threadtest E/Service: thread name--->Thread-75:1565425321794enter methodB
08-10 16:22:04.790 2377-2390/com.example.zengjun.threadtest E/Service: thread name--->Thread-75:1565425324795leave methodB
通过日志输出信息可以看出C与A是异步的,A与B 是同步的。这是因为A、C持有的是对象锁,而A、B 持有的是Class锁。对象锁和Class锁时两个不同的锁。
7.死锁
java 线程死锁是一个经典的多线程问题,因为不同线程都在等待根本不可能被释放的锁,从而导致所有的任务都无法继续进行完成。在多线程中,死锁是必须避免的,因为这样会造成线程的假死。
下面通过一个例子说明:
package com.example.zengjun.threadtest; import android.util.Log; public class DeadThread implements Runnable { private static final String TAG = "DeadThread"; private String mFlag; private Object lock1 = new Object(); private Object lock2 = new Object(); public static final String FLAG_A = "A"; public static final String FLAG_B = "B"; public void setFlag(String flag){ this.mFlag = flag; } @Override public void run() { if(FLAG_A.equals(mFlag)){ synchronized (lock1){ try { Log.e(TAG,"Flag----->" + mFlag); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock2){ Log.e(TAG,"lock1-->lock2"); } } } if(FLAG_B.equals(mFlag)){ synchronized (lock2){ try { Log.e(TAG,"Flag----->" + mFlag); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lock1){ Log.e(TAG,"lock2-->lock1"); } } } } }
package com.example.zengjun.threadtest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); try { DeadThread deadThread = new DeadThread(); deadThread.setFlag(DeadThread.FLAG_A); Thread threadA = new Thread(deadThread); threadA.start(); // 这行代码很重要,不然很可能达不到死锁的效果 Thread.sleep(1000); deadThread.setFlag(DeadThread.FLAG_B); Thread threadB = new Thread(deadThread); threadB.start(); } catch (Exception e) { Log.e(TAG,"main catch"); e.printStackTrace(); } } }
日志输出信息:
08-10 16:39:02.770 2480-2492/com.example.zengjun.threadtest E/DeadThread: Flag----->A
08-10 16:39:03.770 2480-2493/com.example.zengjun.threadtest E/DeadThread: Flag----->B
两个线程相互等待对方的锁。
参考文献:
《javad多线程编程核心技术》