首页 文章 精选 留言 我的

精选列表

搜索[环境层隔离],共10000篇文章
优秀的个人博客,低调大师

Java编程思想——多线程的三大核心源码层解密

欢迎大家关注,欢迎评论 对于Java并发编程,一般来说有以下的关注点: 1.线程安全性,正确性。 2.线程的活跃性(死锁,活锁) 3.性能 其中线程的安全性问题是首要解决的问题,线程不安全,运行出来的结果和预期不一致,那就连基本要求都没达到了。 保证线程的安全性问题,本质上就是保证线程同步,实际上就是线程之间的通信问题。我们知道,在操作系统中线程通信有以下几种方式: 1.信号量 2.信号 3.管道 4.共享内存 5.消息队列 6.socket java中线程通信主要使用共享内存的方式。共享内存的通信方式首先要关注的就是可见性和有序性。而原子性操作一般都是必要的,所以主要关注这三个问题。 1、原子性(Atomicity) 原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。 JMM只是保证了基本的原子性,但类似于i++之类的操作,看似是原子操作,其实里面涉及到: 获取 i 的值。 自增。 再赋值给 i。 这三步操作,所以想要实现i++这样的原子操作就需要用到synchronize或者是lock进行加锁处理。 如果是基础类的自增操作可以使用AtomicInteger这样的原子类来实现(其本质是利用了CPU级别的 的CAS指令来完成的)。 其中用的最多的方法就是:incrementAndGet()以原子的方式自增。 源码如下: 首先是获得当前的值,然后自增 +1。接着则是最核心的compareAndSet()来进行原子更新。 其逻辑就是判断当前的值是否被更新过,是否等于current,如果等于就说明没有更新过然后将当前的值更新为next,如果不等于则返回false进入循环,直到更新成功为止。 还有其中的get()方法也很关键,返回的是当前的值,当前值用了volatile关键词修饰,保证了内存可见性。 private volatile int value; 2、可见性 可见性就是指当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改。 现代计算机中,由于CPU直接从主内存中读取数据的效率不高,所以都会对应的CPU高速缓存,先将主内存中的数据读取到缓存中,线程修改数据之后首先更新到缓存,之后才会更新到主内存。如果此时还没有将数据更新到主内存其他的线程此时来读取就是修改之前的数据。 如上图所示。 volatile关键字就是用于保证内存可见性,当线程A更新了 volatile 修饰的变量时,它会立即刷新到主线程,并且将其余缓存中该变量的值清空,导致其余线程只能去主内存读取最新值。 使用volatile关键词修饰的变量每次读取都会得到最新的数据,不管哪个线程对这个变量的修改都会立即刷新到主内存。 synchronize和加锁也能能保证可见性,实现原理就是在释放锁之前其余线程是访问不到这个共享变量的。但是和volatile相比开销较大。 3、顺序性 Java内存模型中的程序天然有序性可以总结为一句话:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存中主内存同步延迟”现象。 正常情况下的执行顺序应该是1>>2>>3。但是有时JVM为了提高整体的效率会进行指令重排导致执行的顺序可能是2>>1>>3。但是JVM也不能是什么都进行重排,是在保证最终结果和代码顺序执行结果一致的情况下才可能进行重排。 重排在单线程中不会出现问题,但在多线程中会出现数据不一致的问题。 Java 中可以使用volatile来保证顺序性, 和 lock也可以来保证有序性,和保证原子性的方式一样,通过同一段时间只能一个线程访问来实现的。 除了通过volatile关键字显式的保证顺序之外,JVM还通过happen-before原则来隐式的保证顺序性。 其中有一条就是适用于volatile关键字的,针对于volatile关键字的写操作肯定是在读操作之前,也就是说读取的值肯定是最新的。 volatile 的应用 双重检查锁的单例模式 可以用volatile实现一个双重检查锁的单例模式: 这里的volatile关键字主要是为了防止指令重排。 如果不用volatile,singleton = new Singleton();,这段代码其实是分为三步: 分配内存空间。(1) 初始化对象。(2) 将singleton对象指向分配的内存地址。(3) 加上volatile是为了让以上的三步操作顺序执行,反之有可能第二步在第三步之前被执行就有可能某个线程拿到的单例对象是还没有初始化的,以致于报错。 控制停止线程的标记 这里如果没有用 volatile 来修饰 flag ,就有可能其中一个线程调用了stop()方法修改了 flag 的值并不会立即刷新到主内存中,导致这个循环并不会立即停止。 这里主要利用的是volatile的内存可见性。 总结一下: volatile关键字只能保证可见性,顺序性,不能保证原子性。 欢迎工作一到五年的Java工程师朋友们加入Java架构开发:468947140 点击链接加入群聊【Java-BATJ企业级资深架构】:https://jq.qq.com/?_wv=1027&k=5zMN6JB 本群提供免费的学习指导 架构资料 以及免费的解答 不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导 如果觉得本文还可以,欢迎大家《关注》,算是对笔者的支持

优秀的个人博客,低调大师

Android源码剖析之Framework层实战版(Ams管理Activity启动)

本文来自http://blog.csdn.net/liuxian13183/,引用必须注明出处! 讲到实战,就不得不拿两个例子来说明,本篇想拿的是应用最广泛的两个:Ams和Wms,一个管理activity,一个管理窗口,而前面我们已经讲了不少,本篇不再赘述。 关于Ams对activity的管理,无非这几个方面:启动哪个activity、物理按键对activity处理、内存骤减时activity的回收规则,以及暂停activity的一系列操作。 先说如何启动activity?有哪些知识点。 Ams调度activity,运行某activity需要通过Ams决定,一般情况下仅限允许均可执行;记录运行的页面,保存activity一些状态,以便下次启动更加快速,一般放在ActivityRecord,以前叫HistoryRecord;做进程管理,清楚运行哪些进程,各自的状态和信息集合 当然Ams除了管理activity,还会管理其他两个主要数据类,如Process和Task;Process同样也有个ProcessRecord用来记录进程运行信息和状态以及内部有哪些组件(指service、provider这样的),多个apk可以运行在同一进程中,可以通过android:process来设置,一般情况下还是不要,因为手机默认64M内存(不同分辨率不同),内存一般也仅够一个apk使用;而TaskRecord记录activity所在的栈的id、intent和activity的数量,用于对栈进行管理。 AMS常见的默认系统常量: 1、MAX_ACTIVITIES=20: 通过硬件设置,当前页面仅有一个activity存活,其他20个被缓存起来,超过则杀死优先低的 2、MAX_RECENT_TASKS=20:通过硬件设置,栈最多存储20个,超过后最先舍弃最早的栈 3、PAUSE_TIMEOUT=500:启动activity最长时间为500ms,否则关闭该页面,一般表现是“屏幕黑一下” 4、LAUNCH_TIMEOUT=10*1000:启动进程最长10s,否则舍弃 5、PROC_START_TIMEOUT=10*1000:启动进程后10s内必须报告AMS启动状态,否则AMS不承认此进程的存在 以上是最基础的变量,相信在多年开发中,大家对其引起的现象已经历历在目 其次在启动和暂停activity过程中,需要一些变量来保存状态,主要因为AMS是通过Service机制动作,比如 1、mPendingActivityLaunched:即将启动的activity列表 2、mStoppingActivities:A跳入B,B启动后A即被加入此队列,区别于mHistory的A跳入即加入 3、mHistory:所有后台activity,A跳入B即A被加入,或按Home键B被加入,回来按回退键则B被清除;finishing=true 4、mFinishingActivities:上面列表中执行完finish方法的activity,此时并未完全杀死等待被回收 5、mLRUActivities:最近使用的activity,包含mHistory里的activity和已经清除的activity 此外还有HistroyRecord记录mPausingActivity(record对象,记录执行onPause方法的activity),mResumeActivity、mFocusedActvity和mLastPausedActivity等。 上面讲完基础,接下来进程流程,讲讲如何启动Activity? 一般调用方式有四种,点击手机图标、执行startActivityForResult(startActivity同理)、注册硬件监听启动、被action启动 以下是启动Activity的过程和startActivityForResult的启动(startActivity传一个默认的requestCode-1,最后仍然调用startActivityForResult方法,所以放在一起讲) 启动前的权限检查和准备工作 启动Activity的时候往往还需要进行权限检查,以查看其是否符合启动条件。查询不满足条件则返回,否则继续;检查Component是否存在,不满足返回,满足继续,启动startActivityLocked(caller,intent)方法,完成以下几件事 1、检查如果启动自己则直接返回 2、会加入INTENT_FLAG_FORWARD_RESULT标志,用于从C直接返回结果给A,使用方法A调用startActivityForResut->B调用startActivity->C调用setResult直接回到A 3、检查call-ActivityRecord是否存在且有指定权限 4、如果Ams中有IActivityController对象,则通知Controller进行相应控制 5、创建临时HistoryRecord对象,不一定加入mHistory列表,如不关闭则加入 6、检查是否允许切换activity,否则加入mPendingActivityLaunched 7、判断pendingActivity是否有要启动的activity,有则先执行,无则执行该intent 如想了解launchMode,请移步: Android基础之Activity launchMode详解 如想了解Intent,请移步: Intent中的四个重要属性——Action、Data、Category、Extras 以上是对intent的介绍,接下来会再介绍一下task,也就是如何启动,以什么样的规则启动和退出。以下均指launchFlag,标记均以FLAG_ACTIVITY_开头,介绍时会忽略,请注意一下;启动时会依次判断如下标识 1、NO_USER_ACTION:含义无用户交互;基本不用,主要防止一段时间后执行onUserLeaving方法;接下来如果立即启动,就把r.delay.Resume设为true 2、PREVIOUS_IS_TOP:含义上个intent是否位于栈顶,基本不用;然后为activity赋予权限加入缓存;此时区别于launchMode和launchFlag,前者指activity自己声明的启动方式,后者是明显启动者想让activity如何启动,能过intent设置,但两者有相通性 3、NEW_TASK、SINGLE_TASK、SINGLE_INSTANCE:使用这三者均不建议使用startActivityForResut,而只限于使用startActivity即r.resultTo=0,不需要回传数据的情况下;第1个是会新起一个task,即两个task之间最好不进行数据回传;2和3的相同在于,如果已经存在这样的task和component以及其他相同数据如intent,则均跳到相应栈中,不存在则声明一个新的task;不同点在于:2的task里可以包含多个activity,而3仅能包含一个;可能跟每个task内存大小有关,不同功用的activity可以申请不同的task使用,这一点也可以看上面“Android基本之Activity LaunchMode详解” 4、CLEAR_TOP、REORDER_TO_FRONT:前者如自己存在,则清除该栈上面的其他activity;后者仅把自己放在最上面;举例A1->A2->A3,前者启动A2则变成A1->A2,后者启动A2则变成A1->A3->A2 5、NO_HISTORY:不要保存自己,设置A3,则A1->A2->A3->A4,执行完还是A1->A2,皮之不存,毛将焉附。 一般情况下,以上如果调整栈的顺序,那么是可以执行的;如A、B、C三个栈,分别有2个activity,如果从C切换到B,那么是这样的A1->A2->B1->B2->C1->C2,调整后A1->A2->C1->C2->B1->B2。 系统还提供另外一种办法来设置activity的启动方式,那就是 1、android:clearTaskOnLaunch=true/false:是否清除task里的其他activity 2、android:finishOnTaskLaunch:是否关闭task中已有的此activity 3、android:allowTaskReparent:是否将自己带入启动的task中 4、android:alwaysRetainTaskState:是否由系统维护activity状态;一般应用在根activity,每次可以打开最后打开的页面 最近讲的几个东西之间的关系,画了一张图,如下所示 正式启动工作(主要流程均为ActivityThread执行-在attach时初始化) 一、暂停当前activity 1、判断该activity是否存在;然后执行onUserLeaving方法,避免此activity再与物理按键交互,如后退键 2、调用performPauseActivity(告知暂停而非超时等情况,将prev指向自己),先onSaveInstanceState,再执行onPause ·3、报告AMS暂停完毕,通过IPC调用AMS的completePauseActivity方法 二、调用resumeTopActivity方法: 1、如果mHistory有记录且直接启动,否则执行startHomeActivityLocked方法启动主界面程序; 2、系统处于睡眠状态或当前activity未被暂停,则停止; 3、从mStoppingActivities和mWarningVisibleActivities里移除目标对象; 4、将被停止的activity设置为不可见状态,通知activty或task切换 5、判断目录进程是否存在并且activityThread存活,否则执行startSpecificActivityLocked方法;如果进程不存在,则调用 Process类启动新进程,设置pid加入ProcessRecord,启动完之后再通知Ams启动目标Activity,至于启动Process的过程跟调用 暂停或启用activity的过程无异,仅仅参数发生变化而已,以及变量不同和意义不同。当然我们再讲一点,启动进程毕 竟是个重要流程,提取odex文件,前面几篇文章有过介绍,指已经优化过的dex文件,通过Service、Provider和 Broadcast加入引用,创建完成。 6、最终调用performLaunchActivity,执行attach,执行setTheme,几个on方法,拿到DecorView加入Wms,设 置可见。 接下来咱们讲讲停止工作:一般情况下是长时间不使用,或者应用内存紧张,或者启动时设置no_history才会执行,执行过程 而应用与上面生命周期无异,也就是设置不可见,加个mStoppingActivities,异步通知Ams停止,最后调用onStop方法等等, 关闭activity也同样如此;至于关闭的优先级前面似乎没讲,接下来会着重介绍一下。 Android系统如何管理自己内存的?同样可以预习一下,原理协同,再做补充。 一般情况下优化级分为-16到15级,而android默认仅使用0-15级,越小优先级越高,当前可见activity最高为0。 为什么会产生这个优化级,主要android采用的是关闭而不退出,退出而不清理的原则,主要为了二次加载更加快速; 其次是上面停止activity的原因。而这个规则由Ams加权得出,有这样几个变量:是否展示在当前页或是持久化 activity、相应的配合组件、剩余内存量、HOME进程、活跃activity数量和组件等,就像JVM的内存管理规则一样, 详见:Java高级之虚拟机垃圾回收机制 由于Ams无法预知内存的变动(OOM除外),因而采用这套机制:最先是空进程(无activity进程),其次是 activity,再次是前台的配合组件,如Service、Receiver、Provider,最后才是前台activity;而这些操作由OOM Killer 进程直接管理,关于activity回收需要满足以下几种情况: 1、finish操作或crash或anr,通过updateOomAdjLocked方法让其指定优先级,动态调整 2、如果运行的activity超过20个,必须是已经stop的、不可见的、非常驻进程 因而不合理的手机架构也会造成系统的崩溃。PS:持久化对象的在ProcessRecord的persistent变量是否为true LocalActivityManager存在于Activity内部,用来装载和管理activity以及各种状态,维持一个最低的内存消耗;核 心在于它使用UI线程的activityThread来装载指定的 activity;有同学可能会问task越少或者histroyRecord越小,内存 会占用越小吗?这个问题跟手机里装一个app和装n个app重量是否增加是一样的道理,但是越过限制以后(task允许) 内存占用确实会变大,直到出现OOM。 以下是按键方面的内容(锁屏下,基本均不操作) 1、后退键:Activity里监听到onBackPressed方法;一般执行的是finish动作,执行performDestroyActivity方法 2、Home键:Acitivty的onKey方法无法截取它,属于设计原因;Wms中使用PhoneWindowManager类中的 interceptKeyTi截取消息,发现是它,再执行launchHomeFromHotKey;2.0之后添加另一个Home键执行 startDockOrHome方法来监听硬件。 与普通的启动区别在于,能启动特殊的intent,方法在ContextImpl里。而长按震动、关闭所有窗口、会弹出LRT- lasted recenttask,可以调用Ams的getRecentTask来取出,通过弹出的窗体的点击事件,进入相应的应用。 总结一下: 进程启动:1、内核创建进程数据结构,指出其地址总线 2、装载函数,读取代码,拿到数据总线 3、将程序指针指向目标地址入口 虚拟机启动:首次是从Zygote进程fork出来一个子进程,用来加载资源,然后启动SystemSever,用来监 控手机按键和触摸事件,以及管理Ams、Pms、Wms等,最终根据配置文件的HomeActivity,启动它。 Activity启动,然后触发init.rc文件执行main函数,启动一个ActivityThread 这就是所谓的UI线程,由Looper声明一个MessageQueue,接着创建Activity对象,初始化ViewRoot和token,用来分 发消息和接收转换为本地消息,为后面执行Activity的生命周期,创建PhoneWindow,执行attach方法,初始化内部组 件,根据Wms返回的消息,适时的执行生命周期,执行setContentView创建DecorView(View内部也有ViewRoot来设置View的各种属性),由Wms加入到窗口中,设置Visiable,加载结束。 注:每个应用仅有一个ActivityThread来异步处理内部事务,如果出错则App Crash;具体应用启动后,会创建ApplicationThread和ActivityThread,分别用来处理应用事务和Activity事务,并且创建MessageQueue,不停的轮循;AMS通过判断加载某个Activity和资源的加载情况,将消息从手机端通过Binder发给当前应用的ViewRoot对象,通过handler把消息放入消息队列,轮循出来的消息处理,即推动Activity的生命周期执行。 问题1:在应用运行过程中Window有多个吗?是的,每个Activity都会创建Window(见1)。 问题2:在应用运行过程中WindowManager有多个吗?Activity的WindowManager通过系统的WindowManager创建(见2、3、4) Activity类 final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window) { attachBaseContext(context); mFragments.attachHost(null /*parent*/); 1、 mWindow = new PhoneWindow(this, window); mWindow.setWindowControllerCallback(this); mWindow.setCallback(this); mWindow.setOnWindowDismissedCallback(this); mWindow.getLayoutInflater().setPrivateFactory(this); if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); } if (info.uiOptions != 0) { mWindow.setUiOptions(info.uiOptions); } mUiThread = Thread.currentThread(); mMainThread = aThread; mInstrumentation = instr; mToken = token; mIdent = ident; mApplication = application; mIntent = intent; mReferrer = referrer; mComponent = intent.getComponent(); mActivityInfo = info; mTitle = title; mParent = parent; mEmbeddedID = id; mLastNonConfigurationInstances = lastNonConfigurationInstances; if (voiceInteractor != null) { if (lastNonConfigurationInstances != null) { mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor; } else { mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this, Looper.myLooper()); } } 2、mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); if (mParent != null) { mWindow.setContainer(mParent.getWindow()); } 3、mWindowManager = mWindow.getWindowManager(); mCurrentConfig = config; }Window类: public void setWindowManager(WindowManager wm, IBinder appToken, String appName, boolean hardwareAccelerated) { mAppToken = appToken; mAppName = appName; mHardwareAccelerated = hardwareAccelerated || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false); if (wm == null) { wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); } 4、 mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this); }WindowManagerImpl类: * Provides low-level communication with the system window manager for * operations that are bound to a particular context, display or parent window. * Instances of this object are sensitive to the compatibility info associated * with the running application.提供一个跟系统WindowManager低等级的沟通方式,用于特别的Context、Display或者父Window。 WindowManagerGlobal类的解释同上。 public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); } @Override public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.updateViewLayout(view, params); } private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) { // Only use the default token if we don't have a parent window. if (mDefaultToken != null && mParentWindow == null) { if (!(params instanceof WindowManager.LayoutParams)) { throw new IllegalArgumentException("Params must be WindowManager.LayoutParams"); } // Only use the default token if we don't already have a token. final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params; if (wparams.token == null) { wparams.token = mDefaultToken; } } } @Override public void removeView(View view) { mGlobal.removeView(view, false); }

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册