多线程笔记
来源:互联网 发布:亨廷顿中国知乎 编辑:程序博客网 时间: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
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程 笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程(笔记)
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 事务传播行为
- 使用MariaDB数据库管理系统。
- Fragment碎片(小activity)定义及用法
- golang 走起(六) 超时
- EmguCV学习(三)
- 多线程笔记
- Android切换前后置摄像头并录制视频
- 北邮OJ 2016网预 - Saber's Conjecture
- 浅说jQuery.Cookie.js
- curl采集登陆后的界面
- 如何在阿里云HPC上搭建 neural style 服务?
- golang 走起(七) 多态
- windows10下使用STM32F103的软件集合
- JS简单验证码