多线程笔记

来源:互联网 发布:亨廷顿中国知乎 编辑:程序博客网 时间:2024/06/03 08:28
1.线程的状态:        新建状态      新创建了一个线程对象        就绪状态      现场对象创建后,其他线程调用了该对象的start()方法,                      该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权        运行状态      就绪状态的线程获取了CPU使用权,这个脑子执行程序代码        阻塞状态      线程因某种原因放弃CPU使用权,暂时停止运行,直到线程进入就绪状态,才有机会转到运行状态。                      阻塞的三种情况:                          等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中,需要其他对象调用notify()或notifyAll()方法,让其重回就绪状态                          同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中                          其他阻塞:运行的线程执行sleep()或join()方法,或发出I/O请求时,JVM会把该线程置为阻塞状态。                                    当sleep()超时,join()线程终止或超时,I/O处理完成,线程重新转入就绪状态        死亡状态      线程的run()方法执行完了,或因异常退出了run()方法,该线程结束生命周期2.线程的操作        1.调整线程优先级     1—10级,默认是5,越大,优先级越高                             setPriority()   getPriority()        2.线程休眠           Thread.sleep(long mills),使当前线程转到阻塞状态,让出CPU资源给其他线程        3.线程等待           Object类的wait()方法,导致当前的线程等待,直到其他线程调用此对象(Object)的notify()或notifyAll()方法唤醒该线程        4.线程让步           Thread.yield()方法,暂停当前正在执行的线程对象,把执行机会让给相同或更高优先级的线程        5.线程合并           join()方法,等待其他线程终止。                             在当前线程调用另一个线程t的t.join()方法,则当前线程转入阻塞状态,直到t线程运行结束,当前线程再由阻塞状态变为就绪状态        6.线程唤醒           Object类的notify()和notifyAll()方法,唤醒在此对象监视器上等待的单个线程。                             如果所有线程都在此对象上等待,notify()则会选择唤醒其中一个线程(随机选择),notifyAll()会唤醒在此对象监视器上等待的所有线程。3.sleep()和wait()的区别        sleep()是Thread类的静态方法,表示在指定的毫秒数内让当前正在执行的线程休眠        wait()则是Object类的普通方法,表示当前线程等待,直到其他线程调用这个对象的notify()或notifyAll()方法        synchronized(obj){        }        解析:             Java引入了同步监视器解决线程同步问题             obj就是同步监视器,上面的代码含义:                线程开始执行同步代码块之前,必须先获得对同步监视器的锁定,任何时候只能有一个线程可以获得对同步监视器的锁定,                同步代码执行结束后,该线程自然释放掉对同步监视器的锁定        sleep()和wait()都具有使当前线程休眠的作用,区别在于:             1.sleep()是Thread类的静态方法,它不能改变对象的锁,即:当在一个synchronized方法中调用sleep()时,线程休眠了,但对象的锁未被释放,其他线程仍无法访问这个对象。             2.wait()方法则会在线程休眠的同时,释放掉对象锁,其他线程可以访问该对象4.定时器      Timer       线程用其安排以后再后台线程中执行的任务,可安排任务执行一次,或定期重复执行      TimerTask   由Timer安排为一次执行或重复执行的任务5.可重入锁  ---  ReentrantLock      两种构造函数:           ReentrantLock()        --- 非公平锁,不会根据线程等待时间的长短来优先调度线程           ReentrantLock(true)    --- 公平锁,  这些锁倾向于将访问权授予等待时间最长的线程, 吞吐量低,速度极慢      ReentrantLock的使用方法           class X{             private final ReentrantLock lock = new ReentrantLock();             //定义其他变量             public void m(){                //当试图获得锁时,如果锁已经被别的线程占用,则该线程会一直被阻塞,直到获得锁                lock.lock();                try{                //处理数据                }finally{                  lock.unlock():                }             }           }      ReentrantLock的常用方法           lock()                 获得锁对象,如果该锁对象没有被其他线程占有,则立刻获得锁,并执行接下来的处理,                             如果锁对象被其他线程占有,则该线程就会被阻塞在请求所得对象的操作上,直到其他线程释放锁,该线程得到锁,才能继续向下执行           unlock()                 释放锁,已经获得锁对象的线程在操作完数据后要释放锁,以便其他的线程重新获得锁来执行自己的操作,否则所有的试图获得锁的线程都不能继续向下执行。                         在finally语句中释放锁      ReentrantLock可重入锁的原理:           ReentrantLock类中有一个计数器,用来表示一个线程获取锁的数量,初始值为0,当一个线程获得锁时,该值被置为1。           可重入:即已经获得锁的线程还可以继续调用同一个锁所保护的方法,即再一次获得锁,                   当再一次获得锁时,ReentrantLock中的计数器就+1,每释放一次锁,计数器就-1,当计数器减为0时,这个线程才是真正的释放了这个锁           ReentrantLock类是依赖于创建它的类的对象,如果两个线程同时访问同一个ReentrantLock对象lock保护的方法,可以保证同步                                                    如果两个线程同时访问非同一个ReentrantLock对象lock保护的方法,不能保证6.深入JVM  http://blog.csdn.net/vernonzheng/article/details/8458483      JVM内存划分          方法区、Java堆、Java栈、程序计数器、本地方法栈          JVM每遇到一个线程,就为其分配一个程序计数器、Java方法栈、原生方法栈,当线程终止时,两者锁占用的内存空间也会被释放掉。          栈中存储的是栈帧,每个栈帧对应一个 "运行现场",在每个 "运行现场"中,如果出现了一个局部对象,则它的实例数据被保存在堆中,而类数据被保存在方法区      当一个class文件被ClassLoader load进入JVM后,方法指令保存在Stack中,此时Heap堆中没有数据,      然后程序计数器开始执行指令,如果是静态方法,直接依次执行指令代码,当然此时指令代码是不能访问heap堆数据区的;      如果是非静态方法,在执行前要先new对象,在Heap中分配数据,并把Stack中的地址指针交给非静态方法,这样程序计数器依次执行指令,而指令代码此时能访问到Heap数据区      注:指令可以理解为类的方法      栈: ---  线程私有          Java程序的运行区,在线程创建时创建,生命周期跟所在线程相同,线程结束,栈内存即释放,栈不存在垃圾回收问题          栈中数据以栈帧格式存在,栈帧是一个内存区块,是一个数据集,是一个有关方法和运行期数据的数据集,          当一个方法A被调用时就产生一个栈帧F1,并被压入到栈中,A方法又调用了B方法,则产生栈帧F2也被压入栈,          执行完毕后,先弹出F2栈帧,再弹出F1栈帧   ---  先进后出          栈帧中保存的3类数据:              本地变量      包括:输入参数、输出参数、方法内的变量              栈操作        记录出栈、入栈的操作              栈帧数据      包括:类文件、方法等      堆: ---  线程共享的一块内存区域,GC就作用于堆上          JVM的内存数据区,Heap的管理复杂,每次分配不定长的内存空间,专门用来保存对象的实例,          即:在Heap中分配一定的内存来保存对象实例,实际上也只是保存对象实例的属性值,属性类型和对象本身的类型标记等,              并不保存对象的方法(方法是指令,保存在Stack中)          堆中分配的对象,需要在Stack栈中保存一个内存地址(对象引用),用来定位该对象实例在Heap中的位置,便于操作该对象                Young年轻代                      主要是新创建的对象                      Young区分为3部分:                             一个Eden区                             两个相同的Survivor区                      说明:                           Survivor区间中,某一时刻其中一个是被使用的,另一个留作垃圾收集时复制对象用,                           在Young区变满时,GC机会将存活的对象移到空闲的Survivor区,根据JVM策略,在经过几次GC后,任然存活于Survivor的对象将被移动到Old区                Old年老代                      主要保存生命周期长的对象,一般是一些老的对象,当一些对象复制转移一定次数后(GC多次回收还存活下来的对象),对象将会被转移到Old代                JVM参数,调整内存大小: 堆内存、栈内存大小都可以定义,堆内存的三个部分、新生的的各个比例都能调整                     Total Heap  调节堆的总内存                         -Xms   指定JVM初始启动后的初始化内存                         -Xmx   指定JVM堆的最大内存                     调节年轻代内存                         -XX:NewRatio=8   old:young=8:1                         即:young年轻代 eden+2*survivor = 1/9                         -XX:SurvivorRatio=32  eden:survivor=32:1, 一个Survivor占Young年轻代的1/34                         -Xmn    设置年轻代的大小                     设置永久带大小                         -XX:PermSize=16M                         -XX:MaxPermSize=64M                     设置线程栈大小                         -XX:Xss=128K      方法区:  ---  线程共享区             存储类的定义数据,常量,静态变量,常量池等      常见问题?            1.为什么会产生 StackOverFlowError?                一个线程把Stack内存耗尽,一般是递归函数造成的            2.为什么会产生OutOfMemory?                 Heap堆内存没有足够的可用内存了,新申请内存的对象大于Heap空闲内存                 分两种:                       OutOfMemory:java heap size    申请不到足够内存,检查应用或调整堆内存大小                       OutOfMemory:PermGenSpace      永久存储区的死对象太多,一般重启即可7.线程中的锁:       1.持有锁的线程释放锁的场景:                1.加锁的代码块执行完成                2.加锁的代码块执行过程中出现异常,线程终止                3.Object.wait() 会释放锁       2.不释放锁的场景:                1.sleep()/join() 不会释放锁                  sleep()   目的是让线程暂停指定时间                  join()    目的是让线程等待指定线程执行完成后,它才开始运行8.线程池       应用场景:               1.最佳资源利用率               2.程序吞吐量               3.任务明确但数量多               4.thread创建和管理的成本               5.合理构件程序结构,封装对同一类型事物的处理       线程池基本结构:               1.任务队列               2.工作线程集合               3.队列和集合的管理逻辑                          1.控制队列大小                          2.控制工作线程集合的大小                          3.管理工作线程生命周期                          4.控制工作现场的执行任务                                 1.任务逻辑异常                                 2.任务耗时问题                                 3.任务并发引用                                 4.避免错误                          5.死锁                                 1.资源过载                                 2.并发问题9.线程的执行       1.每次调用start()方法时,都会创建一个执行线程       2.当一个程序的所有线程都运行完成时(所有非守护线程都运行完成),这个Java程序结束       3.如果初始线程(执行main()方法的线程)结束了,其余的线程将继续执行,直到它们运行结束       4.如果某一个线程调用了System.exit()来结束程序的执行,则所有的线程都将结束10.线程的中断机制:       1.isInterrupted()和interrupt()方法       Java提供了中断机制,可以使用它来结束一个线程,这种机制要求线程检查它是否被中断了,然后决定是不是响应这个中断请求,       线程允许忽略中断请求并继续执行           eg:              创建一个线程,使其运行5秒后,再通过中断机制强制使其终止              /**               * 创建一个线程,使其运行5秒,再通过中断机制强制使其终止               * Created by hetiewei on 2016/4/14.               */              public class ThreadInterruptTest1 {                  public static void main(String args[]){                      Thread task = new PrimeGenerator();                      task.start();                      try {                          Thread.sleep(5000);                      } catch (InterruptedException e) {                          e.printStackTrace();                      }                      //5秒之后,线程被中断                      task.interrupt();                  }              }              class PrimeGenerator extends Thread {                  @Override                  public void run() {                      long number = 1L;                      while (true){                          if(isPrime(number)){                              System.out.println("Number "+number+" is Prime ");                          }                          //调用isInterruped()方法检查线程是否被中断,如果被中断,则写一个信息并结束线程的执行                          if (isInterrupted()){                              System.out.println("The Prime generator has been Interrupted ");                              return;                          }                          number++;                      }                  }                  private boolean isPrime(long num){                      if (num < 2){                          return true;                      }                      for (long i=2;i<num/2; i++){                          if (num%i==0){                              return false;                          }                      }                      return true;                  }              }       2.使用InterruptedException异常,来更好的控制线程的中断           eg:             线程类完成,在一个文件夹及其子文件夹中寻找一个指定文件, 通过InterruptedException异常来控制线程的中断                 递归遍历文件                 public static void main(String args[]){                         FileSearch seacher = new FileSearch("D:/","test");                         Thread thread = new Thread(seacher);                             thread.start();                         //等待10秒,然后中断线程                         try {                             TimeUnit.SECONDS.sleep(10);                         } catch (InterruptedException e) {                             e.printStackTrace();                         }                         thread.interrupt();                     }       3.等待线程的终止   --- join()方法             一些情形下,必须等待线程的终止,eg:程序在执行其他任务时,必须先初始化一些必须的资源,             可以使用线程来完成这些初始化任务,等待线程终止,在执行程序的其他任务             Thread类的join()方法:                   当一个线程对象的join()方法被调用时,调用它的线程将被挂起,直到这个线程对象完成它的任务             线程t1中调用:              t2.join()                        t2线程运行结束,t1才继续运行              t2.join(100)                        t1在一下两个条件之一成立时,即可继续运行:                                                    1.t2线程运行结束                                                    2.过了100毫秒11.守护线程  setDaemon(true)           优先级很低,通常当同一个应用程序中没有其他的线程运行时,守护线程才运行。           当守护线程是程序中唯一运行的线程时,守护线程执行结束后,JVM将结束这个程序           守护线程通常是无限循环的,以等待服务请求或执行线程的任务,在没有其他线程运行时,守护线程随时可能结束           典型:                Java的垃圾回收器 GC           eg:              创建一个工作线程和一个守护线程                      用户线程:将事件写入到一个队列中                      守护线程:管理这个队列,如果生成的事件超过10秒,就会被移除           特别注意:                   setDaemon()只能在start()方法调用之前设置,一旦线程开始运行,将不能修改守护状态12.线程局部变量  --- ThreadLocal           如果创建的对象是实现了Runnable接口的类的实例,用它作为入参创建多个线程对象,并启动这些线程,           则:所有的线程将共享相同的属性。即:你在一个线程中改变了一个属性,所有线程都会被这个改变所影响。           某些情况下,这个对象的属性不需要被所有线程共享,使用ThreadLocal则能提高性能13.线程安全           线程安全问题,何时出现?                 多线程同时访问同一个临界资源时,临界资源(一个变量、一个对象、一个文件、一个数据库表等)           如何解决线程安全问题?                 序列化访问临界资源方案 : 即:在同一时刻,只能有一个线程访问临界资源  ---  同步互斥访问                 实现:                     在访问临界资源的代码前加锁,当访问完临界资源后是否锁,让其他线程继续访问, 对临界资源加锁,当一个线程访问该临界资源时,其他线程只能等待                 两种方式实现同步互斥访问:                     synchronized  和  Lock           synchronized同步方法或同步块                 在Java中,每个对象都拥有一个锁标记(监视器),多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。                 使用synchronized标记一个方法或代码块,当某个线程调用该对象的synchronized方法或代码块时,这个线程便获得了该对象的锁,                 其他线程暂时无法访问这个方法,只能等待这个方法执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或代码块                 说明:                          1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。                        2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的,                        3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。                          4)每个类也会有一个锁,它可以用来控制对static数据成员的并发访问,并且如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,                             此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象。                 synchronized代码块                 synchronized代码块类似于以下这种形式:                          synchronized(synObject) {                              }                          当在某个线程中执行这段代码块,该线程会获取对象synObject的锁,从而使得其他线程无法同时访问该代码块。                          synObject可以是this,代表获取当前对象的锁,也可以是类中的一个属性,代表获取该属性的锁。           Lock 解决并发安全问题                 Lock方式 VS synchronized方式 实现线程同步访问                    1.synchronized是Java内置关键字,Lock是一个类,通过这个类可实现同步访问                    2.synchronized不需要手动释放锁,代码块执行完毕后,系统会自动让线程释放锁,                      Lock需要手动释放锁,如果没主动释放锁,可能导致死锁现象                 使用lock()的语法格式:                      Lock lock = ...;                      lock.lock();                      try{                          //处理任务                      }catch(Exception ex){                      }finally{                          lock.unlock();   //释放锁                      }                 说明:                      1.lock()方法用来获取锁,如果锁已被其他线程获取,则等待                      2.tryLock() 有返回值,表示用来尝试获取锁,获取成功返回true,获取失败返回false                        该方法无论如何都会立即返回,在拿不到锁时不会一直等待                      3.tryLock(long time, TimeUnit unit) 类似tryLock(),                        该方法在拿不到锁时会等待一定时间,在时间期限内仍拿不到锁,返回false,                        如果一开始就拿到锁或在等待时间内拿到锁,返回true                 使用tryLock()获取锁的语法格式:                      Lock lock = ...;                      if(lock.tryLock()) {                           try{                               //处理任务                           }catch(Exception ex){                           }finally{                               lock.unlock();   //释放锁                           }                      }else {                          //如果不能获取锁,则直接做其他事情                      }           锁的分类:                   可重入锁:                           具备可重入性的锁,锁的分配是基于线程,而不是基于方法调用                                即:同一个线程,外层方法获得锁之后,内层方法仍然有获取该锁的代码,但不受影响                                    一个线程可重复获取某个对象的锁,不必等到第一次获取的这个对象的锁释放                           synchronized和ReentrantLock都是可重入锁                   可中断锁:                           可以响应中断的锁,Lock的lockInterruptibly()方法                           synchronized时不可中断锁,Lock是可中断锁                   公平锁:  性能差                         尽量以请求的顺序来获取锁,等待时间最久的线程先获取锁                         ReentrantLock和ReentrantReadWriteLock默认都是非公平锁,可以设置为公平锁,但性能差                   读写锁:                         将对一个资源(文件等)的访问分成了2个锁,一个读锁和一个写锁,                         ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。                         可以通过readLock()获取读锁,通过writeLock()获取写锁。           几个常用的锁:                         ReentrantLock    ---    可重入锁                                        lock()                                        unlock()                         ReadWriteLock    ---    读写锁                                        Lock readLock()                                        Lock writeLock()                                        说明:                                             将文件的读写操作分开,分成2个锁分配给线程,从而使得多个线程可以同时进行读操作                         ReentrantReadWriterLock   ---  可重入读写锁,实现了ReadWriteLock接口,提供了更丰富的方法           Lock   VS   synchronized , 如何选择?                             1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;                           2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;                           3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;                           4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。                           5)Lock可以提高多个线程进行读操作的效率。                           总结:在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized           volatile
0 0
原创粉丝点击