首页 文章 精选 留言 我的

精选列表

搜索[农业大模型],共10004篇文章
优秀的个人博客,低调大师

企业图谱强势来袭,玩转企业大数据

2016年的大数据浪潮是一波又一波,“大数据”、“云计算”、“人工智能”开始频繁出现在人们的视线中。阿里云率先以大数据+云计算来赋能各行各业,阿里云并不只是自己在做大数据,而是通过打造公共、开放的大数据服务平台来开放自己的技术能力,服务更多的企业。 企业图谱作为阿里云2017年推出的首款数据服务产品,将会为企业提供一站式的企业数据服务。提供企业多维度信息查询,深度挖掘企业与企业、企业与个人关系链路,方便企业构建基于企业画像及企业关系网络的风险控制、市场监测等企业级服务。 企业图谱三大优势 挖掘企业隐形关系:从海量互联网结构化数据中挖掘企业的隐形关系,如产业链上下游、合作、竞争等关系,并对企业及企业关键人物做朋友圈标识,从而在无唯一ID的海量数据中对企业进行识别。 企业关系进行量化分析:根据企业的入股情况,合作深度、朋友圈重合度、竞品所占市

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

码农幸福指数:哪种编程语言最让人感到幸福?

云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 刻板印象无处不在,在IT世界中也是如此。“ PHP是最糟糕的语言”或“ Python适合初学者”,这样的固有认知已然扎根在很多人的印象里。 编程营2020即将开赛,我们将通过github和来自开发人员的调查数据找到真相,快来看看是哪个编程语言最终“C位出道”吧! 数据采集 Github对于任何开发人员来说都不会陌生。它不仅可以用作版本控制工具,还可以收集存储库和项目统计信息、用户形象数据和评论。我们就可以访问有关年龄,性别和个人资料照片的信息,用到的工具是Microsoft Face API。 Github提供了25个最受欢迎的存储库列表,并显示了前5名贡献者。我将使用Eigencoder应用程序从每个存储库中检测30-80张个人资料照片并对其进行分析。 性别 FaceAPI具有从面部结构中检测性别的功能。也许不够全面,但也不难看出,AI和Webdev语言其实是有不少女性使用者的。 年龄 年龄指标的结果很有趣,较老的编程语言似乎本应由年长的程序员使用,但Java却拥有着最年轻的开发人员群体。 微笑 在面对不喜欢的编程语言时,一些程序员甚至都不会拥有一张微笑的个人资料照片,Face API使用0到1之间的值来衡量微笑的数量。 这么看起来,R开发人员是最幸福的,其次是Go,C#和Python,而Java开发人员似乎并不喜欢他们的工具。 评论分析 Tobias Hermann分析了reddit各个语言子板块的开发者使用的评论词语。 幸福 使用积极语言群体最多的是Clojure,Lisp和Scala开发人员。 脏话 你可能会在PHP社区中听到最多的脏话,这实在不足为奇。第二大“脏话最多”的语言社区是Javascript,其次是Java。使用mathematica语言的开发人员似乎最自在。 CodinGame2020开发人员调查 根据Codingame对于各个行业中开发人员的幸福程度调查显示,前三名分别是: 1.数据科学家/机器学习专家2.游戏开发商/工程师3.开发老师和学术研究人员 以上的数据分析并不能代表全部的结果,但也有一定的参考价值。 像PHP,Html和R这样的编程语言主要由女性使用,C和C++主要由男性使用。C ++语言拥有最年长的开发者群体,而Java语言则是年轻开发群体的首选。最快乐的开发人员似乎是那些使用R,Go,C#和Python的人。 你在工作中发现的全部幸福和意义取决于你的人生方向和人生目标。有人可能会在机器学习行业蓬勃发展,而另一些人则更喜欢不断在游戏开发方面寻求新的创意等等。 当然,甲之蜜糖,乙之砒霜,这只是大部分人的感受和喜欢,做少数人依旧有乐趣所在。 【云栖号在线课堂】每天都有产品技术专家分享!课程地址:https://yqh.aliyun.com/live 立即加入社群,与专家面对面,及时了解课程最新动态!【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK 原文发布时间:2020-05-25本文作者:读芯术本文来自:“读芯术公众号”,了解相关信息可以关注“读芯术”

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

码农千千万,你凭什么说自己精通iOS!

自学iOS的人不少,科班出身的也很多,但是到什么程度才有资格说自己精通iOS?个人觉得至少需要经历以下几个阶段: 1.重视代码品质,精益求精,这是技术开发的本质,也是程序员的立足之本。 对处于还没工作或者工作1年左右这个阶段的人来说,看书或者看视频都是比较好的提升方式!需要注意的是,在看书的过程中一定要思考能否运用书中的经验来改善自己写的代码,运用到实处! 2.打好基础后,主动跳出舒适区,不断扩大自己的技术视野。 这一步比较难,因为很多人会被公司的技术和业务牵着走。比如公司当前的业务规模决定了技术上不需要分布式技术,很多人就不会去思考规模大了之后如何利用分布式技术来解决问题。 这种时候,除了寻找更大规模的业务外,要时刻保持跳出当前层级和环境来思考的习惯。比如,你只用了关系型数据库,有没有想过数据一直产生,到达TB级别的时候该如何快速检索与保存呢? 3.扩展了知识宽度,还要加强自己的技术深度。 很多人信奉“不去造轮子”,看似性价比很高的一句话,却不知道这句话是有前提的,那就是“你得知道轮子的内部结构”,否则就是自我麻痹。接下来需要选择某一个或几个方面深入研究下去,构建自己的核心竞争力。 一个有技术深度的程序员才是有灵魂的。而大厂也愿意为有趣的灵魂买单,这个时候看的就不仅仅是工资水平,更多的在于晋升和发展前景的广阔性。 技术的突破需要依赖业务场景的需求和自身刻意的规划学习,二者缺一不可。但前者是机会,往往不是自己能完全把控的,如果在工作中没有实践场景,最好的办法就是系统的学习与梳理,待机会来临时才能一展身手。 那如何学习才能快速入门并精通呢? 当真正开始学习的时候难免不知道从哪入手,导致效率低下影响继续学习的信心。 最重要的是不知道哪些技术需要重点掌握,学习时频繁踩坑,最终浪费大量时间,所以在学习之前,有一个自己的交流圈子和市场调研需求方向是很重要的!

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

【码农教程】单元测试利器——手把手教你使用Mockito

作者:京东零售 秦浩然 从你成为开发人员的那一天起,写单元测试终究是你逃不开的宿命!那开发人员为什么不喜欢写单元测试呢?究其原因,无外乎是依赖。依赖其他的服务、依赖运行的环境、等等,各种依赖都成为了我们写单元测试的绊脚石。那现在有个单元测试利器可以帮我们解决依赖的问题,你愿意使用一下吗?你愿意!那就是我们要学习的Mockito 一、前期准备~ 1、准备工作 <!--mockito依赖--> <dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <version>2.7.19</version> <scope>test</scope> </dependency> <!-- junit依赖 --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> 2、入门知识 1)Mockito:简单轻量级的做mocking测试的框架; 2)mock对象:在调试期间用来作为真实对象的替代品; 3)mock测试:在测试过程中,对那些不容易构建的对象用一个虚拟对象来代替测试的方法就叫mock测试; 4)stub:打桩,就是为mock对象的方法指定返回值(可抛出异常); 5)verify:行为验证,验证指定方法调用情况(是否被调用,调用次数等); 3、五分钟入门Demo @Test public void test0() { //1、创建mock对象(模拟依赖的对象) final List mock = Mockito.mock(List.class); //2、使用mock对象(mock对象会对接口或类的方法给出默认实现) System.out.println("mock.add result => " + mock.add("first")); //false System.out.println("mock.size result => " + mock.size()); //0 //3、打桩操作(状态测试:设置该对象指定方法被调用时的返回值) Mockito.when(mock.get(0)).thenReturn("second"); Mockito.doReturn(66).when(mock).size(); //3、使用mock对象的stub(测试打桩结果) System.out.println("mock.get result => " + mock.get(0)); //second System.out.println("mock.size result => " + mock.size()); //66 //4、验证交互 verification(行为测试:验证方法调用情况) Mockito.verify(mock).get(Mockito.anyInt()); Mockito.verify(mock, Mockito.times(2)).size(); //5、验证返回的结果(这是JUnit的功能) assertEquals("second", mock.get(0)); assertEquals(66, mock.size()); } 二、让我们开始学习吧! 1、行为验证 • 一旦mock对象被创建了,mock对象会记住所有的交互,然后你就可以选择性的验证你感兴趣的交互,验证不通过则抛出异常。 @Test public void test1() { final List mockList = Mockito.mock(List.class); mockList.add("mock1"); mockList.get(0); mockList.size(); mockList.clear(); // 验证方法被使用(默认1次) Mockito.verify(mockList).add("mock1"); // 验证方法被使用1次 Mockito.verify(mockList, Mockito.times(1)).get(0); // 验证方法至少被使用1次 Mockito.verify(mockList, Mockito.atLeast(1)).size(); // 验证方法没有被使用 Mockito.verify(mockList, Mockito.never()).contains("mock2"); // 验证方法至多被使用5次 Mockito.verify(mockList, Mockito.atMost(5)).clear(); // 指定方法调用超时时间 Mockito.verify(mockList, timeout(100)).get(0); // 指定时间内需要完成的次数 Mockito.verify(mockList, timeout(200).atLeastOnce()).size(); } 2、如何做一些测试桩stub • 默认情况下,所有的函数都有返回值。mock函数默认返回的是null,一个空的集合或者一个被对象类型包装的内置类型,例如0、false对应的对象类型为Integer、Boolean; • 一旦测试桩函数被调用,该函数将会一致返回固定的值; • 对于 static 和 final 方法, Mockito 无法对其 when(…).thenReturn(…) 操作。 @Test public void test2() { //静态导入,减少代码量:import static org.mockito.Mockito.*; final ArrayList mockList = mock(ArrayList.class); // 设置方法调用返回值 when(mockList.add("test2")).thenReturn(true); doReturn(true).when(mockList).add("test2"); System.out.println(mockList.add("test2")); //true // 设置方法调用抛出异常 when(mockList.get(0)).thenThrow(new RuntimeException()); doThrow(new RuntimeException()).when(mockList).get(0); System.out.println(mockList.get(0)); //throw RuntimeException // 无返回方法打桩 doNothing().when(mockList).clear(); // 为回调做测试桩(对方法返回进行拦截处理) final Answer<String> answer = new Answer<String>() { @Override public String answer(InvocationOnMock invocationOnMock) throws Throwable { final List mock = (List) invocationOnMock.getMock(); return "mock.size result => " + mock.size(); } }; when(mockList.get(1)).thenAnswer(answer); doAnswer(answer).when(mockList).get(1); System.out.println(mockList.get(1)); //mock.size result => 0 // 对同一方法多次打桩,以最后一次为准 when(mockList.get(2)).thenReturn("test2_1"); when(mockList.get(2)).thenReturn("test2_2"); System.out.println(mockList.get(2)); //test2_2 System.out.println(mockList.get(2)); //test2_2 // 设置多次调用同类型结果 when(mockList.get(3)).thenReturn("test2_1", "test2_2"); when(mockList.get(3)).thenReturn("test2_1").thenReturn("test2_2"); System.out.println(mockList.get(3)); //test2_1 System.out.println(mockList.get(3)); //test2_2 // 为连续调用做测试桩(为同一个函数调用的不同的返回值或异常做测试桩) when(mockList.get(4)).thenReturn("test2").thenThrow(new RuntimeException()); doReturn("test2").doThrow(new RuntimeException()).when(mockList).get(4); System.out.println(mockList.get(4)); //test2 System.out.println(mockList.get(4)); //throw RuntimeException // 无打桩方法,返回默认值 System.out.println(mockList.get(99)); //null } 3、参数匹配器 • 参数匹配器使验证和测试桩变得更灵活; • 为了合理的使用复杂的参数匹配,使用equals()与anyX() 的匹配器会使得测试代码更简洁、简单。有时,会迫使你重构代码以使用equals()匹配或者实现equals()函数来帮助你进行测试; • 如果你使用参数匹配器,所有参数都必须由匹配器提供; • 支持自定义参数匹配器; @Test public void test3() { final Map mockMap = mock(Map.class); // 正常打桩测试 when(mockMap.get("key")).thenReturn("value1"); System.out.println(mockMap.get("key")); //value1 // 为灵活起见,可使用参数匹配器 when(mockMap.get(anyString())).thenReturn("value2"); System.out.println(mockMap.get(anyString())); //value2 System.out.println(mockMap.get("test_key")); //value2 System.out.println(mockMap.get(0)); //null // 多个入参时,要么都使用参数匹配器,要么都不使用,否则会异常 when(mockMap.put(anyString(), anyInt())).thenReturn("value3"); System.out.println(mockMap.put("key3", 3)); //value3 System.out.println(mockMap.put(anyString(), anyInt())); //value3 System.out.println(mockMap.put("key3", anyInt())); //异常 // 行为验证时,也支持使用参数匹配器 verify(mockMap, atLeastOnce()).get(anyString()); verify(mockMap).put(anyString(), eq(3)); // 自定义参数匹配器 final ArgumentMatcher<ArgumentTestRequest> myArgumentMatcher = new ArgumentMatcher<ArgumentTestRequest>() { @Override public boolean matches(ArgumentTestRequest request) { return "name".equals(request.getName()) || "value".equals(request.getValue()); } }; // 自定义参数匹配器使用 final ArgumentTestService mock = mock(ArgumentTestService.class); when(mock.argumentTestMethod(argThat(myArgumentMatcher))).thenReturn("success"); doReturn("success").when(mock).argumentTestMethod(argThat(myArgumentMatcher)); System.out.println(mock.argumentTestMethod(new ArgumentTestRequest("name", "value"))); // success System.out.println(mock.argumentTestMethod(new ArgumentTestRequest())); //null } 4、执行顺序验证 • 验证执行顺序是非常灵活的-你不需要一个一个的验证所有交互,只需要验证你感兴趣的对象即可; • 你可以仅通过那些需要验证顺序的mock对象来创建InOrder对象; @Test public void test4() { // 验证同一个对象多个方法的执行顺序 final List mockList = mock(List.class); mockList.add("first"); mockList.add("second"); final InOrder inOrder = inOrder(mockList); inOrder.verify(mockList).add("first"); inOrder.verify(mockList).add("second"); // 验证多个对象多个方法的执行顺序 final List mockList1 = mock(List.class); final List mockList2 = mock(List.class); mockList1.get(0); mockList1.get(1); mockList2.get(0); mockList1.get(2); mockList2.get(1); final InOrder inOrder1 = inOrder(mockList1, mockList2); inOrder1.verify(mockList1).get(0); inOrder1.verify(mockList1).get(2); inOrder1.verify(mockList2).get(1); } 5、确保交互(interaction)操作不会执行在mock对象上 • 一些用户可能会在频繁地使用verifyNoMoreInteractions(),甚至在每个测试函数中都用。但是verifyNoMoreInteractions()并不建议在每个测试函数中都使用; • verifyNoMoreInteractions()在交互测试套件中只是一个便利的验证,它的作用是当你需要验证是否存在冗余调用时; @Test public void test5() { // 验证某个交互是否从未被执行 final List mock = mock(List.class); mock.add("first"); verify(mock, never()).add("test5"); //通过 verify(mock, never()).add("first"); //异常 // 验证mock对象没有交互过 final List mock1 = mock(List.class); final List mock2 = mock(List.class); verifyZeroInteractions(mock1); //通过 verifyNoMoreInteractions(mock1, mock2); //通过 verifyZeroInteractions(mock, mock2); //异常 // 注意:可能只想验证前面的逻辑,但是加上最后一行,会导致出现异常。建议使用方法层面的验证,如:never(); // 在验证是否有冗余调用的时候,可使用此种方式。如下: final List mockList = mock(List.class); mockList.add("one"); mockList.add("two"); verify(mockList).add("one"); // 通过 verify(mockList, never()).get(0); //通过 verifyZeroInteractions(mockList); //异常 } 6、使用注解简化mock对象创建 注意!下面这句代码需要在运行测试函数之前被调用,一般放到测试类的基类或者test runner中: MockitoAnnotations.initMocks(this); 也可以使用内置的runner: MockitoJUnitRunner 或者一个rule : MockitoRule; // 代替 mock(ArgumentTestService.class) 创建mock对象; @Mock private ArgumentTestService argumentTestService; // 若改注解修饰的对象有成员变量,@Mock定义的mock对象会被自动注入; @InjectMocks private MockitoAnnotationServiceImpl mockitoAnnotationService; @Test public void test6() { // 注意!下面这句代码需要在运行测试函数之前被调用,一般放到测试类的基类或者test runner中; MockitoAnnotations.initMocks(this); when(argumentTestService.argumentTestMethod(new ArgumentTestRequest())).thenReturn("success"); System.out.println(argumentTestService.argumentTestMethod(new ArgumentTestRequest())); //success System.out.println(mockitoAnnotationService.mockitoAnnotationTestMethod()); //null } 7、监控真实对象(部分mock) • 可以为真实对象创建一个监控(spy)对象。当你使用这个spy对象时真实的对象也会也调用,除非它的函数被stub了; • 尽量少使用spy对象,使用时也需要小心形式,例如spy对象可以用来处理遗留代码; • stub语法中同样提供了部分mock的方法,可以调用真实的方法; 完全mock: 上文讲的内容是完全mock,即创建的mock对象与真实对象无关,mock对象的方法默认都是基本的实现,返回基本类型。可基于接口、实现类创建mock对象。 部分mock: 所谓部分mock,即创建的mock对象时基于真实对象的,mock对象的方法都是默认使用真实对象的方法,除非stub之后,才会以stub为准。基于实现类创建mock对象,否则在没有stub的情况下,调用真实方法时,会出现异常。 注意点: Mockito并不会为真实对象代理函数调用,实际上它会拷贝真实对象。因此如果你保留了真实对象并且与之交互,不要期望从监控对象得到正确的结果。 当你在监控对象上调用一个没有被stub的函数时并不会调用真实对象的对应函数,你不会在真实对象上看到任何效果 @Test public void test7() { // stub部分mock(stub中使用真实调用)。注意:需要mock实现类,否则会有异常 final StubTestService stubTestService = mock(StubTestServiceImpl.class); when(stubTestService.stubTestMethodA("paramA")).thenCallRealMethod(); doCallRealMethod().when(stubTestService).stubTestMethodB(); System.out.println(stubTestService.stubTestMethodA("paramA")); //stubTestMethodA is called, param = paramA System.out.println(stubTestService.stubTestMethodB()); //stubTestMethodB is called System.out.println(stubTestService.stubTestMethodC()); //null // spy部分mock final LinkedList<String> linkedList = new LinkedList(); final LinkedList spy = spy(linkedList); spy.add("one"); spy.add("two"); doReturn(100).when(spy).size(); when(spy.get(0)).thenReturn("one_test"); System.out.println(spy.size()); //100 System.out.println(spy.get(0)); //one_test System.out.println(spy.get(1)); //two // spy可以类比AOP。在spy中,由于默认是调用真实方法,所以第二种写法不等价于第一种写法,不推荐这种写法。 doReturn("two_test").when(spy).get(2); when(spy.get(2)).thenReturn("two_test"); //异常 java.lang.IndexOutOfBoundsException: Index: 2, Size: 2 System.out.println(spy.get(2)); //two_test // spy对象只是真实对象的复制,真实对象的改变不会影响spy对象 final List<String> arrayList = new ArrayList<>(); final List<String> spy1 = spy(arrayList); spy1.add(0, "one"); System.out.println(spy1.get(0)); //one arrayList.add(0, "list1"); System.out.println(arrayList.get(0)); //list1 System.out.println(spy1.get(0)); //one // 若对某个方法stub之后,又想调用真实的方法,可以使用reset(spy) final ArrayList<String> arrayList1 = new ArrayList<>(); final ArrayList<String> spy2 = spy(arrayList1); doReturn(100).when(spy2).size(); System.out.println(spy2.size()); //100 reset(spy2); System.out.println(spy2.size()); //0 } 8、@Mock 和 @Spy的使用 • @Mock 等价于 Mockito.mock(Object.class); • @Spy 等价于 Mockito.spy(obj); 区分是mock对象还是spy对象: Mockito.mockingDetails(someObject).isMock(); Mockito.mockingDetails(someObject).isSpy(); @Mock private StubTestService stubTestService; @Spy private StubTestServiceImpl stubTestServiceImpl; @Spy private StubTestService stubTestServiceImpl1 = new StubTestServiceImpl(); @Test public void test8() { MockitoAnnotations.initMocks(this); // mock对象返回默认 System.out.println(stubTestService.stubTestMethodB()); //null // spy对象调用真实方法 System.out.println(stubTestServiceImpl.stubTestMethodC()); //stubTestMethodC is called System.out.println(stubTestServiceImpl1.stubTestMethodA("spy")); //stubTestMethodA is called, param = spy // 区分是mock对象还是spy对象 System.out.println(mockingDetails(stubTestService).isMock()); //true System.out.println(mockingDetails(stubTestService).isSpy()); //false System.out.println(mockingDetails(stubTestServiceImpl).isSpy()); //true } 9、ArgumentCaptor(参数捕获器)捕获方法参数进行验证。(可代替参数匹配器使用) • 在某些场景中,不光要对方法的返回值和调用进行验证,同时需要验证一系列交互后所传入方法的参数。那么我们可以用参数捕获器来捕获传入方法的参数进行验证,看它是否符合我们的要求。 ArgumentCaptor介绍 通过ArgumentCaptor对象的forClass(Class ArgumentCaptor的Api argument.capture() 捕获方法参数 argument.getValue() 获取方法参数值,如果方法进行了多次调用,它将返回最后一个参数值 argument.getAllValues() 方法进行多次调用后,返回多个参数值 @Test public void test9() { List mock = mock(List.class); List mock1 = mock(List.class); mock.add("John"); mock1.add("Brian"); mock1.add("Jim"); // 获取方法参数 ArgumentCaptor argument = ArgumentCaptor.forClass(String.class); verify(mock).add(argument.capture()); System.out.println(argument.getValue()); //John // 多次调用获取最后一次 ArgumentCaptor argument1 = ArgumentCaptor.forClass(String.class); verify(mock1, times(2)).add(argument1.capture()); System.out.println(argument1.getValue()); //Jim // 获取所有调用参数 System.out.println(argument1.getAllValues()); //[Brian, Jim] } 10、简化 ArgumentCaptor 的创建 @Mock private List<String> captorList; @Captor private ArgumentCaptor<String> argumentCaptor; @Test public void test10() { MockitoAnnotations.initMocks(this); captorList.add("cap1"); captorList.add("cap2"); System.out.println(captorList.size()); verify(captorList, atLeastOnce()).add(argumentCaptor.capture()); System.out.println(argumentCaptor.getAllValues()); } 11、高级特性:自定义验证失败信息 @Test public void test11() { final ArrayList arrayList = mock(ArrayList.class); arrayList.add("one"); arrayList.add("two"); verify(arrayList, description("size()没有调用")).size(); // org.mockito.exceptions.base.MockitoAssertionError: size()没有调用 verify(arrayList, timeout(200).times(3).description("验证失败")).add(anyString()); //org.mockito.exceptions.base.MockitoAssertionError: 验证失败 } 12、高级特性:修改没有测试桩的调用的默认返回值 • 可以指定策略来创建mock对象的返回值。这是一个高级特性,通常来说,你不需要写这样的测试; • 它对于遗留系统来说是很有用处的。当你不需要为函数调用打桩时你可以指定一个默认的answer; @Test public void test12(){ // 创建mock对象、使用默认返回 final ArrayList mockList = mock(ArrayList.class); System.out.println(mockList.get(0)); //null // 这个实现首先尝试全局配置,如果没有全局配置就会使用默认的回答,它返回0,空集合,null,等等。 // 参考返回配置:ReturnsEmptyValues mock(ArrayList.class, Answers.RETURNS_DEFAULTS); // ReturnsSmartNulls首先尝试返回普通值(0,空集合,空字符串,等等)然后它试图返回SmartNull。 // 如果最终返回对象,那么会简单返回null。一般用在处理遗留代码。 // 参考返回配置:ReturnsMoreEmptyValues mock(ArrayList.class, Answers.RETURNS_SMART_NULLS); // 未stub的方法,会调用真实方法。 // 注1:存根部分模拟使用时(mock.getSomething ()) .thenReturn (fakeValue)语法将调用的方法。对于部分模拟推荐使用doReturn语法。 // 注2:如果模拟是序列化反序列化,那么这个Answer将无法理解泛型的元数据。 mock(ArrayList.class, Answers.CALLS_REAL_METHODS); // 深度stub,用于嵌套对象的mock。参考:https://www.cnblogs.com/Ming8006/p/6297333.html mock(ArrayList.class, Answers.RETURNS_DEEP_STUBS); // ReturnsMocks首先尝试返回普通值(0,空集合,空字符串,等等)然后它试图返回mock。 // 如果返回类型不能mocked(例如是final)然后返回null。 mock(ArrayList.class, Answers.RETURNS_MOCKS); // mock对象的方法调用后,可以返回自己(类似builder模式) mock(ArrayList.class, Answers.RETURNS_SELF); // 自定义返回 final Answer<String> answer = new Answer<String>() { @Override public String answer(InvocationOnMock invocation) throws Throwable { return "test_answer"; } }; final ArrayList mockList1 = mock(ArrayList.class, answer); System.out.println(mockList1.get(0)); //test_answer } 三、学习了这么多,牛刀小试一下! 测试实体类 @Data public class User { /** * 姓名,登录密码 */ 持久层DAO public interface UserDao { /** * 根据name查找user * @param name * @return */ User getUserByName(String name); /** * 保存user * @param user * @return */ Integer saveUser(User user); } 业务层Service接口 public interface UserService { /** * 根据name查找user * @param name * @return */ User getUserByName(String name); /** * 保存user * @param user * @return */ Integer saveUser(User user); } 业务层Serive实现类 @Service public class UserServiceImpl implements UserService { //userDao @Autowired private UserDao userDao; /** * 根据name查找user * @param name * @return */ @Override public User getUserByName(String name) { try { return userDao.getUserByName(name); } catch (Exception e) { throw new RuntimeException("查询user异常"); } } /** * 保存user * @param user * @return */ @Override public Integer saveUser(User user) { if (userDao.getUserByName(user.getName()) != null) { throw new RuntimeException("用户名已存在"); } try { return userDao.saveUser(user); } catch (Exception e) { throw new RuntimeException("保存用户异常"); } } } 现在我们的Service写好了,想要单元测试一下,但是Dao是其他人开发的,目前还没有写好,那我们如何测试呢? public class UserServiceTest { /** * Mock测试:根据name查询user */ @Test public void getUserByNameTest() { // mock对象 final UserDao userDao = mock(UserDao.class); final UserServiceImpl userService = new UserServiceImpl(); userService.setUserDao(userDao); // stub调用 final User user = new User(); user.setName("admin"); user.setPassword("pass"); when(userDao.getUserByName("admin")).thenReturn(user); // 执行待测试方法 final User user1 = userService.getUserByName("admin"); System.out.println("查询结果:" + JacksonUtil.obj2json(user1)); //查询结果:{"name":"admin","password":"pass"} // 验证mock对象交互 verify(userDao).getUserByName(anyString()); // 验证查询结果 Assert.assertNotNull("查询结果为空!", user1); Assert.assertEquals("查询结果错误!", "admin", user1.getName()); } /** * Mock测试:保存user */ @Mock private UserDao userDao; @InjectMocks private UserServiceImpl userService; @Test public void saveUserTest() throws Exception{ // 执行注解初始化 MockitoAnnotations.initMocks(this); // mock对象stub操作 final User user = new User(); user.setName("admin"); user.setPassword("pass"); when(userDao.getUserByName("admin")).thenReturn(user).thenReturn(null); when(userDao.saveUser(any(User.class))).thenReturn(1); // 验证用户名重复的情况 try { userService.saveUser(user); throw new Exception(); //走到这里说明验证失败 } catch (RuntimeException e) { System.out.println("重复用户名保存失败-测试通过"); //重复用户名保存失败-测试通过 } verify(userDao).getUserByName("admin"); // 验证正常保存的情况 user.setName("user"); final Integer integer = userService.saveUser(user); System.out.println("保存结果:" + integer); //保存结果:1 Assert.assertEquals("保存失败!", 1, integer.longValue()); verify(userDao).saveUser(any(User.class)); verify(userDao, times(2)).getUserByName(anyString()); } } 根据以上代码我们可以知道,当我们的待测类开发完成而依赖的类的实现还没有开发完成。此时,我们就可以用到我们的Mock测试,模拟我们依赖类的返回值,使我们的待测类与依赖类解耦。这样,我们就可以对我们的待测类进行单元测了。 四、参考文档及进一步学习~ Mockito英文版javadoc:https://javadoc.io/static/org.mockito/mockito-core/3.3.3/org/mockito/Mockito.html Mockito中文文档(部分):https://blog.csdn.net/bboyfeiyu/article/details/52127551#35 Mockito使用教程:https://www.cnblogs.com/Ming8006/p/6297333.html 参数捕获器使用:https://www.journaldev.com/21892/mockito-argumentcaptor-captor-annotation 利用ArgumentCaptor(参数捕获器)捕获方法参数进行验证:https://www.iteye.com/blog/hotdog-916364 改变mock返回值:https://www.huangyunkun.com/2014/10/25/mockito-deep-stub-with-enum/ 五分钟了解Mockito:https://www.iteye.com/blog/liuzhijun-1512780 使用Mockito进行单元测试:https://www.iteye.com/blog/qiuguo0205-1443344 JUnit + Mockito 单元测试:https://blog.csdn.net/zhangxin09/article/details/42422643 Mockito中@Mock与@InjectMock:https://www.cnblogs.com/langren1992/p/9681600.html mockito中两种部分mock的实现,spy、callRealMethod:https://www.cnblogs.com/softidea/p/4204389.html Mockito 中被 Mocked 的对象属性及方法的默认值:https://www.cnblogs.com/fnlingnzb-learner/p/10635250.html 单元测试工具之Mockito:https://blog.csdn.net/qq_32140971/article/details/90598454 引入Mockito测试用@Spy和@Mock:https://blog.csdn.net/message_lx/article/details/83308114 Mockito初探(含实例):https://www.iteye.com/blog/sgq0085-2031319 测试覆盖率统计:https://blog.csdn.net/lvyuan1234/article/details/82836052?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task 测试覆盖率无法统计解决:https://blog.csdn.net/zhanglei082319/article/details/81536398

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

老码农的祖传干货:如何解决Java的并发编程?

作者:京东零售刘跃明 Monitor概念 Java对象的内存布局 对象除了我们自定义的一些属性外,还有其它数据,在内存中可以分为三个区域:对象头、实例数据、对齐填充,这三个区域组成起来才是一个完整的对象。 对象头:在JVM中需要大量存储对象,存储时为了实现一些额外的功能,需要在对象中添加一些标记字段用于增强对象功能,这些标记字段组成了对象头。 实例数据:存放类的属性数据信息,包括父类的属性信息。 对齐填充:由于虚拟机要求对象其实地址必须是8字节的整数倍,需要存在填充区域以满足8字节的整数倍,填充数据不是必须存在的,仅仅是为了字节对齐。   图1 Java对象头 JVM中对象头的方式有以下两种(以32位虚拟机为例): 普通对象 Object Header (64 bits) Mark Word (32 bits) Klass Word (32 bits) 数组对象 Object Header (96 bits) Mark Word(32bits) Klass Word(32bits) array length(32bits) Mark Word 这部分主要用来存储对象自身的运行数据,如hashcode、gc分带年龄等,Mark Word的位长度为JVM的一个Word大小,也就是说32位JVM的Mark Word为32位,64位JVM为64位。为了让一个字大小存储更多的信息,JVM将字的最低两个位设置为标记位,不同标记位下的Mark Word示意如下: Mark Word (32 bits) State identity_hashcode:25 age:4 biased_lock:1 lock:2 Normal thread:23 epoch:2 age:4 biased_lock:1 lock:2 Biased ptr_to_lock_record:30 lock:2 LightweightLocked ptr_to_heavyweight_monitor:30 lock:2 HeavyweightLocked  lock:2 Marked for GC 其中各部分的含义如下: lock:2位的锁状态标记位,该标记的值不同,整个Mark Word表示的含义不同。 biased_lock lock 状态 0 01 无锁 1 01 偏向锁 0 00 轻量级锁 0 10 重量级锁 0 11 GC标记 biased_lock:对象是否启用偏向锁标记,只占1个二进制位,为1时表示对象启用偏向锁,为0时表示对象没有偏向锁。 age:4位的Java对象年龄,在GC中,如果对象再Survivor区复制一次,年龄增加1,当对象达到设定的阈值时,将会晋升到老年代,默认情况下,并行GC的年龄阈值为15,并发GC的年龄阈值为6,由于age只有4位,所以最大值为15,这就是-XX:MaxTenuringThreshold选项最大值为15的原因。 identity_hashcode:25位的对象表示Hash码,采用延迟加载技术,调用方法System.idenHashcode()计算,并会将结果写到该对象头中,当对象被锁定时,该值会移动到管程Monitor中。 thread:持有偏向锁的线程ID。 epoch:偏向时间戳。 ptr_to_lock_record:指向栈中锁记录的指针。 ptr_to_heavyweight_monitor:指向管程Monitor的指针。 Klass Word 这一部分用于存储对象的类型指针,该指针指向它的类元数据,JVM通过这个指针确定对象是哪个类的实例,该指针的位长度为JVM的一个字大小,即32位的JVM为32位,64位的JVM为64位。 array length 如果对象是一个数组,那么对象头还需要有额外的空间用于存储数组的长度,这部分数据的长度也随着JVM架构的不同而不同:32位的JVM长度为32位,64位JVM则为64位。 Monitor原理 Monitor被翻译为监视器或管程 每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)之后,该对象头的Mark Word中就被设置指向Monitor对象的指针。 Monitor结构如下:   图2 • 刚开始Monitor中Owner为null • 当Thread-2执行synchronized(obj)就会将Monitor的所有者Owner置为Thread-2,Monitor中只能有一个Owner • 在Thread-2上锁的过程中,如果Thread-3、Thread-4、Thread-5也来执行synchronized(obj),就会进入EntryList BLOCKED • Thread-2执行完同步代码块的内容,然后唤醒EntryList中等待的线程来竞争锁,竞争是非公平的,也就是先进并非先获取锁 • 图2中WaitSet中的Thread-0、Thread-1是之前获得过锁,但条件不满足进入WAITING状态的线程,后面讲wait-notify时会分析 注意: • synchronized必须是进入同一个对象的Monitor才有上述的效果 • 不加synchronized的对象不会关联监视器,不遵从以上规则 synchronized原理 static final Object lock = new Object(); static int counter = 0; public static void main(String[] args) { synchronized (lock) { counter++; } } 对应的字节码为: public static main([Ljava/lang/String;)V TRYCATCHBLOCK L0 L1 L2 null TRYCATCHBLOCK L2 L3 L2 null L4 LINENUMBER 6 L4 GETSTATIC MyClass03.lock : Ljava/lang/Object; DUP ASTORE 1 MONITORENTER //注释1 L0 LINENUMBER 7 L0 GETSTATIC MyClass03.counter : I ICONST_1 IADD PUTSTATIC MyClass03.counter : I L5 LINENUMBER 8 L5 ALOAD 1 MONITOREXIT //注释2 L1 GOTO L6 L2 FRAME FULL [[Ljava/lang/String; java/lang/Object] [java/lang/Throwable] ASTORE 2 ALOAD 1 MONITOREXIT //注释3 L3 ALOAD 2 ATHROW L6 LINENUMBER 9 L6 FRAME CHOP 1 RETURN L7 LOCALVARIABLE args [Ljava/lang/String; L4 L7 0 MAXSTACK = 2 MAXLOCALS = 3 注释1 MONITORENTER的意思为:每个对象都有一个监视锁(Monitor),当Monitor被占用时就会处于锁定状态,线程执行MONITORENTER指令时尝试获取Monitor的所有权,过程如下: • 如果Monitor的进入数为0,则该线程进入Monitor,并将进入数设置为1,该线程即为Monitor的所有者(Owner) • 如果该线程已经占用Monitor,只是重新进入Monitor,则进入Monitor的进入数加1 • 如果其它线程已经占用Monitor,则该线程进入阻塞状态,直到Monitor进入数为0,再重新尝试获取Monitor的所有权 注释2 MONITOREXIT的意思为:执行指令时,Monitor的进入数减1,如果减1后进入数为0,该线程退出Monitor,不再是这个Monitor的所有者,其它被Monitor阻塞的线程重新尝试获取Monitor的所有权。 总结 通过注释1和注释2可知,synchronized的实现原理,底层是通过Monitor的对象来完成,其实wait和notify等方法也依赖Monitor,这就是为什么wait和notify方法必须要在同步方法内调用,否则会抛出java.lang.IllegalMonitorStateException的原因。 如果程序正常执行则按上述描述即可完成,如果程序在同步方法内发生异常,代码则会走注释3,在注释3可以看到MONITOREXIT指令,也就是synchronized已经处理异常情况下的退出。 注:方法级别的synchronized不会在字节码指令中有所体现,而是在常量池中增加了ACC_SYNCHRONIZED标识符,JVM就是通过该标识符来实现同步的,方法调用时,JVM会判断方法的ACC_SYNCHRONIZED是否被设置,如果被设置,线程执行方法前会先获取Monitor所有权,执行完方法后再释放Monitor所有权,本质是一样的。 synchronized原理进阶 轻量级锁 轻量级锁的使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(也就是没有竞争),那么可以使用轻量级锁来优化。 轻量级锁对使用者是透明的,即语法仍然是synchronized 假设有两个方法同步块,利用同一个对象加锁 static final Object obj = new Object(); public static void method1() { synchronized (obj) { // 同步块 A method2(); } } public static void method2() { synchronized (obj) { // 同步块 B } } 创建锁记录(Lock Record)对象,每个线程的栈帧都会包含一个锁记录的结构,内部可以存储锁定对象的Mark Word   图3 让锁记录中Object reference指向锁对象,并尝试用cas替换Object的Mark Word,将Mark Word的值存入锁记录   图4 如果cas替换成功,对象头中存储了锁记录地址和状态00,表示由该线程给对象加锁,这是图示如下   图5 如果cas失败,有两种情况 • 如果是其它线程已经持有了该Object的轻量级锁,这是表明有竞争,进入锁膨胀过程 • 如果是自己线程执行了synchronized锁重入,那么再添加一条Lock Record作为重入的技术   图6 当退出synchronized代码块(解锁时),如果有取值为null的锁记录,表示由重入,这是重置锁记录,表示重入技术减一   图7 当退出synchronized代码块(解锁时),锁记录的值不为null,这时使用cas将Mark Word的值回复给对象头 • 成功,则解锁成功 • 失败,说明轻量级锁进行了锁膨胀或已经升级为重量级锁,进入重量级锁解锁流程 锁膨胀 如果在尝试加轻量级锁的过程中,CAS操作无法成功,这是一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这是需要进行锁膨胀,将轻量级锁变为重量级锁。 当Thread-1进行轻量级加锁时,Thread-0已经对该对象加了轻量级锁   图8 这是Thread-1加轻量级锁失败,进入锁膨胀流程 • 即为Object对象申请Monitor锁,让Object指向重量级锁地址 • 然后自己进入Monitor的EntryList BLOCKED   图9 当Thread-0退出同步块解锁时,使用cas将Mark Word的值恢复给对象头,失败,这是会进入重量级解锁流程,即按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList 中BLOCKED线程 自旋优化 重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步,释放了锁),这是当前线程就可以避免阻塞。 自旋重试成功的情况 线程1(core 1上) 对象Mark 线程2(core 2 上) - 10(重量锁) - 访问同步块,获取Monitor 10(重量锁)重量锁指针 - 成功(加锁) 10(重量锁)重量锁指针 - 执行同步块 10(重量锁)重量锁指针 - 执行同步块 10(重量锁)重量锁指针 访问同步块,获取Monitor 执行同步块 10(重量锁)重量锁指针 自旋重试 执行完毕 10(重量锁)重量锁指针 自旋重试 成功(解锁) 01(无锁) 自旋重试 - 10(重量锁)重量锁指针 成功(加锁) - 10(重量锁)重量锁指针 执行同步块 - … … 自旋重试失败的情况 线程1(core 1上) 对象Mark 线程2(core 2 上) - 10(重量锁) - 访问同步块,获取Monitor 10(重量锁)重量锁指针 - 成功(加锁) 10(重量锁)重量锁指针 - 执行同步块 10(重量锁)重量锁指针 - 执行同步块 10(重量锁)重量锁指针 访问同步块,获取Monitor 执行同步块 10(重量锁)重量锁指针 自旋重试 执行同步块 10(重量锁)重量锁指针 自旋重试 执行同步块 10(重量锁)重量锁指针 自旋重试 执行同步块 10(重量锁)重量锁指针 阻塞 - … … • 自旋会占用CPU时间,单核CPU自旋就是浪费,多核CPU自旋才能发挥优势。 • 在Java 6之后自旋锁是自适应的,比如对象刚刚的一次自旋操作成功过,那么认为这次自旋成功的可能性会高,就多自旋几次;反之,就少自旋甚至不自旋,总之,比较智能。 • Java 7之后不能控制是否开启自旋功能。 偏向锁 轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行CAS操作。 Java 6中引入了偏向锁做进一步优化:只有第一次使用CAS将线程ID设置到对象的Mark Word头,之后发现这个线程ID是自己的就表示没有竞争,不用重新CAS,以后只要不发生竞争,这个对象就归该线程所有。 注: Java 15之后废弃偏向锁,默认是关闭,如果想使用偏向锁,配置-XX:+UseBiasedLocking启动参数。 启动偏向锁之后,偏向锁有一个延迟生效的机制,这是因为JVM启动时会进行一系列的复杂活动,比如装载配置,系统类初始化等等。在这个过程中会使用大量synchronized关键字对对象加锁,且这些锁大多数都不是偏向锁。为了减少初始化时间,JVM默认延时加载偏向锁。这个延时的时间大概为4s左右,具体时间因机器而异。当然我们也可以设置JVM参数 -XX:BiasedLockingStartupDelay=0 来取消延时加载偏向锁。 例如: static final Object obj = new Object(); public static void m1() { synchronized (obj) { // 同步块 A m2(); } } public static void m2() { synchronized (obj) { // 同步块 B m3(); } } public static void m3() { synchronized (obj) { } } 如果关闭偏向锁,使用轻量锁情况:   图10 开启偏向锁,使用偏向锁情况:   图11 偏向状态 回忆一下对象头格式 Mark Word (32 bits) State identity_hashcode:25 age:4 biased_lock:1 lock:2 Normal thread:23 epoch:2 age:4 biased_lock:1 lock:2 Biased ptr_to_lock_record:30 lock:2 LightweightLocked ptr_to_heavyweight_monitor:30 lock:2 HeavyweightLocked  lock:2 Marked for GC 一个对象创建时: • 如果开启了偏向锁(默认开启),那么对象创建后,Mark Word值为0x05,也就是最后是3位为101,这是它的thread、epoch、age都为0 • 如果没有开启偏向锁,那么对象创建后,Mark Word值为0x01,也就是最后3位为001,这时它的hashcode、age都为0,第一次用到hashcode时才会赋值 我们来验证下,使用jol第三方工具,以及对工具打印对象头做了一个处理,让对象头开起来更简便: 测试代码 public synchronized static void main(String[] args){ log.info("{}", toSimplePrintable(object)); } 开启偏向锁的情况下 打印的数据如下(由于Java15之后偏向锁废弃,因此打开偏向锁打印会警告) 17:15:17 [main] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 最后为101,其他都为0,验证了上述第一条。 可能你又要问了,我这也没使用synchronized关键字呀,那不也应该是无锁么?怎么会是偏向锁呢? 仔细看一下偏向锁的组成,对照输出结果红色划线位置,你会发现占用 thread 和 epoch 的 位置的均为0,说明当前偏向锁并没有偏向任何线程。此时这个偏向锁正处于可偏向状态,准备好进行偏向了!你也可以理解为此时的偏向锁是一个特殊状态的无锁。 关闭偏向锁的情况下 打印的数据如下 17:18:32 [main] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 最后为001,其它都是0,验证了上述第二条。 接下来验证加锁的情况,代码如下: private static Object object = new Object(); public synchronized static void main(String[] args){ new Thread(()->{ log.info("{}", "synchronized前"); log.info("{}", toSimplePrintable(object)); synchronized (object){ log.info("{}", "synchronized中"); log.info("{}", toSimplePrintable(object)); } log.info("{}", "synchronized后"); log.info("{}", toSimplePrintable(object)); },"t1").start(); } 开启偏向锁的情况,打印数据如下 17:24:05 [t1] c.MyClass03 - synchronized前 17:24:05 [t1] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 17:24:05 [t1] c.MyClass03 - synchronized中 17:24:05 [t1] c.MyClass03 - 00000000 00000000 00000000 00000001 00001110 00000111 01001000 00000101 17:24:05 [t1] c.MyClass03 - synchronized后 17:24:05 [t1] c.MyClass03 - 00000000 00000000 00000000 00000001 00001110 00000111 01001000 00000101 使用了偏向锁,并记录了线程的值(101前面的一串数字),但是处于偏向锁的对象解锁后,线程id仍存储于对象头中。 关闭偏向锁的情况,打印数据如下 17:28:24 [t1] c.MyClass03 - synchronized前 17:28:24 [t1] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 17:28:24 [t1] c.MyClass03 - synchronized中 17:28:24 [t1] c.MyClass03 - 00000000 00000000 00000000 00000001 01110000 00100100 10101001 01100000 17:28:24 [t1] c.MyClass03 - synchronized后 17:28:24 [t1] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 使用轻量锁(最后为000),并且记录了占中存储的锁信息地址(000前面一串数字),同步块结束后恢复到原先状态(因为没有使用hashcode,所以hashcode值为0)。 偏向锁撤销 在真正讲解偏向撤销之前,需要和大家明确一个概念——偏向锁撤销和偏向锁释放是两码事。 • 撤销:笼统的说就是多个线程竞争导致不能再使用偏向模式的时候,主要是告知这个锁对象不能再用偏向模式 • 释放:和你的常规理解一样,对应的就是 synchronized 方法的退出或 synchronized 块的结束 何为偏向撤销? 从偏向状态撤回原有的状态,也就是将 MarkWord 的第 3 位(是否偏向撤销)的值,从 1 变回 0 如果只是一个线程获取锁,再加上「偏心」的机制,是没有理由撤销偏向的,所以偏向的撤销只能发生在有竞争的情况下 撤销-hashcode调用 调用了对象的hashcode会导致偏向锁被撤销: • 轻量级锁会在锁记录中记录hashcode • 重量级锁会在Monitor中记录hashcode 测试代码如下 private static Object object = new Object(); public synchronized static void main(String[] args){ object.hashCode();//调用hashcode new Thread(()->{ log.info("{}", "synchronized前"); log.info("{}", toSimplePrintable(object)); synchronized (object){ log.info("{}", "synchronized中"); log.info("{}", toSimplePrintable(object)); } log.info("{}", "synchronized后"); log.info("{}", toSimplePrintable(object)); },"t1").start(); } 打印如下: 17:36:05 [t1] c.MyClass03 - synchronized前 17:36:06 [t1] c.MyClass03 - 00000000 00000000 00000000 01011111 00100001 00001000 10110101 00000001 17:36:06 [t1] c.MyClass03 - synchronized中 17:36:06 [t1] c.MyClass03 - 00000000 00000000 00000000 00000001 01101110 00010011 11101001 01100000 17:36:06 [t1] c.MyClass03 - synchronized后 17:36:06 [t1] c.MyClass03 - 00000000 00000000 00000000 01011111 00100001 00001000 10110101 00000001 撤销-其它线程使用对象 当有其它线程使用偏向锁对象时,会将偏向锁升级为轻量级锁。 测试代码如下 private static void test2() { Thread t1 = new Thread(() -> { synchronized (object) { log.info("{}", toSimplePrintable(object)); } synchronized (MyClass03.class) { MyClass03.class.notify();//t1执行完之后才通知t2执行 } }, "t1"); t1.start(); Thread t2 = new Thread(() -> { synchronized (MyClass03.class) { try { MyClass03.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.info("{}", toSimplePrintable(object)); synchronized (object) { log.info("{}", toSimplePrintable(object)); } log.info("{}", toSimplePrintable(object)); }, "t2"); t2.start(); } 打印数据如下 17:51:38 [t1] c.MyClass03 - 00000000 00000000 00000000 00000001 01000111 00000000 11101000 00000101 17:51:38 [t2] c.MyClass03 - 00000000 00000000 00000000 00000001 01000111 00000000 11101000 00000101 17:51:38 [t2] c.MyClass03 - 00000000 00000000 00000000 00000001 01111000 00100000 01101001 01010000 17:51:38 [t2] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 可以看到线程t1是使用偏向锁,线程t2使用锁之前是一样的,但是一旦使用了锁,便升级为轻量级锁,执行完同步代码之后,恢复成撤销偏向锁的状态。 撤销-调用wait/notify 代码如下 private static void test3(){ Thread t1 = new Thread(() -> { log.info("{}", toSimplePrintable(object)); synchronized (object) { log.info("{}", toSimplePrintable(object)); try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } log.info("{}", toSimplePrintable(object)); } }, "t1"); t1.start(); new Thread(() -> { try { Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (object) { log.debug("notify"); object.notify(); } }, "t2").start(); } 打印数据如下 17:57:57 [t1] c.MyClass03 - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 17:57:57 [t1] c.MyClass03 - 00000000 00000000 00000000 00000001 00001111 00001100 11010000 00000101 17:58:02 [t2] c.MyClass03 - notify 17:58:02 [t1] c.MyClass03 - 00000000 00000000 01100000 00000000 00000011 11000001 10000010 01110010 调用wait和notify得是用Monitor,所以会从偏向锁升级为重量级锁。 批量重偏向 如果对象虽然被多个线程访问,但没有竞争,这是偏向了线程t1的对象仍然有机会重新偏向t2,重偏向会重置对象的Thread ID。 当撤销偏向锁阈值超过20次后,JVM会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至加锁线程。 代码如下 public static class Dog{} private static void test4() { Vector<Dog> list = new Vector<>(); Thread t1 = new Thread(() -> { for (int i = 0; i < 30; i++) { Dog d = new Dog(); list.add(d); synchronized (d) { log.info("{}", i+"\t"+toSimplePrintable(d)); } } synchronized (list) { list.notify(); } }, "t1"); t1.start(); Thread t2 = new Thread(() -> { synchronized (list) { try { list.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("===============> "); for (int i = 0; i < 30; i++) { Dog d = list.get(i); log.info("{}", i+"\t"+toSimplePrintable(d)); synchronized (d) { log.info("{}", i+"\t"+toSimplePrintable(d)); } log.info("{}", i+"\t"+toSimplePrintable(d)); } }, "t2"); t2.start(); } 打印如下   图12 另外我在测试的是否发现一个线程,当对象是普通类(如Dog)时,重偏向的阈值就是20,也就是第21次开启了偏向锁,但是如果把普通类替换成Object时,重偏向的阈值就是9,也就是第10次开启了偏向锁并重偏向(如图13),这是怎么回事儿,有了解的同学可以评论交流下。   图13 批量撤销 当撤销偏向锁阈值超过40次后,JVM会这样觉得,自己确实偏向错了,根本不该偏向,于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的。 代码如下 static Thread t1, t2, t3; private static void test6() throws InterruptedException { Vector<Dog> list = new Vector<>(); int loopNumber = 40; t1 = new Thread(() -> { for (int i = 0; i < loopNumber; i++) { Dog d = new Dog(); list.add(d); synchronized (d) { log.info("{}", i + "\t" + toSimplePrintable(d)); } } LockSupport.unpark(t2); }, "t1"); t1.start(); t2 = new Thread(() -> { LockSupport.park(); log.debug("===============> "); for (int i = 0; i < loopNumber; i++) { Dog d = list.get(i); log.info("{}", i + "\t" + toSimplePrintable(d)); synchronized (d) { log.info("{}", i + "\t" + toSimplePrintable(d)); } log.info("{}", i + "\t" + toSimplePrintable(d)); } LockSupport.unpark(t3); }, "t2"); t2.start(); t3 = new Thread(() -> { LockSupport.park(); log.debug("===============> "); for (int i = 0; i < loopNumber; i++) { Dog d = list.get(i); log.info("{}", i + "\t" + toSimplePrintable(d)); synchronized (d) { log.info("{}", i + "\t" + toSimplePrintable(d)); } log.info("{}", i + "\t" + toSimplePrintable(d)); } }, "t3"); t3.start(); t3.join(); log.info("{}", toSimplePrintable(new Dog())); } 打印如下  

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

移动开发大调查:2%码农拿走54%的APP收入

开发者经济近日发布的《2014年移动开发经济报告》(请登录下载)调查了约一万名开发者,结果显示50%的iOS开发者和47%的Android开发者处于应用开发贫困线以下,每月收入不到500美元。这意味着移动开发并不是一个能养家糊口的靠谱行当。以下是开发者APP月收入结构: 此外,虽然游戏是各大应用商店的绝对主力,但是游戏开发者依然在贫困线上挣扎。只有三分之一的开发者在搞游戏开发,这与游戏在APP总数中占据半壁江山不成比例。 调查指出,移动游戏开发是赢者通吃的游戏,贫富差距悬殊。88%的开发者瓜分了app总收入的11%,月收入不到1万美元。而另外9%的开发者瓜分 了APP总收入的35%,月收入在1-10万美元之间。最大的赢家是不到2%的开发者,他们瓜分了app总收入的54%,月入超过10万美元。(下图) 多数游戏,约57%,每月收入不到500美元。为了解决营收问题,很多开发者选择发布多款应用。事实上,大多数月收入超过10万美元的开发者都发布了至少11款游戏。 开发和推广多款app还有助于开发者积累经验,吸取教训;此外,调查还发现那些利用第三方工具进行开发、分析和货币化的开发者更容易挣到钱。 最后,值得注意的是,那些面向企业客户而非消费者的开发者开发出收入5000美元的APP的机会要大一倍,开发出收入2.5万美元APP的机会则大三倍。 开发平台方面,有趣的是,大多数iOS和Android开发者都没有使用原生语言开发APP,事实上47%的iOS开发者都没有使用 Objective-C,而42%de Android开发者也没有用Java开发,这意味着他们使用了第三方开发工具,从而可以使用较为简单的语言,例如HTML、CSS和 Javascript开发。

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

集团企业大用户选择企业邮箱因素

企业邮箱是一个公司办公的必备沟通工具,用户较多的大型企业该如何选择企业邮箱呢。集团型企业用户要选择更合适自身企业邮箱需要考虑方面则更多,是否能够保证企业邮箱安全、邮箱送达率、邮箱的多个场景内实现有效沟通。 因公司规模的不同,对于邮箱上使用的侧重点也会有所差异。相对于集团企业邮箱,专业的企业邮箱更适合集团用户使用。 企业邮箱可以非常便捷的解决公司与员工,公司与公司间的信息交流问题, 集团企业邮箱通过企业自己的域名(如:@tom)做邮箱后缀,企业@前面企业内部自定义,可以是员工姓名,也可以是编号等等。这样便于各个部门和员工的方便交流。另外,正是因为企业邮箱后缀统一化,发出去的邮件都带有企业标志,并且如果员工离职或者人事变动邮箱可以收回来。这样方便企业管理,不会有漏洞,比较安全。 在功能上,企业邮箱,首先要提供无限邮箱容量、大型企业架构、个性化定制、分级管理等方面的基本功能和配置外,还要提供邮箱撤回、邮件误删恢复、等增值服务。这些功能能够保证几百人甚至更大的集团公司,在处理事物上更加得心应手。在移动端更是推出随心邮功能,提高移动办公的高效性及安全性。 在安全上。TOM企业邮箱采用的是先进存储技术,多服务器负载平衡等技术,邮件服务器系统速度快、稳定性强、安全可靠、功能全面。强劲企业邮箱系统管理帮您解决企业信息的安全性和稳定性问题。这也提高了集团企业邮箱的安全性。以上由TOM企业邮箱整理。 TOM企业邮箱在市场也是近20年的企业级邮箱服务商,TOM企业邮箱技术在原有的TOM邮箱基础上,不断升级改造,专业的企业级邮箱在杀毒、反垃圾和商务沟通上更能给客户带来实际的最佳体验,更有多种功能针对企业痛点开发而来

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

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

Spring

Spring

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

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册