多线程学习笔记一

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所属,因为非静态区数据只能被当前对象访问。

原文地址:https://www.cnblogs.com/snoopylovefiona/p/4682521.html