当前位置: 首页 > 后端技术 > Java

学习Java多线程的一些思考

时间:2023-04-02 01:55:09 Java

学习Java多线程的一些思考采用独家技术保证对象的稳定性,避免即使是瞬时状态冲突的影响。所有方法都基于以下三个基本策略:通过确保所有方法永远不会同时修改对象的表示,即对象永远不会进入不一致状态,消除部分或全部独占控制的需要动态机制确保一个对象同一时间只能被一个线程访问。通过隐藏或控制对象的使用权,从结构上保证只有一个线程可以使用该对象。最简单的具有不变性的对象,就是对象中根本没有数据。因此,它们的方法是无状态的,可以理解为这些方法不依赖于任何对象的任何数据。例如:publicclassStatelessAdder{publicstaticintadder(inta,intb){returna+b;相同的安全性适用于数据由final关键字修改的类。这样的类实例不会面临底层的读写冲突和写写冲突。因为它的值不会被覆盖。并且,只要以一致合理的方式创建它们的初始值,这些对象就不会出现更高级别的不可变性错误,例如:classImmutableAdder{privatefinalintoffset;publicImmutableAdder(inta){offset=a;}publicintaddOffset(intb){returnoffset+b;}}在构造函数执行结束之前,无法访问对象的数据。与串行编程相比,这在并发编程中确实更难保证一点。构造函数应该只执行与初始化数据相关的操作。如果一个方法依赖于对象初始化的完成,那么构造函数不应该调用该方法。如果在其他类可访问的成员变量或表中创建对象,则构造函数应避免使用对该对象的引用,并避免使用this关键字调用其他方法。可以理解为避免由此造成的泄漏错误,同步使用锁可以避免底层的存储冲突和高层相应的不变约束冲突,例如:classEvent{privateintn=0;publicintadd(){++n;++n;返回n;}}上面程序中没有加锁,如果多个线程同时执行Event中的add()方法,可能会导致数据不一致的错误。锁定示例:classEvent{privateintn=0;publicsynchronizedintadd(){++n;++n;returnn;}}在上面的程序中,在add()方法中加入了synchronized关键字,使得Conflict-avoidingexecutionroutemechanismObject类及其子类的每个实例都有一把锁。int和float等基本类型不是Object类。原始类型只能通过包含它们的对象来锁定。每个单独的成员变量都不能标记为同步。锁只能应用于作为成员变量的方法。成员变量可以声明为volatile类型,这会影响成员变量的原子性、可见性和顺序。包含基本类型元素的数组也是拥有锁的对象,但数组中的基本元素没有。锁定对象类型的数组不会锁定其基本元素。Class的实例是Object,在用staticsynchronized声明的方法中可以使用Class对象的相关锁。同步用法:synchronizedvoidfunc(){...}voidfunc(){synchronized(this){...}}synchronized关键字不是方法签名的一部分,所以子类继承父类时,synchronized修改字符不会被继承。因此,接口中的方法不能声明为同步的。同样,构造函数不能声明为同步的,而构造函数中的过程可以声明为同步的。子类和父类的方法使用同一个锁,但是内部类的锁与其外部类无关。但是,非静态内部类可以锁定其外部类,liru:synchronized(OuterClass.this){}LockapplicationandreleaseLockapplication和release在使用synchronized关键字时根据内部底层应用程序释放协议来使用。所有的锁都是块结构。进入synchronized方法时获取锁,退出时释放锁。锁定是在“每个线程”的基础上完成的,而不是“每个调用”的基础。同步和原子操作(atomic)并不等价。但是同步可以实现原子操作。如果一个线程释放了锁,其他线程可以拿到,但是这个线程什么时候拿到锁就不能保证了,这里不公平。JVM在类加载和初始化时自动为Class类申请和释放锁。全同步对象锁是最基本的信息接收控制机制。如果S客户端想要在另一个方法或代码块正在执行时调用对象的方法,则锁可以阻止S用户。原子对象基于锁的最安全的并发面向对象设计策略是限制对完全同步的关注:所有方法都是同步的,没有公共成员变量,或其他封装问题。所有的方法都是有限的,(不存在无限递归和无限循环),所有的操作最终都会释放锁所有的成员变量在构造函数中都已经初始化为稳定一致的状态在一个方法的开始和结束,对象状态应该是稳定一致的,即使出现错误,也应该是这样死Lockexamplepublicstaticvoidmain(String[]args){Resource1resource1=newResource1("resource1");Resource1resource2=newResource1("resource2");Threadt1=newThread(()->{for(inti=0;i<100;i++){resource1.saveResource(resource2);}});Threadt2=newThread(()->{for(inti=0;i<100;i++){resource2.saveResource(resource1);}});t1.开始();t2.开始();}上面的代码在执行过程中会产生死锁。死锁:是两个或多个线程有访问两个对象或多个对象的权限,而线程在已经获得锁的情况下都在等待其他线程释放锁。序列化资源为了避免死锁和其他活性故障,我们需要其他_独家技术_,例如序列化资源。序列化资源:就是将每一个嵌套的同步方法或代码块中使用的对象都关联上一个数字标签。如果同步操作是根据对象标签的大小来排序的,那么死锁就不会发生。线程A已经获取了1的同步锁,正在等待2的同步锁,按照初始化顺序,可以避免死锁。例如:publicclassApplyLock{privateListlistOf=newArrayList<>();publicsynchronizedbooleanapplyLock(Resourceresource1,Resourceresource2){if(listOf.contains(resource1)||listOf.contains(resource2)){返回false;}else{//初始化资源listOf.add(resource1);listOf.add(resource2);返回真;}}publicsynchronizedvoidfreeListOf(Resourceresource1,Resourceresource2){listOf.remove(resource1);listOf.remove(resource2);}}我们可以使用System.identityHashCode()的返回值。即使类本身已经覆盖了hashCode方法,System.identityHashCode()也会直接调用hashCode。我们无法保证System.identityHashCode()返回值的唯一性,但在实际运行的系统中,很大程度上保证了该方法的唯一性。例如:publicsynchronizedbooleanapplyLock(Resourceresource1,Resourceresource2){if(listOf.contains(resource1)||listOf.contains(resource2)){returnfalse;}elseif(System.identityHashCode(resource1)