Java1.5特性:静态导入---->import static java.*;
进程:正在进行中的程序(动态)。占一定的内存空间。
在内存中,一段程序加载进来以后,分为代码段和数据段,开始执行代码段,当需要数据的时候,根据所需数据的地址找到该数据。
多线程:在一个进程中的多条不同的程序执行路径。(一个进程当中至少有一个主线程)
开启多线程是为了同时执行多个任务(每个线程都有自己运行的内容,这个内容可以称为该线程的任务)。
多线程的优势和弊端:
优势:解决多个程序同时运行。
弊端:当进程多了的时候,每个进程占用CPU的次数在某一时间段内会变短,就会出现“卡”的情况。
JVM中多线程分析:
虚拟机的启动本身依赖多条线程。例如:垃圾回收器的工作。
补充:
堆栈空间分配(操作系统)
栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
堆栈缓存方式
栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放。
堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
堆栈数据结构区别
堆(数据结构):堆可以被看成是一棵树,如:堆排序。
栈(数据结构):一种先进后出的数据结构。
堆栈(java)
1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。
2.堆栈的优缺点
栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。
但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。
堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。
但缺点是,由于要在运行时动态分配内存,存取速度较慢。
3.Java中的数据类型有两种。
一种是基本类型
这种类型的定义是通过诸如
float a= 3.0;的形式来定义的,称为自动变量。
值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。
这里的a是一个指向float类型的引用,指向3.0这个字面值。
这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。
另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
int a=3;
int b=3;
编译器先处理int a= 3;
首先它会在栈中创建一个变量为a的内存空间,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。
接着处理int b= 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。
注意:这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。
相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。
如上例,我们定义完a与b的值后,再令a=4;那么,b不会等于4,还是等于3。
在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。
因此a值的改变不会影响到b的值。
另一种是包装类数据,【如Integer,String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于【堆】中】,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。
4.String是一个特殊的包装类数据。
即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建(作为对比,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字面值是不能通用的,除了String。
而在JDK5.0中,这种表达式是可以的!因为编译器在后台进行Integer i = new Integer(3)的转换)。
前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。
Java中的有些类,如DateFormat类,可以通过该类的getInstance()方法来返回一个新创建的类,似乎违反了此原则。其实不然。该类运用了单例模式来返回类的实例,只不过这个实例是在该类内部通过new()来创建的,而getInstance()向外部隐藏了此细节。
那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。
关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:
(1)先定义一个名为str的对String类的对象引用变量:String str;
(2)【在【栈】中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象o。
如果已经有了值为"abc"的地址,则查找对象o,并返回o的地址。】【注意:上文说数据时存放在堆中,此文说数据存放在栈中?因为此处不是通过new()创建的】
(3)将str指向对象o的地址。
值得注意的是,一般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用!
为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。
String str1="abc";
String str2="abc";
System.out.println(str1==str2);//true
注意,我们这里并不用str1.equals(str2);的方式,因为这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而我们在这里要看的是,str1与str2是否都指向了同一个对象。
结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。
我们再来更进一步,将以上代码改成:
String str1="abc";
String str2="abc";
str1="bcd";
System.out.println(str1+","+str2);//bcd,abc
System.out.println(str1==str2);//false
这就是说,赋值的变化导致了类对象引用的变化,str1指向了另外一个新对象!而str2仍旧指向原来的对象。上例中,当我们将str1的值改为"bcd"时,JVM发现在栈中没有存放该值的地址,便开辟了这个地址,并创建了一个新的对象,其字符串的值指向这个地址。
事实上,String类被设计成为不可改变(immutable)的类。如果你要改变其值,可以,但JVM在运行时根据新值悄悄创建了一个新对象,然后将这个对象的地址返回给原来类的引用。这个创建过程虽说是完全自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有一定的不良影响。
再修改原来代码:
String str1="abc";
String str2="abc";
str1="bcd";
String str3=str1;
System.out.println(str3);//bcd
String str4="bcd";
System.out.println(str1==str4);//true
我们再接着看以下的代码。
String str1 = new String("abc");
String str2 = "abc";
System.out.println(str1==str2); //false
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1==str2); //false
创建了两个引用。创建了两个对象。两个引用分别指向不同的两个对象。
重要:以上两段代码说明,只要是用new()来新建对象的,都会在堆中创建,而且其字符串是单独存值的,即使与栈中的数据相同,也不会与栈中的数据共享。
5. 数据类型包装类的值不可修改。不仅仅是String类的值不可修改,所有的数据类型包装类都不能更改其内部的值。
6. 结论与建议:
(1)我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。
因此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。
(2)使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。
而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。这个思想应该是享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。
(3)当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。
(4)由于String类的immutable性质,当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。
补充:垃圾回收
对象怎么被回收,只有对象自己最清楚,所以每个对象都具备着能被被回收的方法。---->每个对象都具备的方法,应该定义在Object对象中。
Object类中有一个方法-->protected void finalize();
方法解释:当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
子类重写 finalize 方法,以配置系统资源或执行其他清除。
System类中有一个方法-->static void gc();
方法解释: 运行垃圾回收器。
调用 gc 方法暗示着 Java 虚拟机做了一些努力来回收未用对象,以便能够快速地重用这些对象当前占用的内存。
当控制权从方法调用中返回时,虚拟机已经尽最大努力从所有丢弃的对象中回收了空间。
调用 System.gc() 实际上等效于调用:Runtime.getRuntime().gc();
内存中的垃圾何时回收?
当堆内存中产生垃圾以后,并不是马上进行垃圾回收,因为垃圾回收是一个线程,调用垃圾回收方法会抢占CPU的使用权,会影响主函数的运行。
何时回收?会判断堆内存的剩余空间大小,当到达警戒容量的时候启动垃圾回收。
单线程在执行时的过程:
当一个程序被加载,主函数进入栈内存,到栈底;调用第一个方法,执行第一个方法,第一个方法结束后出栈;调入第二个方法...
当所有方法执行结束后,主函数完成任务,主函数出栈。
创建新执行线程有两种方法。
|--方法一:将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。
|--方法二:是声明实现 Runnable 接口的类。该类然后实现 run 方法。
然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。接下来可以分配并启动该子类的实例。
多线程的创建方式一:
线程的创建JVM是做不了的,要用JVM所在的操作系统完成。我们无法直接操作操作系统来创建新的线程。那么JVM提供了这样的对象来调用底层的方法来创建新的线程。
java.lang包下面包含Thread类:
线程 是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。
每个线程都有一个优先级,高优先级线程的执行优先于低优先级线程。每个线程都可以或不可以标记为一个守护程序。
当某个线程中运行的代码创建一个新 Thread 对象时,该新线程的初始优先级被设定为创建线程的优先级,并且当且仅当创建线程是守护线程时,新线程才是守护程序。
当 Java 虚拟机启动时,通常都会有单个非守护线程(它通常会调用某个指定类的 main 方法)。
Java 虚拟机会继续执行线程,直到下列任一情况出现时为止:
调用了 Runtime 类的 exit 方法,并且安全管理器允许退出操作发生。
非守护线程的所有线程都已停止运行,无论是通过从对 run 方法的调用中返回,还是通过抛出一个传播到 run 方法之外的异常。
创建线程的目的是为了开辟一条新的路径,去运行指定代码,与其他的代码一起运行。运行指定代码,就是这个执行路径的任务。
JVM创建的主线程的任务都定义在了主函数(main方法)内。
那么自定义的线程的内容(任务)定义在哪里呢??
Thread类用于描述线程,而线程是有内容(任务)的,所以Thread类要有对线程内容(任务)的描述。
那么这个任务就是通过Thread类中的run();方法来体现。也就是说Thread类中的run();方法封装了新线程的内容(任务)。
这就是为什么要继承Thread并且要重写run()方法,而不是直接new Thread();的原因。
运行:不能直接调用run()方法,那样跟主函数调用一样,还是单线程。要用start()方法。
void start() : 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
1.获取线程的名称-->Thread-线程编号。(创建时产生)
2.获取运行时线程名称-->static Thread currentThread() 返回对当前正在执行的线程对象的引用。
重点:与单线程相比,多线程的每一个线程都相当于都占用一个单独的栈内存,进行各自方法的弹栈,压栈操作。
在多线程中,当主线程结束的时候,并不意味这JVM结束,只要有 线程还活着,JVM就不会结束。
多线程的四种状态(重点):
临时阻塞状态
|
|
线程创建—————————start()———————>线程运行<——————————————sleep(time)——————————————>冻结状态。
| <———————————notify() / wait()———————————>冻结状态。
|
|
run()方法运行结束
线程运行结束或者
stop()
|
|
|
线程消亡
CPU的执行资格:可以被CPU处理,在处理队列中
CPU的执行权:正在被CPU处理。
运行中的线程既具备执行资格,又有执行权。
冻结状态释放执行权,并释放执行资格。
假设现在有A B C D 四个线程,当A进入运行状态时候,那么B C D三个线程出于什么状态呢?他们有执行资格,但是却没有执行权,既不是运行状态也不是冻结状态。
那么我们称B C D这样的线程状态为临时阻塞状态。
临时阻塞状态:具备执行资格,但不具备执行权,正在等待拿到执行权。
多线程的创建方式二:
如果当前任务要使用多线程来运行,而当前的类已经继承了一个父类,根据Java语言的特性,只能实现单继承,那么这个时候要怎么办?
解决方法一:让该类的父类继承Thread类并覆盖run()方法(不得已而为之)。
解决方法二:声明实现 Runnable 接口的类。该类然后实现 run 方法。 然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。接下来可以分配并启动该子类的实例。
两种方法哪一种更好?
实现Runnable更好:
1.可将线程的任务从线程的子类中分离出来,进行了单独的封装,安装面向对象的思想将任务封装成对象。
2.避免了Java单继承的局限性。
多线程实例:卖票---->要求:开启四个线程,共同买100张票。
方法一:100张票设置为静态方法,将其放入静态区域,而非堆中,共享资源。
方法二:实现runnable接口,创建一个线程任务【对象】,创建四条线程,将任务对象传给四个线程。
补充:this
局部变量和成员变量命名冲突。 局部变量中的值在栈中,成员变量作为对象的属性被封装到堆中,现在要把栈中的局部变量赋值给堆中的局部变量。
这里介绍关键字:this---->代表对象。代表哪个对象呢??代表当前对象。
this:代表当前方法所属对象的引用。
this内存详解:
情景一:执行:Person p = new Person("SNOOPY");
1.首先,在栈中创建引用p
2.在堆中创建地址为0x0056的Person对象并对其默认初始化。name=null;
3.因为new的对象要传值,所以调用对应的构造函数,将其进栈执行。注意:该构造函数进栈的是被Person("SNOOPY")对象调用的,所以this=0x0056;
所以对象中的name被赋值为SNOOPY。
4.最后,将0x0056赋值给p。
注意:当对象引用p调用该对象的其他方法时,当方法一进栈,this即被赋值为p代表的对象的内存地址-->0x0056。
情景二:执行:Person p = new Person("SNOOPY",20);
源码:
Person(String name){
this.name = name;
}
Person(String name,int age){
this(name);//注意:不能写成this.Person(name);因为构造函数是对象初始化的,而this指代的是对象,对象都没有初始化所以不能调用这个方法。
this.age = age;
}
1.首先,在栈中创建引用p
2.在堆中创建地址为0x0089的Person对象并对其默认初始化。name=null;age=0;
3.因为new的对象要传值,此时栈中的局部变量name=SNOOPY;age=20;所以调用对应的构造函数,将其进栈。this被赋值为0x0089。
4.将栈中的name和age的值赋值给堆中地址为0x0089的对象的成员变量。在此时发现this(name)。
5.调用Person(String name)进栈,并进行赋值。结束后弹栈。
6.结束后弹栈Person(String name,int age)。最后,将0x0089赋值给p。
注意:不能用此方法重复的递归调用其它方法,如果一直有方法进栈而没有方法出栈,最后会导致栈内溢出。
补充:什么时候用静态?
|--静态变量
当分析对象中所具备的成员变量的值都相同的时候,这个时候这个成员就可以被修饰成静态。
只要数据在对象中不同,就是对象特有的数据,必须以非静态的方式存储。
如果是相同的数据,对象不需要修改只需要使用即可,不需要存放在对象中,可以修饰成静态的。
|--静态方法
只有一点:就是该方法功能是否访问到对象中特有的数据。
即:从源代码看,该方法功能是否需要访问对象中的非静态变量,如果需要的话,那么该功能就是非静态的,如果不需要,那就用静态修饰符进行修饰。
|--静态块
随着类加载而执行,而且只执行一次。
代码块{//给所有对象初始化。}
补充:JAVA类加载具体过程
JVM栈由堆、栈、本地方法栈、方法区等部分组成。
方法区:存放了要加载的类信息、静态变量、final类型的常量、属性和方法信息。理解为代码段,是用来存放代码的,包括静态区和非静态区。
堆内存:所有通过new创建的对象的内存都在堆中分配。
栈内存:每个线程执行每个方法的时候都会在栈中申请一个栈帧,每个栈帧包括局部变量区和操作数栈,用于存放此次方法调用过程中的临时变量、参数和中间结果。
本地方法栈:用于支持native方法的执行,存储了每个native方法调用的状态。
当一个类要进行加载:
1.代码首先加载到方法区:如果是静态的,就放到静态区,如果是非静态的,就放到非静态区,根据内容的不同,按照自上而下的顺序进行存放。
2.当遇到要用到其他类的情况时候,同样按照顺序进行加载。
3.当遇到静态方法调用的时候,先加载类,类加载接受后,静态方法进栈。注意:栈只是存放静态方法的局部变量,为其开辟空间,其它的语句只有在该方法执行的时候才依次为其开辟空间。
当方法结束后,自动弹栈。
4.当遇到创建对象时例如:Person p = new Person("java",20);此时mian()方法在栈底,添加一个变量p,并在堆中开辟空间地址为0x0096,将Person中的非静态变量初始化。
然后再将构造函数Person(String name,int age)进栈,此构造函数被内存地址为0x0096的对象调用,所以this=0X0096;
然后name=java,age=20;初始化完,将0X0096赋值给p。
多线程安全问题:
实例:
class Ticket implements runnable{ private int num = 100; public void run(){ while(ture){ if(num > 0){ System.out.println(Thread.currentThread.getName()+"===sale==="+num--); } } } }
假如共有三条线程卖票,当num=1时,线程1的执行到了判断语句,刚执行完判断语句,CPU执行权被线程2抢去,此时的num还没有执行【--】的操作,判断条件依然成立。
而线程2也刚刚执行完判断语句1,CPU执行器被线程3抢去,线程3刚刚执行完判断语句之后,CPU执行权又给了线程1,因为之前线程1已经判断过了,所以不需要在判断了,所以线程1卖了1号票。
然后线程2卖了0号票,线程3卖了-1号票。
上述现象为多线程的安全问题。
多线程安全问题产生的原因:
1.多个线程操作共享数据
2.操作共享数据的线程代码有多条
理解:当一条线程在执行操作共享数据的多条代码时,其他线程参与了运算,就会导致线程安全问题。
如何解决?
当一条线程在执行操作共享数据的多条代码时,【不要让】其他线程参与了运算,就不会导致线程安全问题。
在Java中,用同步代码块(同步锁)可以解决这个问题。
同步代码块格式:
synchronized(对象){
需要被同步的代码;
}
此synchronized相当于是一个开关,没有线程占用时候打开,当有一个线程操作该共享数据时该开关关闭。
为什么锁里面要有对象?
因为后期要对这个同步中的线程进行监视,而监视的方法在锁对象中。
同步锁的优点与弊端:
优点-->解决了线程安全问题。
弊端-->相对降低了效率,因为同步外的线程都会判断同步锁。
同步的前提:
必须有多个线程并使用【同一个锁】。
实例:
两位储户,每个人都要到银行存钱,每次100,一共三次。
public class ThreadTest { public static void main(String[] args) { Cus c = new Cus(); Thread t1 = new Thread(c); Thread t2 = new Thread(c); t1.start(); t2.start(); } } class Bank{ private double sum; public void add(double num){ sum += num; System.out.println(Thread.currentThread().getName()+"----sum=="+sum); } } class Cus implements Runnable{ private Bank b = new Bank(); @Override public void run() { for (int i = 0; i < 3; i++) { b.add(100); } } }
没有加锁前,输出结果:
Thread-1----sum==200.0
Thread-0----sum==200.0
Thread-1----sum==300.0
Thread-0----sum==400.0
Thread-1----sum==500.0
Thread-0----sum==600.0
说明在add()方法中存在了线程安全问题。(只要在add()方法中输出前面加一条沉睡语句,即可出现线程安全问题)
加锁后:(还可以在add()中添加锁)
class Cus implements Runnable{ private Bank b = new Bank(); @Override public void run() { synchronized (b) { for (int i = 0; i < 3; i++) { b.add(100); } } } }
运行结果:
Thread-0----sum==100.0
Thread-0----sum==200.0
Thread-0----sum==300.0
Thread-1----sum==400.0
Thread-1----sum==500.0
Thread-1----sum==600.0
同步函数:
可以直接用synchronized关键字修饰函数
class Cus implements Runnable{ private Bank b = new Bank(); @Override public synchronized void run() { for (int i = 0; i < 3; i++) { b.add(100); } } }
上面这段代码其实是有问题的,在run()方法上面加上同步锁,意味着只要第一个线程开启,这个线程就获得了执行权,只要该线程不自动让出执行权或者结束运行,
该线程永远霸占该共享资源。
解决:
class Cus implements Runnable{ private Bank b = new Bank(); @Override public void run() { for (int i = 0; i < 3; i++) { operation(); } } public synchronized void operation(){ b.add(100); } }
问题来了:那么同步函数到底是用的那个锁呢?
验证同步函数的锁:
public class ThreadTest { public static void main(String[] args) throws InterruptedException { Cus c = new Cus(); Thread t1 = new Thread(c); Thread t2 = new Thread(c); t1.start(); c.flag = false; t2.start(); } } class Bank{ private double sum; public double getSum() { return sum; } public void setSum(double sum) { this.sum = sum; } public void add(double num){ sum += num; System.out.println(Thread.currentThread().getName()+"----sum=="+sum); } } class Cus implements Runnable{ private Bank b = new Bank(); public boolean flag = true; @Override public void run() { if(flag){ while(true){ operation(); } }else{ while(true){ synchronized(new Object()){ if(b.getSum() < 10000){ b.add(100); System.out.println("-----------------synchronized--------------"); }else{break;} } } } } public synchronized void operation(){ if(b.getSum() < 10000){ b.add(100); System.out.println("-------------obj-----------------"); } } }
结果为:
Thread-0----sum==100.0
Thread-1----sum==100.0
-----------------synchronized--------------
-----------------synchronized--------------
Thread-1----sum==200.0
-----------------synchronized--------------
Thread-0----sum==300.0
-----------------synchronized--------------
Thread-1----sum==400.0
-----------------synchronized--------------
...
上述结果表明:同步函数与同步块并不是一个对象。如果是一个对象就不会出现上述现象。
使用this关键字。解释见前面补充this关键字。
将new Object()修改为this即可。
总结:同步函数使用的是this。
同步函数和同步代码块的区别:
同步函数锁是固定的this对象。
同步代码块的锁匙任意的对象。
验证静态同步函数的锁。
public class ThreadTest { public static void main(String[] args) throws InterruptedException { Cus c = new Cus(); Thread t1 = new Thread(c); Thread t2 = new Thread(c); t1.start(); Thread.sleep(1); c.flag = false; t2.start(); } } class Bank{ private double sum; public double getSum() { return sum; } public void setSum(double sum) { this.sum = sum; } public void add(double num){ sum += num; System.out.print(Thread.currentThread().getName()+"----sum=="+sum); } } class Cus implements Runnable{ private static Bank b = new Bank(); public boolean flag = true; @Override public void run() { if(flag){ while(true){ operation(); } }else{ while(true){ synchronized(this){ try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } if(b.getSum() < 10000){ b.add(100); System.out.println("-----------------synchronized--------------"); }else{break;} } } } } public static synchronized void operation(){ try { Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } if(b.getSum() < 10000){ b.add(100); System.out.println("-------------obj-----------------"); } } }
运行结果:
Thread-0----sum==100.0-------------obj-----------------
Thread-1----sum==200.0-----------------synchronized--------------
Thread-1----sum==400.0-----------------synchronized--------------
Thread-0----sum==400.0-------------obj-----------------
Thread-0----sum==500.0-------------obj-----------------
Thread-1----sum==600.0-----------------synchronized--------------
Thread-0----sum==800.0-------------obj-----------------
Thread-1----sum==800.0-----------------synchronized--------------
从上述结果来看:静态同步函数锁用的对象不是this。因为静态方法中不能含有this。所以静态同步函数锁用对象是this.getClass();
getClass():只要是同一个类产生的对象,getClass()都是相同的.
解决方法:this.getClass();或者类名.class
补充:在内存中,非静态区都会有一个this所属,因为非静态区数据只能被当前对象访问。