Synchronized是比较常见的,我这里把它归到实现线程同步的第二类,Synchronized、ReentrantLock、ReentrantReadWriteLock、Condition ,这一类中主要是锁。你可以把Synchronized理解为同步锁。它可以用来修饰对象、方法、代码块、类,它的作用就是保证这个对象、方法或者代码块在同一时间内只能被一个调用者访问。
大致的使用方法有下面几种
//同步类方法
public synchronized void method(){
}
public void method(){
//同步代码块
synchronized(object){
}
}
public void method(){
//同步这个类
synchronized(Class.class){
}
}
//同步这个类
public static synchronized void method(){
}
一、同步类方法:public synchronized void method()
当多个线程同时调用一个类的实例的 同步类方法 时,这个方法会同步执行,也就是说一旦一个线程先调用了这个方法,后面的线程如果也调用了这个方法就需要等待前一个线程先执行完毕。
代码演示,新建一个类SyncBean
package com.hpplay.threadcommunicationdemo;
import android.util.Log;
/**
* Created by DON on 2017/3/4.
*/
public class SyncBean {
public static final String TAG = "syncbean";
//加了同步的类方法
public synchronized void testSync(){
Log.e(TAG," start run sync method");
try {
Thread.sleep(2000);
}catch (Exception e){
}
Log.e(TAG," end run sync method");
}
//普通方法,跟同步方法作比较
public void testNormal(){
Log.e(TAG," start run normal method");
try {
Thread.sleep(2000);
}catch (Exception e){
}
Log.e(TAG," end run normal method");
}
}
新建一个两个线程ThreadA和ThreadB,用来演示调用:
package com.hpplay.threadcommunicationdemo;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* Created by DON on 2017/3/3.
*/
public class SynchronizedFragment extends Fragment{
private SyncBean syncBean = new SyncBean();//创建一个 SyncBean的实例,用来下面的ThreadA和ThreadB并发访问
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
RelativeLayout relativeLayout = new RelativeLayout(getActivity());
return relativeLayout;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
//程序入口
ThreadA threadA = new ThreadA();
ThreadB threadB = new ThreadB();
threadA.start();
threadB.start();
//threadA 和 threadB 并发执行,如果是普通的 类方法,会同时执行,如果是同步方法,后调用的就需要等先调用的
}
public class ThreadA extends Thread{
@Override
public void run() {
super.run();
syncBean.testSync();//调用同步类方法
// syncBean.testNormal();//调用普通类方法
}
}
public class ThreadB extends Thread{
@Override
public void run() {
super.run();
syncBean.testSync();//调用同步类方法
// syncBean.testNormal();//调用普通类方法
}
}
}
看输出结果
![](https://img.haomeiwen.com/i2691208/efcf70f546ec0743.png)
分析运行结果:start 和 end 是成对出现的,并且第二对的输出时间(黑色框圈住部分)是在第一对之后。这就说明了在两个线程同时调用
syncBean.testSync();
时,在首先调用的线程方法执行完毕之前,第二个线程发生了阻塞,直到第一个线程将方法执行完毕,第二个线程才开始执行。对比运行
syncBean.testNormal();
,查看控制台输出
03-04 14:25:57.729 26639-26688/? E/syncbean: start run normal method
03-04 14:25:57.729 26639-26689/? E/syncbean: start run normal method
03-04 14:25:59.729 26639-26688/? E/syncbean: end run normal method
03-04 14:25:59.729 26639-26689/? E/syncbean: end run normal method
两个线程几乎同时开始执行方法,几乎同时输出 start ,然后两秒之后,又同时输出 end,这就是异步,假设在syncBean.testNormal();
中修改了某个变量的值,第一个线程改为3,第二个线程改为5,那么第一个线程沉睡两秒之后再去读取的时候就是5,并不能拿到它之前的3,这是一个风险,如果使用 synchronized 修饰就不会出现这种情况。
二、同步代码块:synchronized(object){ }
修改testSync()
方法
public void testSync(){
Log.e(TAG," before start run sync method");//这里的代码不在synchronized包括,如果多个线程访问,会并发(异步)执行
synchronized (TAG) {//代码从这里开始同步
Log.e(TAG, " start run sync method");
try {
Thread.sleep(2000);
} catch (Exception e) {
}
Log.e(TAG, " end run sync method");
}//代码同步到这里结束
}
运行,查看输出
03-04 15:15:42.860 28344-28369/? E/syncbean: before start run sync method
//线程A先执行before
03-04 15:15:42.860 28344-28369/? E/syncbean: start run sync method
//线程A执行start 然后进入沉睡
03-04 15:15:42.860 28344-28370/? E/syncbean: before start run sync method
//线程B异步执行before,然后碰到synchronized (TAG) ,发生阻塞
03-04 15:15:44.860 28344-28369/? E/syncbean: end run sync method
//线程A执行end
03-04 15:15:44.861 28344-28370/? E/syncbean: start run sync method
//线程B开始接着执行start
03-04 15:15:46.861 28344-28370/? E/syncbean: end run sync method
//线程B执行end
从输出信息可以看出在synchronized (TAG) {}
括号外的代码是异步的,括号内的代码是同步的,这就是用synchronized 实现代码块的同步。
三、同步这个类,synchronized(Class.class){}
与static synchronized
前面的两种是同步类的实例,synchronized(Class.class){}
与static synchronized
均是同步这个类。同步类方法和同步代码块都是针对同一个对象来说,假如再生成一个SyncBean的实例 syncbeanB,在线程B中中调用syncbeanB.testSync()
,代码不会同步执行,因为这是两个对象。用static
修饰的方法是静态方法,那么静态方法是属于类的而不是属于对象的。在单例模式中通常会使用这种方式来保证全局只生成一个对象的实例。下面将SyncBean改成一个单例类
package com.hpplay.threadcommunicationdemo;
import android.util.Log;
/**
* Created by DON on 2017/3/4.
*/
public class SyncBean {
public static final String TAG = "syncbean";
private static SyncBean instance = null;
private SyncBean(){
}
public static synchronized SyncBean getInstance(){
synchronized (SyncBean.class){
Log.e(TAG,"before create instance");
if(instance == null){
instance = new SyncBean();
Log.e(TAG,"create instance");
}
Log.e(TAG," after create instance");
}
return instance;
}
}
在线程A和线程B中分别调用SyncBean.getInstance();
输出
03-04 16:10:24.928 29967-29980/? E/syncbean: before create instance
03-04 16:10:24.928 29967-29980/? E/syncbean: create instance
03-04 16:10:24.928 29967-29980/? E/syncbean: after create instance
03-04 16:10:24.928 29967-29981/? E/syncbean: before create instance
03-04 16:10:24.928 29967-29981/? E/syncbean: after create instance
可以看到只创建了一个类的实例。
这是一个简单的单例模式的实现,也是单例模式中常说的懒汉模式,就是在第一次调用的才生成对象。
你要说为什么不一开始就直接生成一个静态变量instance留给以后用呢?
答案是因为懒嘛,所以才叫懒汉模式。
Synchronized也有很明显的缺点,因为要保证同一时间内只能有一个线程调用,所以阻塞了其他的调用者,这样就影响了程序执行效率。这个缺点在某些高并发的程序中会显得特别明显,所幸在手机app开发中大部分场景并发并不高,用起来还是很方便的
网友评论