‘壹’ java的问题,那几个变量为什么要在前面假如final呢
在方法参数前面加final关键字就是为了防止数据在方法体中被修改。这里主要分两种情况:第一,用final修饰基本数据类型;第二,用final修饰引用类型。
第一种情况,修饰基本类型(非引用类型)。这时参数的值在方法体内是不能被修改的,即不能被重新赋值。否则编译就通不过。例如:
public void testInt(final int param1) {
param1 = 100;
}
在方法体内修改了基本数据类型变量的值,在Eclipse中则会提示:“ The final local variable param1 cannot be assigned. It must be blank and not using a compound assignment.“
第二种情况,修饰引用类型。这时参数变量所引用的对象是不能被改变的。作为引用的拷贝,参数在方法体里面不能再引用新的对象。否则编译通不过。例如:
public void testFinal2(final Object param2) {
param2 = new Object();
}
在方法体内修改了参数的引用,在Eclipse中则会提示:“ The final local variable param2 cannot be assigned. It must be blank and not using a compound assignment.“
‘贰’ 如何创建线程如何保证线程安全
线程安全等级
之前的博客中已有所提及“线程安全”问题,一般我们常说某某类是线程安全的,某某是非线程安全的。其实线程安全并不是一个“非黑即白”单项选择题。按照“线程安全”的安全程度由强到弱来排序,我们可以将java语言中各种操作共享的数据分为以下5类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。
1、不可变
在java语言中,不可变的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要再采取任何的线程安全保障措施。如final关键字修饰的数据不可修改,可靠性最高。
2、绝对线程安全
绝对的线程安全完全满足Brian GoetZ给出的线程安全的定义,这个定义其实是很严格的,一个类要达到“不管运行时环境如何,调用者都不需要任何额外的同步措施”通常需要付出很大的代价。
3、相对线程安全
相对线程安全就是我们通常意义上所讲的一个类是“线程安全”的。
它需要保证对这个对象单独的操作是线程安全的,我们在调用的时候不需要做额外的保障措施,但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。
在java语言中,大部分的线程安全类都属于相对线程安全的,例如Vector、HashTable、Collections的synchronizedCollection()方法保证的集合。
4、线程兼容
线程兼容就是我们通常意义上所讲的一个类不是线程安全的。
线程兼容是指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境下可以安全地使用。Java API中大部分的类都是属于线程兼容的。如与前面的Vector和HashTable相对应的集合类ArrayList和HashMap等。
5、线程对立
线程对立是指无论调用端是否采取了同步错误,都无法在多线程环境中并发使用的代码。由于java语言天生就具有多线程特性,线程对立这种排斥多线程的代码是很少出现的。
一个线程对立的例子是Thread类的supend()和resume()方法。如果有两个线程同时持有一个线程对象,一个尝试去中断线程,另一个尝试去恢复线程,如果并发进行的话,无论调用时是否进行了同步,目标线程都有死锁风险。正因此如此,这两个方法已经被废弃啦。
二、线程安全的实现方法
保证线程安全以是否需要同步手段分类,分为同步方案和无需同步方案。
1、互斥同步
互斥同步是最常见的一种并发正确性保障手段。同步是指在多线程并发访问共享数据时,保证共享数据在同一时刻只被一个线程使用(同一时刻,只有一个线程在操作共享数据)。而互斥是实现同步的一种手段,临界区、互斥量和信号量都是主要的互斥实现方式。因此,在这4个字里面,互斥是因,同步是果;互斥是方法,同步是目的。
在java中,最基本的互斥同步手段就是synchronized关键字,synchronized关键字编译之后,会在同步块的前后分别形成monitorenter和monitorexit这两个字节码质量,这两个字节码指令都需要一个reference类型的参数来指明要锁定和解锁的对象。
此外,ReentrantLock也是通过互斥来实现同步。在基本用法上,ReentrantLock与synchronized很相似,他们都具备一样的线程重入特性。
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能问题,因此这种同步也成为阻塞同步。从处理问题的方式上说,互斥同步属于一种悲观的并发策略,总是认为只要不去做正确地同步措施(例如加锁),那就肯定会出现问题,无论共享数据是否真的会出现竞争,它都要进行加锁。
2、非阻塞同步
随着硬件指令集的发展,出现了基于冲突检测的乐观并发策略,通俗地说,就是先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再采用其他的补偿措施。(最常见的补偿错误就是不断地重试,直到成功为止),这种乐观的并发策略的许多实现都不需要把线程挂起,因此这种同步操作称为非阻塞同步。
非阻塞的实现CAS(compareandswap):CAS指令需要有3个操作数,分别是内存地址(在java中理解为变量的内存地址,用V表示)、旧的预期值(用A表示)和新值(用B表示)。CAS指令执行时,CAS指令指令时,当且仅当V处的值符合旧预期值A时,处理器用B更新V处的值,否则它就不执行更新,但是无论是否更新了V处的值,都会返回V的旧值,上述的处理过程是一个原子操作。
CAS缺点:
ABA问题:因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。
ABA问题的解决思路就是使用版本号。在变量前面追加版本号,每次变量更新的时候把版本号加一,那么A-B-A就变成了1A-2B-3C。JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
3、无需同步方案
要保证线程安全,并不是一定就要进行同步,两者没有因果关系。同步只是保证共享数据争用时的正确性的手段,如果一个方法本来就不涉及共享数据,那它自然就无需任何同步操作去保证正确性,因此会有一些代码天生就是线程安全的。
1)可重入代码
可重入代码(ReentrantCode)也称为纯代码(Pure Code),可以在代码执行的任何时刻中断它,转而去执行另外一段代码,而在控制权返回后,原来的程序不会出现任何错误。所有的可重入代码都是线程安全的,但是并非所有的线程安全的代码都是可重入的。
可重入代码的特点是不依赖存储在堆上的数据和公用的系统资源、用到的状态量都是由参数中传入、不调用 非可重入的方法等。
(类比:synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到一个对象锁后,再次请求此对象锁时时可以再次得到该对象的锁)
2)线程本地存储
如果一段代码中所需的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行?如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内。这样无需同步也能保证线程之间不出现数据的争用问题。
符合这种特点的应用并不少见,大部分使用消费队列的架构模式(如“生产者-消费者”模式)都会将产品的消费过程尽量在一个线程中消费完。其中最重要的一个应用实例就是经典的Web交互模型中的“一个请求对应一个服务器线程(Thread-per-Request)”的处理方式,这种处理方式的广泛应用使得很多Web服务器应用都可以使用线程本地存储来解决线程安全问题。
‘叁’ Java中的关键字static和final的作用
final
根据程序上下文环境,Java关键字final有“这是无法改变的”或者“终态的”含义,它可以修饰非抽象类、非抽象类成员方法和变量。你可能出于两种理解而需要阻止改变:设计或效率。
final类不能被继承,没有子类,final类中的方法默认是final的。
final方法不能被子类的方法覆盖,但可以被继承。
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
final不能用于修饰构造方法。
注意:父类的private成员方法是不能被子类方法覆盖的,因此private类型的方法默认是final类型的。
1、final类
final类不能被继承,因此final类的成员方法没有机会被覆盖
,默认都是final的。在设计类时候,如果这个类不需要有子类,类的实现细节不允许改变,并且确信这个类不会载被扩展,那么就设计为final类。
2、final方法
如果一个类不允许其子类覆盖某个方法,则可以把这个方法声明为final方法。
使用final方法的原因有二:
第一、把方法锁定,防止任何继承类修改它的意义和实现。
第二、高效。编译器在遇到调用final方法时候会转入内嵌机制,大大提高执行效率。
例如:
publicclassTest1{
publicstaticvoidmain(String[]args){
//TODO自动生成方法存根
}
publicvoidf1(){
System.out.println("f1");
}
//无法被子类覆盖的方法
publicfinalvoidf2(){
System.out.println("f2");
}
publicvoidf3(){
System.out.println("f3");
}
privatevoidf4(){
System.out.println("f4");
}
}
publicclassTest2extendsTest1{
publicvoidf1(){
System.out.println("Test1父类方法f1被覆盖!");
}
publicstaticvoidmain(String[]args){
Test2t=newTest2();
t.f1();
t.f2();//调用从父类继承过来的final方法
t.f3();//调用从父类继承过来的方法
//t.f4();//调用失败,无法从父类继承获得
}
}
3、final变量(常量)
用final修饰的成员变量表示常量,值一旦给定就无法改变!
final修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。
从下面的例子中可以看出,一旦给final变量初值后,值就不能再改变了。
另外,final变量定义的时候,可以先声明,而不给初值,这中变量也称为final空白,无论什么情况,编译器都确保空白final在使用之前必须被初始化。但是,final空白在final关键字final的使用上提供了更大的灵活性,为此,一个类中的final数据成员就可以实现依对象而有所不同,却有保持其恒定不变的特征。
packageorg.leimin;
publicclassTest3{
privatefinalStringS="final实例变量S";
privatefinalintA=100;
publicfinalintB=90;
publicstaticfinalintC=80;
privatestaticfinalintD=70;
publicfinalintE;//final空白,必须在初始化对象的时候赋初值
publicTest3(intx){
E=x;
}
/**
*@paramargs
*/
publicstaticvoidmain(String[]args){
Test3t=newTest3(2);
//t.A=101;//出错,final变量的值一旦给定就无法改变
//t.B=91;//出错,final变量的值一旦给定就无法改变
//t.C=81;//出错,final变量的值一旦给定就无法改变
//t.D=71;//出错,final变量的值一旦给定就无法改变
System.out.println(t.A);
System.out.println(t.B);
System.out.println(t.C);//不推荐用对象方式访问静态字段
System.out.println(t.D);//不推荐用对象方式访问静态字段
System.out.println(Test3.C);
System.out.println(Test3.D);
//System.out.println(Test3.E);//出错,因为E为final空白,依据不同对象值有所不同.
System.out.println(t.E);
Test3t1=newTest3(3);
System.out.println(t1.E);//final空白变量E依据对象的不同而不同
}
privatevoidtest(){
System.out.println(newTest3(1).A);
System.out.println(Test3.C);
System.out.println(Test3.D);
}
publicvoidtest2(){
finalinta;//final空白,在需要的时候才赋值
finalintb=4;//局部常量--final用于局部变量的情形
finalintc;//final空白,一直没有给赋值.
a=3;
//a=4;出错,已经给赋过值了.
//b=2;出错,已经给赋过值了.
}
}
4、final参数
当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。
publicclassTest4{
publicstaticvoidmain(String[]args){
newTest4().f1(2);
}
publicvoidf1(finalinti){
//i++;//i是final类型的,值不允许改变的.
System.out.print(i);
}
}
二、static
static表示“全局”或者“静态”的意思,用来修饰成员变量和成员方法,也可以形成静态static代码块,但是Java语言中没有全局变量的概念。
被static修饰的成员变量和成员方法独立于该类的任何对象。也就是说,它不依赖类特定的实例,被类的所有实例共享。只要这个类被加载,Java虚拟机就能根据类名在运行时数据区的方法区内定找到他们。因此,static对象可以在它的任何对象创建之前访问,无需引用任何对象。
用public修饰的static成员变量和成员方法本质是全局变量和全局方法,当声明它类的对象市,不生成static变量的副本,而是类的所有实例共享同一个static变量。
static变量前可以有private修饰,表示这个变量可以在类的静态代码块中,或者类的其他静态成员方法中使用(当然也可以在非静态成员方法中使用--废话),但是不能在其他类中通过类名来直接引用,这一点很重要。实际上你需要搞明白,private是访问权限限定,static表示不要实例化就可以使用,这样就容易理解多了。static前面加上其它访问权限关键字的效果也以此类推。
static修饰的成员变量和成员方法习惯上称为静态变量和静态方法,可以直接通过类名来访问,访问语法为:
类名.静态方法名(参数列表...)
类名.静态变量名
用static修饰的代码块表示静态代码块,当Java虚拟机(JVM)加载类时,就会执行该代码块(用处非常大,呵呵)。
1、static变量
按照是否静态的对类成员变量进行分类可分两种:一种是被static修饰的变量,叫静态变量或类变量;另一种是没有被static修饰的变量,叫实例变量。两者的区别是:
对于静态变量在内存中只有一个拷贝(节省内存),JVM只为静态分配一次内存,在加载类的过程中完成静态变量的内存分配,可用类名直接访问(方便),当然也可以通过对象来访问(但是这是不推荐的)。
对于实例变量,没创建一个实例,就会为实例变量分配一次内存,实例变量可以在内存中有多个拷贝,互不影响(灵活)。
2、静态方法
静态方法可以直接通过类名调用,任何的实例也都可以调用,因此静态方法中不能用this和super关键字,不能直接访问所属类的实例变量和实例方法(就是不带static的成员变量和成员成员方法),只能访问所属类的静态成员变量和成员方法。因为实例成员与特定的对象关联!这个需要去理解,想明白其中的道理,不是记忆!!!
因为static方法独立于任何实例,因此static方法必须被实现,而不能是抽象的abstract。
3、static代码块
static代码块也叫静态代码块,是在类中独立于类成员的static语句块,可以有多个,位置可以随便放,它不在任何的方法体内,JVM加载类时会执行这些静态的代码块,如果static代码块有多个,JVM将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。例如:
publicclassTest5{
privatestaticinta;
privateintb;
static{
Test5.a=3;
System.out.println(a);
Test5t=newTest5();
t.f();
t.b=1000;
System.out.println(t.b);
}
static{
Test5.a=4;
System.out.println(a);
}
publicstaticvoidmain(String[]args){
//TODO自动生成方法存根
}
static{
Test5.a=5;
System.out.println(a);
}
publicvoidf(){
System.out.println("hhahhahah");
}
}
运行结果:
3
hhahhahah
1000
4
5
利用静态代码块可以对一些static变量进行赋值,最后再看一眼这些例子,都一个static的main方法,这样JVM在运行main方法的时候可以直接调用而不用创建实例。
4、static和final一块用表示什么
static final用来修饰成员变量和成员方法,可简单理解为“全局常量”!
对于变量,表示一旦给值就不可修改,并且通过类名可以访问。
对于方法,表示不可覆盖,并且可以通过类名直接访问。
特别要注意一个问题:
对于被static和final修饰过的实例常量,实例本身不能再改变了,但对于一些容器类型(比如,ArrayList、HashMap)的实例变量,不可以改变容器变量本身,但可以修改容器中存放的对象,这一点在编程中用到很多。
也许说了这么多,反倒把你搞晕了,还是看个例子吧:
publicclassTestStaticFinal{
="aaa";
=null;
privatefinalStringstrFinalVar=null;
=0;
=newInteger(8);
privatestaticfinalArrayList<String>alStaticFinalVar=newArrayList<String>();
privatevoidtest(){
System.out.println("-------------值处理前---------- ");
System.out.println("strStaticFinalVar="+strStaticFinalVar+" ");
System.out.println("strStaticVar="+strStaticVar+" ");
System.out.println("strFinalVar="+strFinalVar+" ");
System.out.println("intStaticFinalVar="+intStaticFinalVar+" ");
System.out.println("integerStaticFinalVar="+integerStaticFinalVar+" ");
System.out.println("alStaticFinalVar="+alStaticFinalVar+" ");
//strStaticFinalVar="哈哈哈哈";//错误,final表示终态,不可以改变变量本身.
strStaticVar="哈哈哈哈";//正确,static表示类变量,值可以改变.
//strFinalVar="呵呵呵呵";//错误,final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。
//intStaticFinalVar=2;//错误,final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。
//integerStaticFinalVar=newInteger(8);//错误,final表示终态,在定义的时候就要初值(哪怕给个null),一旦给定后就不可再更改。
alStaticFinalVar.add("aaa");//正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。
alStaticFinalVar.add("bbb");//正确,容器变量本身没有变化,但存放内容发生了变化。这个规则是非常常用的,有很多用途。
System.out.println("-------------值处理后---------- ");
System.out.println("strStaticFinalVar="+strStaticFinalVar+" ");
System.out.println("strStaticVar="+strStaticVar+" ");
System.out.println("strFinalVar="+strFinalVar+" ");
System.out.println("intStaticFinalVar="+intStaticFinalVar+" ");
System.out.println("integerStaticFinalVar="+integerStaticFinalVar+" ");
System.out.println("alStaticFinalVar="+alStaticFinalVar+" ");
}
publicstaticvoidmain(Stringargs[]){
newTestStaticFinal().test();
}
}
看了上面这个例子,就清楚很多了,但必须明白:通过static final修饰的容器类型变量中所“装”的对象是可改变的。这是和一般基本类型和类类型变量差别很大的地方。
‘肆’ 并发编程 atomicboolean什么场景下适用
1.类AtomicBoolean
可以用原子方式更新的 boolean 值。有关原子变量属性的描述,请参阅 java.util.concurrent.atomic 包规范。AtomicBoolean 可用在应用程序中(如以原子方式更新的标志),但不能用于替换 Boolean。
2.构造函数
1.AtomicBoolean()
使用初始值 false 创建新的 AtomicBoolean。
2.AtomicBoolean(boolean initialValue)
使用给定的初始值创建新的 AtomicBoolean。
3.方法详解
get
public final boolean get()
返回当前值。
返回:
‘伍’ final的作用
一、final关键字的基本用法
在Java中,final关键字可以用来修饰类、方法和变量(包括成员变量和局部变量)。下面就从这三个方面来了解一下final关键字的基本用法。
1、修饰类
当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。
在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。
2/6
2、修饰方法
下面这段话摘自《Java编程思想》第四版第143页:
“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
因此,如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。即父类的final方法是不能被子类所覆盖的,也就是说子类是不能够存在和父类一模一样的方法的。
final修饰的方法表示此方法已经是“最后的、最终的”含义,亦即此方法不能被重写(可以重载多个final修饰的方法)。此处需要注意的一点是:因为重写的前提是子类可以从父类中继承此方法,如果父类中final修饰的方法同时访问控制权限为private,将会导致子类中不能直接继承到此方法,因此,此时可以在子类中定义相同的方法名和参数,此时不再产生重写与final的矛盾,而是在子类中重新定义了新的方法。(注:类的private方法会隐式地被指定为final方法。)
3/6
3、修饰变量 修饰变量是final用得最多的地方,也是本文接下来要重点阐述的内容。
final成员变量表示常量,只能被赋值一次,赋值后值不再改变。
当final修饰一个基本数据类型时,表示该基本数据类型的值一旦在初始化后便不能发生变化;如果final修饰一个引用类型时,则在对其初始化之后便不能再让其指向其他对象了,但该引用所指向的对象的内容是可以发生变化的。本质上是一回事,因为引用的值是一个地址,final要求值,即地址的值不发生变化。
final修饰一个成员变量(属性),必须要显示初始化。这里有两种初始化方式,一种是在变量声明的时候初始化;第二种方法是在声明变量的时候不赋初值,但是要在这个变量所在的类的所有的构造函数中对这个变量赋初值。
当函数的参数类型声明为final时,说明该参数是只读型的。即你可以读取使用该参数,但是无法改变该参数的值。
4/6
二、深入理解final关键字
在了解了final关键字的基本用法之后,这一节我们来看一下final关键字容易混淆的地方。
1、类的final变量和普通变量有什么区别?
当用final作用于类的成员变量时,成员变量(注意是类的成员变量,局部变量只需要保证在使用之前被初始化赋值即可)必须在定义时或者构造器中进行初始化赋值,而且final变量一旦被初始化赋值之后,就不能再被赋值了。
5/6
2、被final修饰的引用变量指向的对象内容可变吗?
引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的
6/6
3、final参数的问题
在实际应用中,我们除了可以用final修饰成员变量、成员方法、类,还可以修饰参数、若某个参数被final修饰了,则代表了该参数是不可改变的。如果在方法中我们修改了该参数,则编译器会提示你:The final local variable i cannot be assigned. It must be blank and not using a compound assignment。
java采用的是值传递,对于引用变量,传递的是引用的值,也就是说让实参和形参同时指向了同一个对象,因此让形参重新指向另一个对象对实参并没有任何影响。
‘陆’ 在方法参数中使用final 有什么用
1 常量(final)的设置是不是只是为了防止程序员在以后修改程序时,忘记这个值是不能修改的?如果假设“程序员一定记得”这个值不允许改变,那么就可以不设为final吧?
程序员工作不是一个人的工作,你设置为final,别人将来维护的时候一看就知道这个变量不能修改,而不需要去记忆这个是不能变化的值,是常量。这个是代码规范。
另外final常量一般设置为static的,一定程度上节省了内存开销。
2 有些方法的参数中会声明参数为final,这样用有什么用? 例如 public A(final Map map);
参数声明为final就是标明这个对象的内存地址不允许修改。即就是如果你在方法里面修改为
map=newHashMap();是不允许的。但是map里面的值可以修改。
举一个例子
public class Test {
public static void main(String[] args) {
Map map = new HashMap();
process(map);
System.out.println(map.keySet().iterator().next());//结果输出aaa
}
private static void process(final Map map) {
map.put("aaaa", "1111");//尽管map为final,但是只是地址不变
//map里面的内容可以改变
//map = new HashMap();//当从新为final变量map分配内存地址,
//引起编译错误,因为final修饰的map内存地址不可以改变。
}
}
‘柒’ 并发编程解惑之线程
主要内容:
进程是资源分配的最小单位,每个进程都有独立的代码和数据空间,一个进程包含 1 到 n 个线程。线程是 CPU 调度的最小单位,每个线程有独立的运行栈和程序计数器,线程切换开销小。
Java 程序总是从主类的 main 方法开始执行,main 方法就是 Java 程序默认的主线程,而在 main 方法中再创建的线程就是其他线程。在 Java 中,每次程序启动至少启动 2 个线程。一个是 main 线程,一个是垃圾收集线程。每次使用 Java 命令启动一个 Java 程序,就相当于启动一个 JVM 实例,而每个 JVM 实例就是在操作系统中启动的一个进程。
多线程可以通过继承或实现接口的方式创建。
Thread 类是 JDK 中定义的用于控制线程对象的类,该类中封装了线程执行体 run() 方法。需要强调的一点是,线程执行先后与创建顺序无关。
通过 Runnable 方式创建线程相比通过继承 Thread 类创建线程的优势是避免了单继承的局限性。若一个 boy 类继承了 person 类,boy 类就无法通过继承 Thread 类的方式来实现多线程。
使用 Runnable 接口创建线程的过程:先是创建对象实例 MyRunnable,然后将对象 My Runnable 作为 Thread 构造方法的入参,来构造出线程。对于 new Thread(Runnable target) 创建的使用同一入参目标对象的线程,可以共享该入参目标对象 MyRunnable 的成员变量和方法,但 run() 方法中的局部变量相互独立,互不干扰。
上面代码是 new 了三个不同的 My Runnable 对象,如果只想使用同一个对象,可以只 new 一个 MyRunnable 对象给三个 new Thread 使用。
实现 Runnable 接口比继承 Thread 类所具有的优势:
线程有新建、可运行、阻塞、等待、定时等待、死亡 6 种状态。一个具有生命的线程,总是处于这 6 种状态之一。 每个线程可以独立于其他线程运行,也可和其他线程协同运行。线程被创建后,调用 start() 方法启动线程,该线程便从新建态进入就绪状态。
NEW 状态(新建状态) 实例化一个线程之后,并且这个线程没有开始执行,这个时候的状态就是 NEW 状态:
RUNNABLE 状态(就绪状态):
阻塞状态有 3 种:
如果一个线程调用了一个对象的 wait 方法, 那么这个线程就会处于等待状态(waiting 状态)直到另外一个线程调用这个对象的 notify 或者 notifyAll 方法后才会解除这个状态。
run() 里的代码执行完毕后,线程进入终结状态(TERMINATED 状态)。
线程状态有 6 种:新建、可运行、阻塞、等待、定时等待、死亡。
我们看下 join 方法的使用:
运行结果:
我们来看下 yield 方法的使用:
运行结果:
线程与线程之间是无法直接通信的,A 线程无法直接通知 B 线程,Java 中线程之间交换信息是通过共享的内存来实现的,控制共享资源的读写的访问,使得多个线程轮流执行对共享数据的操作,线程之间通信是通过对共享资源上锁或释放锁来实现的。线程排队轮流执行共享资源,这称为线程的同步。
Java 提供了很多同步操作(也就是线程间的通信方式),同步可使用 synchronized 关键字、Object 类的 wait/notifyAll 方法、ReentrantLock 锁、无锁同步 CAS 等方式来实现。
ReentrantLock 是 JDK 内置的一个锁对象,用于线程同步(线程通信),需要用户手动释放锁。
运行结果:
这表明同一时间段只能有 1 个线程执行 work 方法,因为 work 方法里的代码需要获取到锁才能执行,这就实现了多个线程间的通信,线程 0 获取锁,先执行,线程 1 等待,线程 0 释放锁,线程 1 继续执行。
synchronized 是一种语法级别的同步方式,称为内置锁。该锁会在代码执行完毕后由 JVM 释放。
输出结果跟 ReentrantLock 一样。
Java 中的 Object 类默认是所有类的父类,该类拥有 wait、 notify、notifyAll 方法,其他对象会自动继承 Object 类,可调用 Object 类的这些方法实现线程间的通信。
除了可以通过锁的方式来实现通信,还可通过无锁的方式来实现,无锁同 CAS(Compare-and-Swap,比较和交换)的实现,需要有 3 个操作数:内存地址 V,旧的预期值 A,即将要更新的目标值 B,当且仅当内存地址 V 的值与预期值 A 相等时,将内存地址 V 的值修改为目标值 B,否则就什么都不做。
我们通过计算器的案例来演示无锁同步 CAS 的实现方式,非线程安全的计数方式如下:
线程安全的计数方式如下:
运行结果:
线程安全累加的结果才是正确的,非线程安全会出现少计算值的情况。JDK 1.5 开始,并发包里提供了原子操作的类,AtomicBoolean 用原子方式更新的 boolean 值,AtomicInteger 用原子方式更新 int 值,AtomicLong 用原子方式更新 long 值。 AtomicInteger 和 AtomicLong 还提供了用原子方式将当前值自增 1 或自减 1 的方法,在多线程程序中,诸如 ++i 或 i++ 等运算不具有原子性,是不安全的线程操作之一。 通常我们使用 synchronized 将该操作变成一个原子操作,但 JVM 为此种操作提供了原子操作的同步类 Atomic,使用 AtomicInteger 做自增运算的性能是 ReentantLock 的好几倍。
上面我们都是使用底层的方式实现线程间的通信的,但在实际的开发中,我们应该尽量远离底层结构,使用封装好的 API,例如 J.U.C 包(java.util.concurrent,又称并发包)下的工具类 CountDownLath、CyclicBarrier、Semaphore,来实现线程通信,协调线程执行。
CountDownLatch 能够实现线程之间的等待,CountDownLatch 用于某一个线程等待若干个其他线程执行完任务之后,它才开始执行。
CountDownLatch 类只提供了一个构造器:
CountDownLatch 类中常用的 3 个方法:
运行结果:
CyclicBarrier 字面意思循环栅栏,通过它可以让一组线程等待至某个状态之后再全部同时执行。当所有等待线程都被释放以后,CyclicBarrier 可以被重复使用,所以有循环之意。
相比 CountDownLatch,CyclicBarrier 可以被循环使用,而且如果遇到线程中断等情况时,可以利用 reset() 方法,重置计数器,CyclicBarrier 会比 CountDownLatch 更加灵活。
CyclicBarrier 提供 2 个构造器:
上面的方法中,参数 parties 指让多少个线程或者任务等待至 barrier 状态;参数 barrierAction 为当这些线程都达到 barrier 状态时会执行的内容。
CyclicBarrier 中最重要的方法 await 方法,它有 2 个重载版本。下面方法用来挂起当前线程,直至所有线程都到达 barrier 状态再同时执行后续任务。
而下面的方法则是让这些线程等待至一定的时间,如果还有线程没有到达 barrier 状态就直接让到达 barrier 的线程执行任务。
运行结果:
CyclicBarrier 用于一组线程互相等待至某个状态,然后这一组线程再同时执行,CountDownLatch 是不能重用的,而 CyclicBarrier 可以重用。
Semaphore 类是一个计数信号量,它可以设定一个阈值,多个线程竞争获取许可信号,执行完任务后归还,超过阈值后,线程申请许可信号时将会被阻塞。Semaphore 可以用来 构建对象池,资源池,比如数据库连接池。
假如在服务器上运行着若干个客户端请求的线程。这些线程需要连接到同一数据库,但任一时刻只能获得一定数目的数据库连接。要怎样才能够有效地将这些固定数目的数据库连接分配给大量的线程呢?
给方法加同步锁,保证同一时刻只能有一个线程去调用此方法,其他所有线程排队等待,但若有 10 个数据库连接,也只有一个能被使用,效率太低。另外一种方法,使用信号量,让信号量许可与数据库可用连接数为相同数量,10 个数据库连接都能被使用,大大提高性能。
上面三个工具类是 J.U.C 包的核心类,J.U.C 包的全景图就比较复杂了:
J.U.C 包(java.util.concurrent)中的高层类(Lock、同步器、阻塞队列、Executor、并发容器)依赖基础类(AQS、非阻塞数据结构、原子变量类),而基础类是通过 CAS 和 volatile 来实现的。我们尽量使用顶层的类,避免使用基础类 CAS 和 volatile 来协调线程的执行。J.U.C 包其他的内容,在其他的篇章会有相应的讲解。
Future 是一种异步执行的设计模式,类似 ajax 异步请求,不需要同步等待返回结果,可继续执行代码。使 Runnable(无返回值不支持上报异常)或 Callable(有返回值支持上报异常)均可开启线程执行任务。但是如果需要异步获取线程的返回结果,就需要通过 Future 来实现了。
Future 是位于 java.util.concurrent 包下的一个接口,Future 接口封装了取消任务,获取任务结果的方法。
在 Java 中,一般是通过继承 Thread 类或者实现 Runnable 接口来创建多线程, Runnable 接口不能返回结果,JDK 1.5 之后,Java 提供了 Callable 接口来封装子任务,Callable 接口可以获取返回结果。我们使用线程池提交 Callable 接口任务,将返回 Future 接口添加进 ArrayList 数组,最后遍历 FutureList,实现异步获取返回值。
运行结果:
上面就是异步线程执行的调用过程,实际开发中用得更多的是使用现成的异步框架来实现异步编程,如 RxJava,有兴趣的可以继续去了解,通常异步框架都是结合远程 HTTP 调用 Retrofit 框架来使用的,两者结合起来用,可以避免调用远程接口时,花费过多的时间在等待接口返回上。
线程封闭是通过本地线程 ThreadLocal 来实现的,ThreadLocal 是线程局部变量(local vari able),它为每个线程都提供一个变量值的副本,每个线程对该变量副本的修改相互不影响。
在 JVM 虚拟机中,堆内存用于存储共享的数据(实例对象),也就是主内存。Thread Local .set()、ThreadLocal.get() 方法直接在本地内存(工作内存)中写和读共享变量的副本,而不需要同步数据,不用像 synchronized 那样保证数据可见性,修改主内存数据后还要同步更新到工作内存。
Myabatis、hibernate 是通过 threadlocal 来存储 session 的,每一个线程都维护着一个 session,对线程独享的资源操作很方便,也避免了线程阻塞。
ThreadLocal 类位于 Thread 线程类内部,我们分析下它的源码:
ThreadLocal 和 Synchonized 都用于解决多线程并发访问的问题,访问多线程共享的资源时,Synchronized 同步机制采用了以时间换空间的方式,提供一份变量让多个线程排队访问,而 ThreadLocal 采用了以空间换时间的方式,提供每个线程一个变量,实现数据隔离。
ThreadLocal 可用于数据库连接 Connection 对象的隔离,使得每个请求线程都可以复用连接而又相互不影响。
在 Java 里面,存在强引用、弱引用、软引用、虚引用。我们主要来了解下强引用和弱引用:
上面 a、b 对实例 A、B 都是强引用
而上面这种情况就不一样了,即使 b 被置为 null,但是 c 仍然持有对 C 对象实例的引用,而间接的保持着对 b 的强引用,所以 GC 不会回收分配给 b 的空间,导致 b 无法回收也没有被使用,造成了内存泄漏。这时可以通过 c = null; 来使得 c 被回收,但也可以通过弱引用来达到同样目的:
从源码中可以看出 Entry 里的 key 对 ThreadLocal 实例是弱引用:
Entry 里的 key 对 ThreadLocal 实例是弱引用,将 key 值置为 null,堆中的 ThreadLocal 实例是可以被垃圾收集器(GC)回收的。但是 value 却存在一条从 Current Thread 过来的强引用链,只有当当前线程 Current Thread 销毁时,value 才能被回收。在 threadLocal 被设为 null 以及线程结束之前,Entry 的键值对都不会被回收,出现内存泄漏。为了避免泄漏,在 ThreadLocalMap 中的 set/get Entry 方法里,会对 key 为 null 的情况进行判断,如果为 null 的话,就会对 value 置为 null。也可以通过 ThreadLocal 的 remove 方法(类似加锁和解锁,最后 remove 一下,解锁对象的引用)直接清除,释放内存空间。
总结来说,利用 ThreadLocal 来访问共享数据时,JVM 通过设置 ThreadLocalMap 的 Key 为弱引用,来避免内存泄露,同时通过调用 remove、get、set 方法的时候,回收弱引用(Key 为 null 的 Entry)。当使用 static ThreadLocal 的时候(如上面的 Spring 多数据源),static 变量在类未加载的时候,它就已经加载,当线程结束的时候,static 变量不一定会被回收,比起普通成员变量使用的时候才加载,static 的生命周期变长了,若没有及时回收,容易产生内存泄漏。
使用线程池,可以重用存在的线程,减少对象创建、消亡的开销,可控制最大并发线程数,避免资源竞争过度,还能实现线程定时执行、单线程执行、固定线程数执行等功能。
Java 把线程的调用封装成了一个 Executor 接口,Executor 接口中定义了一个 execute 方法,用来提交线程的执行。Executor 接口的子接口是 ExecutorService,负责管理线程的执行。通过 Executors 类的静态方法可以初始化
ExecutorService 线程池。Executors 类的静态方法可创建不同类型的线程池:
但是,不建议使用 Executors 去创建线程池,而是通过 ThreadPoolExecutor 的方式,明确给出线程池的参数去创建,规避资源耗尽的风险。
如果使用 Executors 去创建线程池:
最佳的实践是通过 ThreadPoolExecutor 手动地去创建线程池,选取合适的队列存储任务,并指定线程池线程大小。通过线程池实现类 ThreadPoolExecutor 可构造出线程池的,构造函数有下面几个重要的参数:
参数 1:corePoolSize
线程池核心线程数。
参数 2:workQueue
阻塞队列,用于保存执行任务的线程,有 4 种阻塞队列可选:
参数 3:maximunPoolSize
线程池最大线程数。如果阻塞队列满了(有界的阻塞队列),来了一个新的任务,若线程池当前线程数小于最大线程数,则创建新的线程执行任务,否则交给饱和策略处理。如果是无界队列就不存在这种情况,任务都在无界队列里存储着。
参数 4:RejectedExecutionHandler
拒绝策略,当队列满了,而且线程达到了最大线程数后,对新任务采取的处理策略。
有 4 种策略可选:
最后,还可以自定义处理策略。
参数 5:ThreadFactory
创建线程的工厂。
参数 6:keeyAliveTime
线程没有任务执行时最多保持多久时间终止。当线程池中的线程数大于 corePoolSize 时,线程池中所有线程中的某一个线程的空闲时间若达到 keepAliveTime,则会终止,直到线程池中的线程数不超过 corePoolSize。但如果调用了 allowCoreThread TimeOut(boolean value) 方法,线程池中的线程数就算不超过 corePoolSize,keepAlive Time 参数也会起作用,直到线程池中的线程数量变为 0。
参数 7:TimeUnit
配合第 6 个参数使用,表示存活时间的时间单位最佳的实践是通过 ThreadPoolExecutor 手动地去创建线程池,选取合适的队列存储任务,并指定线程池线程大小。
运行结果:
线程池创建线程时,会将线程封装成工作线程 Worker,Worker 在执行完任务后,还会不断的去获取队列里的任务来执行。Worker 的加锁解锁机制是继承 AQS 实现的。
我们来看下 Worker 线程的运行过程:
总结来说,如果当前运行的线程数小于 corePoolSize 线程数,则获取全局锁,然后创建新的线程来执行任务如果运行的线程数大于等于 corePoolSize 线程数,则将任务加入阻塞队列 BlockingQueue 如果阻塞队列已满,无法将任务加入 BlockingQueue,则获取全局所,再创建新的线程来执行任务
如果新创建线程后使得线程数超过了 maximumPoolSize 线程数,则调用 Rejected ExecutionHandler.rejectedExecution() 方法根据对应的拒绝策略处理任务。
CPU 密集型任务,线程执行任务占用 CPU 时间会比较长,应该配置相对少的线程数,避免过度争抢资源,可配置 N 个 CPU+1 个线程的线程池;但 IO 密集型任务则由于需要等待 IO 操作,线程经常处于等待状态,应该配置相对多的线程如 2*N 个 CPU 个线程,A 线程阻塞后,B 线程能马上执行,线程多竞争激烈,能饱和的执行任务。线程提交 SQL 后等待数据库返回结果时间较长的情况,CPU 空闲会较多,线程数应设置大些,让更多线程争取 CPU 的调度。
‘捌’ android 编程中 final是什么意思
final 如果用在类中就是最终类,不能被继承;若用在变量中,就相当于是一个常量;用在函数中,这个方法不能被重写;就是这个意思
‘玖’ java语言中 final和static 的 区别是什么
final 被修饰的变量为常量一旦赋值不能修改,被修改的方法为最终方法不能
被重写,被修饰的类是最终类,不能被继承
static 被修饰的变量和方法,为该整个类及其类的对象所共享,一个类或对
象修改了被定义的类或方法,其他的也会跟着变量。。这就是说被static修饰的
是大家共用的。。
就一块肥皂多个人用,每个人用都会少而不是像不用static修饰的是每个人有
每个人的肥皂,你用自己的和别人没有关系。。
祝你好运!
如果还是不很明白,到我空间转转也许会更明白。。。