java编程思想
初读java编程思想,由于我之前都是通过网络视频的方式来学习Java知识,所以此书初读起来有些晦涩难懂,但是我还是尽力去看,然后记下我初看时觉得对我有用的笔记和一些自己的理解。
第一章 对象导论
1.多态
在处理类型的层次结构时,把一个对象不当做它所属的特定类型来对待,而是将其当做其基类(父类)的对象来对待。这使得人们可以编写出不依赖于特定类型的代码。
2.单继承
单继承结构保证所有对象都具备某些功能,可以在每个对象上执行某些基本操作。
3.参数化类型
一个编译器可以自动定制作用于特定类型上的类,用于向下转型,如Circle是一种Shape类型,但是不知道某个Object是Circle还是Shape,此时就需要参数化类型,其实就是泛型。
4.对象的创建和生命周期
当处理完某个对象之后,系统某个其他部分可能还在处理它。java提供了被称为“垃圾回收器”的机制,它可以自动发现对象何时不再被使用,并继而销毁它。可以避免暗藏的内存泄露问题。
5.并发编程
并发:指应用能够交替执行不同的任务,例:吃完饭->喝水 喝完水->睡觉 一觉醒来->吃饭......
并行:指应用能够同时执行不同的任务,例:吃饭的时候可以边吃饭边打电话,这两件事情可以同时执行。
并行存在锁的概念,如两个相同线程并行访问到同一资源,该资源只能被一个线程使用,该资源会被锁定,被一个线程占用时,该任务锁定该项资源,完成任务,然后释放资源锁,使其它任务可以使用这项资源。
第二章 一切都是对象
1.对象存储到什么地方
(1)寄存器
(2)堆栈(RAM区):堆栈指针向下移动,分配新的内存,向上移动,则释放内存。
(3)堆(RAM区):一种通用内存池。
堆不同于堆栈的好处是:编译器不需要知道存储的数据在堆里存活多长时间。
坏处:用堆进行存储分配和清理可能比用堆栈进行存储分配需要更多的时间。
(4)常量存储
直接存放在代码内部。
(5)非RAM存储
数据存活在程序之外,如磁盘。
2.高精度数字
Biginteger:支持任意精度的整数。
bigdecimal:支持任何精度的定点数。
3.static
修饰代码块:先于构造器加载,并且只会加载一次,方法内没有this,全局方法,仅通过类本身来调用static方法。
第三章 操作符
第四章 控制执行流程
第三、四两章我觉得很基础,所以没有笔记。
第五章 初始化与清理
1.构造器:这是一个在创建对象时被自动调用的特殊方法,确保初始化,java会在用户有能力操作对象之前自动调用相应的构造器,由编译器调用,与类名相同,无返回值。
(1)不接受任何参数的构造器叫做默认构造器。
(2)如果你没有定义构造器,编译器自动帮你创建默认构造器,但是如果你定义了构造器,编译器就不会再去创建默认构造器。
例子:
class Bird2{
Bird2(int f){}
Bird(double d){}
}
public class NoSynthesis{
public static void main(String[] args){
Bird2 b=new Bird2();//报错,没有默认构造器
Bird2 b2=new Bird2(1);
Bird2 b3=new Bird2(1.8);
}
}
(3)除构造器之外,编译器禁止在其他任何方法中调用构造器。
2.this:指出当前对象的引用 如 A a=new A();指出的就是引用a。
3.垃圾回收器:垃圾回收器只会释放那些经由new分配的内存。
4.finalize()方法:并非使用new获得的内存区域时,使用该方法释放内存,该区域主要指在java代码中调用了非java代码。也许会调用c的malloc()函数来分配空间,要释放空间得调用free(),但该方法是c、c++中才有的,所以在java中要使用finalize()。当垃圾回收器准备好释放对象占用的存储空间,将首先调用finalize()方法,并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。在做清理工作时,我们可以在finalize()里加入某种擦除功能,当垃圾回收发生时,finalize()得到调用,数据就会被擦除,要是垃圾回收没有发生,数据就会保留。
(1)垃圾回收只与内存有关,也就是说使用垃圾回收器的唯一原因是为了回收程序不在使用的内存。无论是垃圾回收,还是终结,都不保证一定会发生。如果jvm并未面临内存耗尽的情形,它是不会浪费时间去执行垃圾回收以恢复内存的。
5.垃圾回收器如何工作:
(1)停止-复制
先暂停程序的运行,然后将所有存活的对象从当前堆复制到另一堆,没有被复制的全是垃圾,当对象被复制到新堆时,它们是一个挨着一个的,所以新堆保持紧凑排列,然后就可以按像传送带一样的方法,每分配一个新对象,就往前移动一格,这样简单、直接地分配新空间了。当把对象从一处搬到另一处时,所有指向它的引用都必须修正。
缺点:效率会降低。原因是,首先,得有两个堆,然后在这两个分离的堆之间来回捣腾,从而得维护比实际需要多一倍的空间。第二个问题,是复制,如果程序只有少量垃圾,或者没垃圾,垃圾回收器仍然会将所有内存自一处复制到另一处,这很浪费。
(2)标记-清扫
虚拟机进行检查,要是没有新垃圾产生,就会转向这一方式,该方式速度相当慢,但是当你知道只会产生少量垃圾甚至不会产生垃圾时,它的速度就很快了。
该方式所依据的思路同样是从堆栈和静态存储区出发,遍历所有的引用,进而找出所有存活的对象,每当它找到一个存活对象,就会给对象设一个标记,这个过程中不会回收任何对象。只有全部标记工作完成的时候,清理才会开始。在清理过程中,没有标记的对象将会被释放,不会发生任何复制动作,所以剩下的堆空间是不连续的,垃圾回收器要是希望得到连续空间的话,就得重新整理剩下的对象。
内存会分配较大的“块”,停止-复制要把所有存活对象复制到新堆,会造成大量内存复制行为,有了块之后垃圾回收器,就可以往废弃的块里拷贝对象了,这种好处就是先将空间分配好,执行时,就不需要现去分配空间,减少时间,典型的空间换时间。
java实际垃圾清理是“自适应技术”:java虚拟机会进行监视,如果对象都很稳定,垃圾回收器的效率降低的话,就切换到“标记-清扫”方式,同样,java虚拟机会跟踪“标记-清扫”的效果,要是堆空间出现很多碎片,就会切换回“停止-复制”方式。
6.自动初始化:如果没给基本类型的字段赋值,编译器会给其赋一个默认值,自动初始化会在构造器被调用之前执行。
第六章 访问权限控制
1.重构:即重写代码,以使得它更可读、更易理解、并因此而更具可维护性。
第七章 复用类
1.带参数的构造器:
class Game{
Game(int i){
print("Game constructor");
}
}
class BoarGame extends Game{
BoarGame(int i){
super(i)
print("BoarGame constructor");
}
}
在继承中构造器的执行顺序:先父类构造器,然后子类构造器,上面代码要说明父类写的是带参数的构造器,所以没有默认构造器,子类必须在自身构造器中调用父类的带参构造器,因为子类会调用父类的默认构造器,但是此时父类没有默认构造器,所以不调用父类的带参构造器的话,会报错。
2.名称屏蔽:
如果java的基类拥有某个已被多次重载的方法名称,那么在子类中重新定义该方法名称不会屏蔽其在基类中的任何版本。就是说父类中有一个方法的多个重载,子类也重载了该方法,它不会屏蔽掉父类的多个重载。@Override指重写,如果你不想重载,加这个就不允许重载,只能重写。
3.组合与继承:
组合和继承都允许在新的类中放置子对象,组合是显示的这样做,而继承是隐式的做。
4.protected:
就类用户而言,这是private的,但对于任何继承与此类的子类或其他任何位于同一个包内的类来说,它是可以访问的。
5.向上转型:
举个例子:乐器A 、钢琴B 、B继承A ,B拥有A的所有方法,那么就可以说B是A类型的,再通俗就是父类引用指向子类对象。即 A a=new B() , a引用指向了一个B对象,表示B对象是A类型的。
延伸:向下转型
A a=new A();
B b=(B)a ; 表示A对象是B类型
6.final:
(1)一个永不改变的编译时常量。
(2)一个在运行时被初始化的值,而你不需要它被改变。
final修饰方法
把方法锁定,防止任何继承类修改它的含义,即确保在继承中父类的方法不会被子类覆盖。
final修饰类
你不允许被修饰的类有子类,即不能被继承。
第八章 多态
1.后期绑定:即多态实现机制,编译器一直不知道对象的类型,但是方法调用机制能找到正确的方法体,并加以调用。多态只能发生在方法调用时。当调用对象某个方法时,JVM查找该对象类的方法表以确定该方法的直接引用地址,有了地址后才真正调用该方法。
2.子类继承父类时,如果父类的一个方法私有,子类重写这个方法是不成功的,即使方法名一样也不能覆盖父类的这个方法,只能被当做一个新的方法去看待。
3.只有普通的方法调用是多态的,如果直接访问某个域,这个域就将在编译期进行解析。如子类和父类中有两个相同名字的基本类型 如int a,子类不会覆盖父类的int a,而是分配了不同的存储空间,向上转型时,调用的就是父类的方法。
如 :
class Super{
public int field=0;
public int getField(){return field};
}
class Sub extends Supoer{
public int field=1;
public int getField(){return field};
public int getSuperField(){return super.field};
}
//Super sup=new Sub();
//sup.field=0,sup.getField()=1
//Sub sub=new Sub();
//sub.field=1,sub.getField()=1,sub.getSuperField=0;
为什么会这种结果:第一个是向上转型,调用的是Super这个类型的方法,所以field为0,sup.getField()=1不是因为调用的是子类的方法,还是调用的Super里的,但是它返回的field却是子类的,由于是有两个不同的field,不同点是field、super.field,所以要返回1的话,就要用super.field。
4.构造器与多态:构造器相当于static方法,只不过该static声明是隐式的,子类调用构造器前,要先调用父类的构造器。
5.继承与清理:一般清理由垃圾回收器自己去执行,但在某些必要条件下,要自己写清理方法,此时子类继承父类,父类中写了清理方法,但是子类存在特殊处理,所以重写父类清理方法,覆盖了父类清理,此时要注意,父类就不会被清理,由于是在子类中进行,父类的清理方法被覆盖掉了,所以在子类中没有执行父类的清理方法,所以一定要加上super的父类清理方法,否则父类的清理不会发生。
6.构造器内部的多态方法的行为:在继承中,最好将父类直接放到子类中看,这样直观。
class Glyph{
void draw() {
print("G.draw()");
}
Glyph(){
print("Glyph() before draw()");
draw();
print("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph{
private int radius=1;
RoundGlyph(int r){
radius=r;
print("RoundGlyph.RoundGlyph(),radius="+radius);
}
void draw(){
print("RoundGlyph.draw,radius="+radius);
}
}
public class PolyConstructors{
public static void main(String[] args){
new RoundGlyph(5);
}
}
//输出 Glyph() before draw()
//RoundGlyph.RoundGlyph(),radius=0
//Glyph() after draw()
//RoundGlyph.RoundGlyph(),radius=5
这种输出结果的理解,先是调用父类构造方法,走到draw()时,要把父类放到在子类中结合看,可以看到draw()被重写了,所以调用的是子类的draw(),radius为0是由于在一切事物发生之前,将分配给对象的存储空间初始化二进制的0,即在调用父类构造前,先分配存储空间,初始化的值为0,执行完父类构造后,再给radius赋值为5。
第九章 接口
1.抽象类和抽象方法:包含抽象方法的类叫做抽象类,如果一个类包含一个或多个抽象方法,该类必须被限定为抽象的,否则编译器会报错。抽象类主要用来被继承,不能被实例化的类。
2.使用接口的核心原因:
(1)为了能够向上转型为多个基类型。
(2)防止客户端程序员创建该类的对象,并确保这仅仅是建立一个接口。
接口与抽象类之间的选择: 如果要创建不带任何方法定义和成员变量的基类,那么就应该选择接口,而非抽象类。
3.通过继承来扩展接口:一个类只能继承一个基类,但能实现多个接口,接口可以继承接口,并且可以多继承。
4.接口中的域:放入接口中的任何域都是自动static和final的。
第十章 内部类
1.链接到外部类:内部类自动拥有对其外围类所有成员的访问权,privte修饰的也可访问。
理解:就是内部类能直接访问外部类的信息,但外围类不能直接访问内部类里的信息。
2.使用.this与.new:创建一个内部类对象
DotNew dn=new DotNew();
DotNew.Inner dn1=dn.new Inner();
3.内部类与向上转型:一个类里面,含有一个被private修饰的内部类,外部类不能访问这个内部类,除了内部类自己。
4.在方法和作用域内的内部类:在一个作用域内的内部类,在作用域外,他是不可用的,除此之外,他与普通的类没有区别,作用域就是指{ }。
5.匿名内部类:
写法:
return new A(){};
在匿名内部类,他不可能有命名构造器,因为它根本没有名字。如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的。
6.嵌套类:如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static,这通常称为嵌套类。普通的内部类对象隐式的保存了一个引用,指向创建它的外围对象。
嵌套类,意味着:
(1)要创建嵌套类的对象,并不需要其外围类的对象
(2)不能从嵌套类的对象中访问非静态的外围类对象
如2中创建一个内部类对象,只需要
Inner dn1=new Inner();
7.接口内部的类:接口里可以放一个接口的实现类,该类实际就是嵌套类,因为接口自动public和static。
8.为什么需要内部类:完成间接的多继承实现,即一个类只能继承一个父类,这时这个类只有两个可用类型,即本身和父类类型,但是如果还想有另外几个类型的话,就可以在子类中写一个方法,方法里写一个类。
如
class Z extends D{
E makeE() {
return new E(){};
}
}
这时如果
Z z=new Z();
z.makeE();
它z返回的类型就是E了。
9.闭包与回调:闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。内部类是面向对象的闭包,它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向外围类对象的引用,在此作用域内,内部类有权限操作所有的成员,包括private成员。
10.内部类的继承:
class withinner{
class Inner{
}
}
public class InheritInner extends WithInner.Inner{
InheritInner(WithInner wi){
wi.super;
}
}
这种继承内部类的类,它的构造器,必须传入一个内部类的外围类的对象的引用,然后必须在构造器中使用*.super这个语法。估计是规定,没有为啥。
第十一章 持有对象
1.泛型和类型安全的容器
在<>可以放多个类型,不止一个。
编译期错误:指将java文件转为二进制的class失败。
运行时错误:转为二进制是成功的,在运行时出错。
泛型里面也可以添加它的子类
2.List
List:线性顺序存储元素、元素可重复、可以存放null、可查找
LinkedList:基于链表,中间插入删除快,按位置查找慢
ArrayList:基于数组,中间插入删除慢、按位置查找快,原因是数组固定大小,插入时,要重新建一个数组
3.迭代器
Iterator: Iterator是一个迭代器接口,专门用来迭代各种Collection集合,包括Set集合和List集合。可以用来遍历不知道具体数目的list和set。
(1)next()获得序列中的下一个元素
(2)hashNext()检查序列中是否还有元素
(3)remove()将迭代器新近返回的元素删除
public static void main(String[] args)
{
List<String> list=new ArrayList<>();
list.add("abc");
list.add("edf");
list.add("ghi");
for(Iterator<String> it=list.iterator();it.hasNext();)
{
System.out.println(it.next());
}
}
第十二章 通过异常处理错误
1.异常说明
Java提供相应的语法(强制使用这个语法),使你能以礼貌的方式告知客户端程序员某个方法可能会抛出的异常类型。这种做法的好处是,为异常先占个位子,以后就可以抛出这种异常而不用修改已有的代码。
2.使用finally进行清理
无论异常是否被抛出,finally子句总能被执行。
3.finally用来做什么?
析构函数是“当对象不再使用”会被调用的函数,java没有析构函数可供调用。java有垃圾回收机制,所以内存释放不是问题。当要把内存之外的资源恢复到它们的初始状态时,就要用到finally。
第十四章 类型信息
1.类字面常量
就是static final先于static代码块。
2.泛化的class引用
newinstance()也是用来创建新的对象,其与new()的区别是:
newInstance()是弱类型,效率低,只能调用无参构造;new()是强类型,高效率,能调用任何public构造器
FancyToy fancytoy=ftClass.newInstance();
Class<? super FancyToy> up = ftClass.getSuperclass();
//下面这个会报错
Class<Toy> up2 = ftClass.getSuperclass();
//Toy在编译期就知道它是什么类型了,即Toy.class。getSuperclass()不仅仅只是“某个类,它是FancyToy”
的超类。
3.新的转型语法
class building{}
class House extends Building{}
public class ClassCasts{
public static void main(String[] args){
Building b=new House();
Class<House> houseType = House.class;
House h=houseType.cast(b);//相当于h=(House)b;
}
}
4.类型转换前先做检查
它的作用是用来判断,instanceof 左边对象是否为instanceof 右边类的实例。如x instanceof Dog 判断对象x是否属于Dog类。instanceof只可将其与命名类型进行比较,而不能与Class对象作比较。也可判断子类对象是否属于父类。
5.instanceof与Class的等价性
instanceof保持了类的概念,它指的是“你是这个类吗,或者你是这个类的派生类吗?”而如果用==比较实际的Class对象,就没有考虑继承——它或者是这个确切的类型,或者不是。
6.类方法提取器
getFields、getConstructors、getMethods获取的分别是一个class里面的public 变量、构造器、方法public Method[] getMethods()返回某个类的所有公用(public)方法包括其继承类的公用方法,当然也包括它所实现接口的方法。(包括继承的public)public Method[] getDeclaredMethods()对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。当然也包括它所实现接口的方法。(不包括继承的所有)
第十五章 泛型
1.泛型:
参数化类型。这个术语的意思是适用许多许多类型和接口的区别就是,不用一定要接口定义的方法。
2.理解了程序边界所在,你才能成为程序高手,因为只有知道了某个技术不能做到什么,你才能更好地做到所能做的(部分原因是,不必浪费时间在死胡同里乱转)。
3.泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。T就是类型参数。
4.泛型中能存入指定类型或者指定类型的子类,泛型与多态不冲突。
5.元组:
它是将一组对象直接打包存储于其中的一个单一对象,这个对象允许读取其中元素,但是不允许向其中存放新的对象(这个概念也称为数据传送对象或信使)。
6.final,只要存在一次等于,就不准出现第二个等于,相同都不行。在对象被构造出来之后,声明为final的元素便不能被在赋予其他值
下面这个程序,你可以使用first和second,但你却无法将其他值赋给它们
public class TwoTuple<A,B> {
public final A first;
public final B second;
public TwoTuple(A a,B b){
first=a;
second=b;
}
}
7.泛型方法:
public <T> void f(T x){ }
注意:当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型。这通常称为类型参数推断。
第十九章 枚举
1.enum:定义常量,相当于public static final。
2.除了不能继承一个enum之外,我们基本上可以将enum看做一个常规的类。如果打算定义自己的方法,那么必须在enum实例序列的最后添加一个分号,同时必须先定义enum实例,即方法写在后面。感觉也相当于通配符的意义,便于代码清晰与维护。
3.enum里面不能有重复,相当于set,但是不能存取。
4.驱动代码:就是enum中的实例对象还可以写方法,如enum a{WINKEN{syso}}}。
5.用于多路分发:就是如Number.plus(Number),或者match(new Item,new Item),Number是超类,这种不知道具体类型,多态可以动态绑定一个,但另一个就不行,这只是引出枚举的分发。
第二十一章 并发
1.并发的多面性:
用并发解决的问题大体上可以分为“速度”和“设计可管理性”两种。速度:如果想让程序运行的很快,可以将其断开为多个片段,在单独的处理器上运行每个片段。使用并发编程最吸引人的一个原因就是要产生具有可响应的用户界面。
java的线程机制是抢占式的,这表示调度机制会周期的中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个线程都会分配到数量合理的时间去驱动它的任务。
建议使用runnable接口,因为单继承,继承thread过于局限,接口还可以继承。
start()方法被用来启动新创建的线程,而且start()内部 调用了run()方法,这和直接调用run()方法的效果不一样。当你调用run()方法的时候,只会是在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程。
2.基本线程机制
一个线程就是在进程中的一个单一的顺序控制流。
yield()(让步):使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。cpu会从众多的可执行态里选择,也就是说,当前也就是刚刚的那个线程还是有可能会被再次执行到的,并不是说一定会执行其他线程而该线程在下一次中不会执行到了。
当Runnable导出一个类时,它必须具有run()方法,但是这个方法并无特殊之处,他不会产生任何内在的线程能力,要实现线程行为,你必须显示地将一个任务附着到线程上。
实现线程的方式:
(1)实现runnable接口。
(2)使用Thread类。
(3)Executor(执行器):单个的Executor被用来创建和管理系统中的所有任务。(个人感觉就是调用线程池)
3.后台线程
是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。daemon.setDaemon(true)。
4.加入一个线程
如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(即t.isAlive()返回为假)也可以在调用join()时带上一个超时参数,这样如果目标线程在这段时间到期时还没有结束的话,join()方法总能返回。 对join()方法的调用可以被中断,做法是在调用线程上调用interrupt()方法,这时需要用到try-catch子句。
5.捕获异常
会存在异常在try catch捕获不到的情况未捕获的异常通过uncaughtException来捕获。
6.解决共享资源
锁 synchronized 一个任务获得锁,外面的任务等待锁,但不是排队,而是根据抢占式,都有机会。
7.临界区
防止多个线程同时访问方法内部的部分代码而不是防止整个方法。通过这种方式分离出来的代码段被称为临界区。这也被称为同步控制块。
8.线程本地存储
防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享(第一种应该是锁,对方法的共享)。Threadlocal对象通常当做静态域存储,它保证不会出现竞争条件。
ThreadLocal 是线程的局部变量, 是每一个线程所单独持有的,其他线程不能对其进行访问, 通常是类中的 private static 字段,是对该字段初始值的一个拷贝,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
当使用ThreadLocal维护变量的时候 为每一个使用该变量的线程提供一个独立的变量副本,即每个线程内部都会有一个该变量,这样同时多个线程访问该变量并不会彼此相互影响,因此他们使用的都是自己从内存中拷贝过来的变量的副本, 这样就不存在线程安全问题,也不会影响程序的执行性能。
但是要注意,虽然ThreadLocal能够解决上面说的问题,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。
(1)ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
(2)ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
(3)ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
(4)ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
9.装饰性花园
TimeUnit.SECONDS.sleep(5)线程等待五秒,TimeUnit.MILLISECONDS.sleep(5000)线程等待五秒。两者的时间单位不一样。内部都是Thread.sleep实现
volatile,这就指示JVM,这个变量是不稳定的,每次使用它都到主存中进行读取主存即内存,其作用是用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。只要计算机在运行中,CPU就会把需要运算的数据调到内存中进行运算,当运算完成后CPU再将结果传送出来,内存的运行也决定了计算机的稳定运行。
volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。(相当于每个线程访问的是最新的变量)(这个是公用,threadlocal是各自用各自复制的副本,private。本质区别就是,threadlocal是各自用各自,这个是共用,就是用的是最新的,我加1后,你得到的是加1后的)。
10.线程状态
一个线程可以出于以下四种状态之一
(1)新建:当线程被创建时,它只会短暂的处于这种状态,此时他已经分配了必要的系统资源,并执行了初始化。此刻线程已经有资格获得cpu时间,之后调度器将把这个线程转变为可运行状态或阻塞状态。
(2)就绪(感觉然后是运行)
(3)阻塞:一个任务进入阻塞状态,可能有如下原因:
1.通过调用sleep使线程进入休眠状态。
2.调用wait(),join使线程挂起。
3.任务在等待某个输入/输出完成。
4.任务试图在某个对象上调用其同步控制方法,但是对象锁不可用(就是等待锁)。
(4)死亡
sleep与wait的区别:sleep使任务进入休眠状态,任务在指定的时间内不会运行,休眠时不释放锁。wait使线程挂起,直到线程得到了notify或notifyAll消息,线程才会进入就绪状态,挂起时释放资源锁。
为什么sleep属于Thread,wait属于Object?
其实两者都可以让线程暂停一段时间,但是本质的区别是一个线程的运行状态控制,一个是线程之间的通讯的问题。就是说sleep是线程里,wait是线程之间。
11.中断:用来打断被阻塞的任务
你能中断对sleep的调用,但是,你不能中断正在试图获取synchronized锁或者试图执行i/o操作的线程。它的两个例子,一个用的socket的close,另一个是nio通道,nio通道就可以中断i/o操作。
注意,当你在线程上调用interrupt()时,中断发生的唯一时刻是在任务要进入阻塞操作中,或者已经在阻塞操作内部时。
interrupted:检查中断状态,这不仅可以告诉你interrupt()是否被调用过,而且还可以清除中断状态。
interrupt() 向当前调用者线程发出中断信号
isinterrupted() 查看当前中断信号是true还是false
interrupted() 是静态方法,查看当前中断信号是true还是false并且清除中断信号,顾名思义interrupted为已经处理中断信号。
另外interruptedException时,他会清除掉interrupt信号。
ReentrantLock上阻塞的任务具备可以被中断的能力。
12.线程之间的协作
调用sleep或者yield的时候,锁并没有被释放。当一个任务在方法里遇到了对wait的调用的时候,线程的执行被挂起,对象上的锁被释放。
调用wait时就是在声明:我已经刚刚做完能做的所有事情,因此我要在这里等待,但是我希望其他的synchronized操作在条件适合的情况下能够执行。 其实就是说我做完了一部分工作,要等别人的工作完成,我才能继续工作,然后释放工具,希望其他需要而人能继续工作。
可以通过notify、notifyAll、或者时间到期,从wait中恢复执行。
13.
notify:唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,必须等待相同的条件,将只唤醒等待特定锁的任务。则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。notifyall:唤醒在此对象监视器上等待的所有线程。
错失的信号:就是抢占式的时候,线程先调用了notify的线程,后调用的wait线程,造成无限等待的死锁。
14.死锁
当以下四个条件同时满足时,就会发生死锁:
1.互斥条件。任务使用的资源中至少有一个是不能共享的。
2.至少有一个任务它必须持有一个资源且正在等待获取一个当前被识别的任务持有的资源。
3.资源不能被抢占,任务必须把资源释放当作普通事件。
4.必须有循环等待。这时,一个任务等待其他任务所持有的资源,后者又在等待另一个任务所持有的资源,这样一直下去,直到有一个任务在等待第一个任务所持有的资源,使得大家被锁住。
死锁必须这四个条件同时满足,所以要解决死锁,只需破坏其中一个条件,最好破坏4。