基础知识总结(二)

2017.10工作总结

10.1 
(1)内存泄漏和内存溢出的区别
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间 
memory leak会最终会导致out of memory!
(2)  vm  内部的线程共享
大多数 JVM 将内存区域划分为 Method Area(Non-Heap)(方法区) ,Heap(堆) , Program Counter Register(程序计数器) ,   VM Stack(虚拟机栈,也有翻译成JAVA 方法栈的),Native Method Stack  ( 本地方法栈 ),其中Method Area 和 Heap 是线程共享的  ,VM Stack,Native Method Stack  和Program Counter Register  是非线程共享的。

(3)jvm判断对象何时回收
在主流的商用程序语言(Java、C#,甚至包括前面提到的古老的Lisp)的主流实现中,
都是称通过可达性分析(Reachability Analysis)来判定对象是否存活的。这个算法的基本思
路就是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所
走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连
(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象是不可用的。

(4) 线程属性
  • GroupName,每个线程都会默认在一个线程组里,我们也可以显式的创建线程组,一个线程组中也可以包含子线程组,这样线程和线程组,就构成了一个树状结构。
  • Name,每个线程都会有一个名字,如果不显式指定,那么名字的规则是“Thread-xxx”。
  • Priority,每个线程都会有自己的优先级,JVM对优先级的处理方式是“抢占式”的。当JVM发现优先级高的线程时,马上运行该线程;对于多个优先级相等的线程,JVM对其进行轮询处理。Java的线程优先级从1到10,默认是5,Thread类定义了2个常量:MIN_PRIORITY和MAX_PRIORITY来表示最高和最低优先级。
  • 每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行
  • isDaemon,这个属性用来控制父子线程的关系,如果设置为true,当父线程结束后,其下所有子线程也结束,反之,子线程的生命周期不受父线程影响。

(5)守护线程
在Java中有两类线程:用户线程 (User Thread)、守护线程 (Daemon Thread)。
所谓 守护线程 ,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说, 只要任何非守护线程还在运行,程序就不会终止
用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。

(6)线程的创建
◆需要从Java.lang.Thread类派生一个新的线程类,重载它的run()方法; 
◆实现Runnalbe接口,重载Runnalbe接口中的run()方法。
为什么Java要提供两种方法来创建线程呢?它们都有哪些区别?相比而言,哪一种方法更好呢?
在Java中,类仅支持单继承,也就是说,当定义一个新的类的时候,它只能扩展一个外部类.这样,如果创建自定义线程类的时候是通过扩展 Thread类的方法来实现的,那么这个自定义类就不能再去扩展其他的类,也就无法实现更加复杂的功能。因此,如果自定义类必须扩展其他的类,那么就可以使用实现Runnable接口的方法来定义该类为线程类,这样就可以避免Java单继承所带来的局限性。
还有一点最重要的就是使用实现Runnable接口的方式创建的线程可以处理同一资源,从而实现资源的共享.(因为可以同时通过thread类开几条线程处理该资源)

(7)   通过实现Runnable接口来实现线程间的资源共享
可见, 如果现实问题中要求必须创建多个线程来执行同一任务,而且这多个线程之间还将共享同一个资源,那么就可以使用实现Runnable接口的方式来创建多线程程序 。而这一功能通过扩展Thread类是无法实现的, 想想看,为什么?

(8)线程切换
线程六种状态
  • 创建:已经有Thread实例了, 但是CPU还没有为其分配资源和时间片。
  • 就绪:线程已经获得了运行所需的所有资源,只等CPU进行时间调度。
  • 运行:线程位于当前CPU时间片中,正在执行相关逻辑。
  • 休眠:一般是调用Thread.sleep后的状态,这时线程依然持有运行所需的各种资源,但是不会被CPU调度。
  • 挂起:一般是调用Thread.suspend后的状态,和休眠类似,CPU不会调度该线程,不同的是,这种状态下,线程会释放所有资源。
  • 死亡:线程运行结束或者调用了Thread.stop方法。

线程状态切换方法
  • Thread()或者Thread(Runnable):构造线程。
  • Thread.start:启动线程。
  • Thread.sleep:将线程切换至休眠状态。(sleep)是静态方法,由类直接调用。
  • Thread.interrupt:中断线程的执行。
  • Thread.join:等待某线程结束。
  • Thread.yield:剥夺线程在CPU上的执行时间片,等待下一次调度。
  • Object.wait:将Object上所有线程锁定,直到notify方法才继续运行。
  • Object.notify:随机唤醒Object上的1个线程。
  • Object.notifyAll:唤醒Object上的所有线程。

(9) thread的构造方法
eg  : public Thread(Runnable target, String name);
Runnable target
实现了 Runnable 接口的类的实例。要注意的是 Thread 类也实现了 Runnable 接口,因此,从 Thread 类继承的类的实例也可以作为 target 传入这个构造方法。
String name
线程的名子。这个名子可以在建立 Thread 实例后通过 Thread 类的 setName 方法设置。如果不设置线程的名子,线程就使用默认的线程名: Thread-N N 是线程建立的顺序,是一个不重复的正整数。

10.9
(1)synchronized关键字 (本质上是一种悲观锁)
多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问时可能出现的问题。
同步机制可以使用 synchronized关键字 实现。
当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
当synchronized方法执行完或发生异常时,会自动释放锁。

(2) 一个对象存在多个synchronized关键字时

如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到了某个synchronized方法,那么在该方法没有执行完毕前,其他线程是无法访问该对象的任何synchronized方法的。  
     结论: 当synchronized关键字修饰一个方法的时候,该方法叫做同步方法。
  Java中的每个对象都有一个锁(lock),或者叫做监视器(monitor),当一个线程访问某个对象的synchronized方法时, 将该对象上锁 其他任何线程都无法再去访问该对象的synchronized方法了(这里是指所有的同步方法,而不仅仅是同一个方法) ,直到之前的那个线程执行方法完毕后(或者是抛出了异常),才将该对象的锁释放掉,其他线程才有可能再去访问该对象的synchronized方法。
  注意这时候是 给对象上锁 ,如果是不同的对象,则各个对象之间没有限制关系。

(3)静态类中的synchronized关键字
    如果某个synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchronized方法所在的类所对应的Class对象。Java中,无论一个类有多少个对象,这些对象会对应唯一一个Class对象,因此当线程分别访问同一个类的两个对象的两个static,synchronized方法时,它们的执行顺序也是顺序的,也就是说一个线程先去执行方法,执行完毕后另一个线程才开始。

(4)synchronized块
synchronized方法 是一种粗粒度的并发控制,某一时刻,只能有一个线程执行该synchronized方法;
   synchronized块 则是一种细粒度的并发控制,只会将块中的代码同步,位于方法内、synchronized块之外的其他代码是可以被多个线程同时访问到的。
   synchronized方法实际上等同于用一个synchronized块包住方法中的所有语句,然后在synchronized块的括号中传入this关键字。 当然,如果是静态方法,需要锁定的则是class对象。
10.15 

(1)线程的等待与唤醒

wait(), notify(), notifyAll()等方法介绍

  在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
Object类中关于等待/唤醒的API详细信息如下:
notify()         -- 唤醒在此对象监视器上等待的单个线程。
notifyAll()    -- 唤醒在此对象监视器上等待的所有线程。
wait()                                          -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout)                     -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。
wait(long timeout, int nanos)   -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
10.17

第5部分 为什么notify(), wait()等函数定义在Object中,而不是Thread中

Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
OK,线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是: 依据“对象的同步锁”
负责唤醒等待线程的那个线程(我们称为“ 唤醒线程 ”),它只有在获取“该对象的同步锁”( 这里的同步锁必须和等待线程的同步锁是同一个 ),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。








猜你喜欢

转载自blog.csdn.net/u013854708/article/details/80043237