线程知识点总结

java线程的两个特性:
 可见性和有序性
 多个线程之间是不能传递数据交互的,他们之间的交互只能通过共享变量来实现。在多个线程之间共享了count类的一个对象,这个对象时被创建在主内存(堆内存)每个线程都有自己的工作内存(线程栈)工作内存存储了主内存count对象的一个副本,当线程操作count对象时,首先从主内存复制count对象到工作内存中,然后执行代码count.count()该变了num值,最后用工作内存count刷新主内存count。当一个对象在多个内存中都存在副本时,如果一个内存修改了共享变量,其他线程也应该能够看到被修改后的值,此为可见性。
 由上述可知,一个赋值操作并不是一个原子性操作,多个线程执行时,CPU对线程的调度是随机的,我们不知道当前程序被执行到哪步就切换到了下一个线程,一个最经典的例子就是银行汇款问题,一个银行账户存款100,这时一个人从该账户取10元,同时另一个人向该账户汇10元,那么余额应该还是100。那么此时可能发生这种情况,A线程负责取款,B线程负责汇款,A从出内存读到100,B从主内存读到100,A执行减10操作,并将数据刷新到主内存,这时主内存数据100-10=90,而B内存执行加10操作,并将数据刷新到主内存,最后主内存数据100+10=110,显然这是一个严重的问题,我们要保证A线程和B线程有序执行,先取款后汇款或者先汇款后取款,此为有序性。
多线程: 线程是指进程中的一个执行流程。 
线程与进程的区别:每个进程都需要操作系统为其分配独立的内存地址空间,而同一进程中的线程在同一块地址空间中工作,这些线程可以共享同一块内存和系统资源。
一个线程只能被启动一次。第二次启动时将会抛出java.lang.IllegalThreadExcetpion异常
线程的五种状态:
 1、新建状态(new):用new语句创建的线程处于新建状态,此时它和其它的java对象一样,仅仅在堆中分配了内存
 2、就绪状态(Runnable):当一个线程创建了以后,其它的线程调用它的start()方法,该线程就进入了就绪状态。处于这个状态的线程位于可运行池中,等待获得cpu的使用权
 3、运行状态(Running):处于这个状态的线程占用cpu,执行程序的代码
 4、阻塞状态(Blocked):当线程处于阻塞状态时,java虚拟机不会给线程分配cpu,直到线程从新进入就绪状态,它才有机会转到运行状态
  1)位于对象等待池中的阻塞状态:当线程运行时,如果执行了某个对象的wait()方法,java虚拟机就会把线程放到这个对象的等待池中。
  2)位于对象锁中的阻塞状态,当线程处于运行状态时,试图获得某个对象的同步锁时,如果该对象的同步锁已经被其他线程占用,JVM就会把这个线程放到这个对象的锁池中。
  3)其他的阻塞状态:当线程执行了sleep()方法,或者调用了其他线程的join()方法,或者发出了I/O请求时,就会进入这个状态中。
 5、死亡状态(Dead):当线程退出了run()方法,就进入了死亡状态,该线程结束了生命周期。或者正常退出或者遇到异常退出
Thread类的isAlive()方法判断一个线程是否还活着,当线程处于死亡状态或者新建状态时,该方法返回false,在其余状态下,该方法返回true。
线程调度模型:
 分时调度模型和抢占调度模型
 JVM采用抢占调度模型
 所谓的多线程的并发运行,其实是指宏观上看,各个线程轮流获得cpu的使用权,分别执行各自的任务。(线程的调度不是跨平台,它不仅取决于java虚拟机,它还依赖于操作系统)
 
 如果希望明确地让一个线程给另外一个线程运行的机会,可以采取以下的办法之一
 1、 调整各个线程的优先级
 2、 让处于运行状态的线程调用Thread.sleep()方法
 3、 让处于运行状态的线程调用Thread.yield()方法
 4、 让处于运行状态的线程调用另一个线程的join()方法

 调整各个线程的优先级
 Thread类的setPriority(int)和getPriority()方法分别用来设置优先级和读取优先级。
 如果希望程序能够移值到各个操作系统中,应该确保在设置线程的优先级时,只使用MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY这3个优先级
 
 线程睡眠:当线程在运行中执行了sleep()方法时,它就会放弃CPU,转到阻塞状态。
 线程让步:当线程在运行中执行了Thread类的yield()静态方法时,如果此时具有相同优先级的其它线程处于就绪状态,那么yield()方法将把当前运行的线程放到运行池中并使另一个线程运行。如果没有相同优先级的可运行线程,则yield()方法什么也不做。
 

 Sleep()方法和yield()方法都是Thread类的静态方法,都会使当前处于运行状态的线程放弃CPU,把运行机会让给别的线程,两者的区别在于:
         1、sleep()方法会给其他线程运行的机会,而不考虑其他线程的优先级,因此会给较低线程一个运行的机会;yield()方法只会给相同优先级或者更高优先级的线程一个运行的机会。
 2、当线程执行了sleep(long millis)方法后,将转到阻塞状态,参数millis指定睡眠时间;当线程执行了yield()方法后,将转到就绪状态。
   3、sleep()方法声明抛出InterruptedException异常,而yield()方法没有声明抛出任何异常
   4、sleep()方法比yield()方法具有更好的移植性
 
   等待其它线程的结束:join()
          当前运行的线程可以调用另一个线程的 join()方法,当前运行的线程将转到阻塞状态,直到另一个线程运行结束,它才恢复运行。
 
 定时器Timer:在JDK的java.util包中提供了一个实用类Timer, 它能够定时执行特定的任务。

 线程的同步
原子操作:根据Java规范,对于基本类型的赋值或者返回值操作,是原子操作。但这里的基本数据类型不包括long和double, 因为JVM看到的基本存储单位是32位,而long 和double都要用64位来表示。所以无法在一个时钟周期内完成。
 
自增操作(++)不是原子操作,因为它涉及到一次读和一次写。
 
原子操作:由一组相关的操作完成,这些操作可能会操纵与其它的线程共享的资源,为了保证得到正确的运算结果,一个线程在执行原子操作其间,应该采取其他的措施使得其他的线程不能操纵共享资源。
 
同步代码块:为了保证每个线程能够正常执行原子操作,Java引入了同步机制,具体的做法是在代表原子操作的程序代码前加上synchronized标记,这样的代码被称为同步代码块。
 
同步锁:每个JAVA对象都有且只有一个同步锁,在任何时刻,最多只允许一个线程拥有这把锁。
 
当一个线程试图访问带有synchronized(this)标记的代码块时,必须获得 this关键字引用的对象的锁,在以下的两种情况下,本线程有着不同的命运。
1、 假如这个锁已经被其它的线程占用,JVM就会把这个线程放到本对象的锁池中。本线程进入阻塞状态。锁池中可能有很多的线程,等到其他的线程释放了锁,JVM就会从锁池中随机取出一个线程,使这个线程拥有锁,并且转到就绪状态。
2、 假如这个锁没有被其他线程占用,本线程会获得这把锁,开始执行同步代码块。
(一般情况下在执行同步代码块时不会释放同步锁,但也有特殊情况会释放对象锁
如在执行同步代码块时,遇到异常而导致线程终止,锁会被释放;在执行代码块时,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池中)
 
线程同步的特征:
1、如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制)
2、 每个对象都有唯一的同步锁
3、 在静态方法前面可以使用synchronized修饰符。
4、 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或者执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。
5、 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。
 
线程安全的类:
1、 这个类的对象可以同时被多个线程安全的访问。
2、 每个线程都能正常的执行原子操作,得到正确的结果。
3、 在每个线程的原子操作都完成后,对象处于逻辑上合理的状态。
 
释放对象的锁:
1、 执行完同步代码块就会释放对象的锁
2、 在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会被释放
3、 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放对象锁,进入对象的等待池。
 
死锁
当一个线程等待由另一个线程持有的锁,而后者正在等待已被第一个线程持有的锁时,就会发生死锁。JVM不监测也不试图避免这种情况,因此保证不发生死锁就成了程序员的责任。
 
如何避免死锁
一个通用的经验法则是:当几个线程都要访问共享资源A、B、C 时,保证每个线程都按照同样的顺序去访问他们。
 
线程通信
Java.lang.Object类中提供了两个用于线程通信的方法
1、 wait():执行了该方法的线程释放对象的锁,JVM会把该线程放到对象的等待池中。该线程等待其它线程唤醒
2、 notify():执行该方法的线程唤醒在对象的等待池中等待的一个线程,JVM从对象的等待池中随机选择一个线程,把它转到对象的锁池中。


线程的优先级
 线程的优先级范围:1-10,JVM线程调度程序是基于优先级的抢先调度机制,在大多数情况下,当前运行的线程优先级大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况下,当线程池中线程都具有相同的优先级,调度程序的JVM实现自由选择它喜欢的线程。这时候调度程序的操作有两种可能:一是选择一个线程运行,直到它阻塞或者运行完成为止。二是时间分片,为池内的每个线程提供均等的运行机会。
 注意:当设计多线程应用程序的时候,一定不要依赖于线程的优先级。因为线程调度优先级操作是没有保障的,只能把线程优先级作用作为一种提高程序效率的方法,但是要保证程序不依赖这种操作。
 设置线程的优先级:线程默认的优先级是创建它的执行线程的优先级。可以通过setPriority(int newPriority)更改线程的优先级。例如:
  Thread t = new MyThread();
  t.setPriority(8);
  t.start();
 线程优先级为1-10之间的正整数,JVM从不会改变一个 线程的优先级。JVM从不会改变一个线程的优先级。然而1-10质检单额值是没有保证的,一些JVM可能不能识别10个不同的值,而将这些优先级进行每两个或多个合并,变成少于10个的优先级,则两个或多个优先级可能被映射为一个优先级。

 线程默认优先级是5,Thread类中有三个常量,定义线程优先级范围:static int MAX_PRIORITY线程可以具有最高优先级
   static int MIN_PRIORITY线程可以具有最低优先级
   static int NORM_PRIORITY默认优先级

posted @ 2012-05-09 12:08  陕北蜂农  阅读(158)  评论(0编辑  收藏  举报