Java 并发系列之四:java 多线程
1. 线程简介
2. 启动和终止线程
3. 对象及变量的并发访问
4. 线程间通信
5. 线程池技术
6. Timer定时器
7. 单例模式
8. SimpleDateFormat
9. txt
1 java并发基础线程 2 线程简介 3 什么是多线程? 4 操作系统调度的最小单元,可以使用 JMX (ThreadMXBean)来查看一个普通的Java程序包含哪些线程 5 可以使用 jstack 查看运行时的线程信息 6 为什么要使用多线程? 7 1. 更多的处理器核心 8 2. 更快的响应时间 9 特别是多业务操作更加快速 10 3. 更好的编程模型 11 Java提供了良好、考究并且一致的多线程编程模型 12 线程优先级 13 背景 14 CPU分配时间片给线程,时间片用完就会发生线程调度,分配到时间片的多少决定了线程使用处理器资源的多少 15 作用 16 线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。 17 Java 18 优先级的范围是1~10,默认优先级是5,优先级搞得线程分配时间片的数量要多于优先级低的线程 19 设置线程优先级时,针对频繁阻塞(休眠/IO操作)的线程需要设置设计高优先级,而偏重计算(需要较多CPU时间/偏运算)的线程需要设置较低的优先级,确保处理器不会被独占。 20 ps:线程优先级不能作为程序正确性的依赖,因为有些操作系统会忽略Java线程对优先级的设定。 21 线程的状态 22 6大状态 23 NEW 24 初始状态,线程被构建,但是还没有调用start()方法 25 RUNNABLE 26 运行状态,Java线程将操作系统中的就绪和运行两种状态统称为“运行状态” 27 BLOCK 28 阻塞状态,表示线程阻塞于锁 29 WAITING 30 等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或者等待) 31 TIME_WAITING 32 超时等待状态,可以在等待的时间自行返回的 33 TERMINATED 34 终止状态,表示当前线程已经执行完毕 35 Java状态转移 36 WAITING-->RUNNABLE 37 Object.notify() 38 Object.notifyAll() 39 LockSupport.unpark(Thread) 40 TIME_WAITING-->RUNNABLE 41 Object.notify() 42 Object.notifyAll() 43 LockSupport.unpark(Thread) 44 BLOCK-->RUNNABLE 45 获取到锁 46 1. 实例化后还未start()方法时的状态 New 47 2. New-->RUNNABLE 48 系统调度 49 Thread.start() 50 running-->ready 51 Thread.yield 52 ready-->running 53 3. RUNNABLE-->WAITING 54 Object.wait() 55 Thread.join() 56 LockSupport.park() 57 4. RUNNABLE-->TIME_WAITING 58 Object.wait(long) 59 Thread.sleep(long) 60 Thread.join(long) 61 LockSupport.parkNanos() 62 LockSupport.parkUntil() 63 5. RUNNABLE-->BLOCKED 64 等待进入synchronized方法 65 等待进入synchronized块 66 6. RUNNABLE-->TERMINATED 67 run方法结束 68 Java 线程状态变迁 69 yield 70 暂停当前正在执行的线程对象。 71 yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。 72 yield()只能使同优先级或更高优先级的线程有执行的机会。 73 注意:yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。 74 注意 75 等待进入synchronized方法/块 为阻塞状态 76 java.concurrent.Lock为 等待状态,因为Lock接口对于阻塞的实现使用了LockSupport类中的相关方法 77 Daemon线程 78 守护线程 79 特殊的线程,陪伴的含义,当进程中不存在非守护线程了,则守护线程自动销毁。 80 典型的守护线程是垃圾回收线程 81 作用是为其他线程提供便利服务 82 是一种支持性线程,主要是用在后台程序做一些后台调度与支持性工作。这意味着当JVM中没有非Daemon线程时,JVM将自动退出。 83 可以通过调用Thread.setDaemon(true)方法将线程设为Daemon线程。(注:该方法必须在start()或者run()方法前执行,也就是说必须在线程启动前执行) 84 注:Daemon线程被用作完成支持性工作,但是在java虚拟机退出时,Daemon线程中的finally块并不一定会执行。在构建Daemon时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑。 85 程序 VS 进程 VS 线程 86 程序 87 一组指令的有序结合,是静态的指令,是永久存在的 88 进程 89 具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源(打开的文件,创建的socket)分配和调度的一个独立单元。进程的存在是暂时的,是一个动态的概念。 90 线程 91 线程是CPU调度和分配的基本单位,是比进程更小的能独立运行的基本单元。本身基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器、一组寄存器和栈)。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。 92 进程 VS 线程 93 定义 94 进程是资源分配和调度的基本单位;线程是CPU/任务调度和执行的基本单位 95 包含关系 96 1个进程包含有多(大于等于1)个线程; 线程是进程的一部分(轻量级进程) 97 地址空间 98 进程之间地址空间独立; 同一进程内的线程共享本进程的地址空间 99 切换开销 100 进程之间切换开销大; 线程之间切换开销小(创建和销毁) 101 创建 102 进程fork/vfork; 线程pthread_create 103 销毁 104 进程结束,它拥有的所有线程都将销毁; 线程结束,不会影响同个进程中的其他线程 105 私有属性 106 进程:PCB(进程控制块); 线程:TCB(线程控制块),线程Id,寄存器,上下文 107 一个程序至少只有一个进程,一个进程至少有一个线程 108 启动和终止线程 109 构造线程 110 init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc ) 111 父线程就是当前线程(开启多线程的线程),子线程对象是由其父线程来进行空间分配的,继承了parent线程是否为Daemon、优先级、加载资源的contextClassLoader以及可继承的ThreadLocal。还会分配给一个唯一的ID来标识这个child线程。 112 init()运行完毕,线程对象就初始化好了,在堆内存中等待运行 113 构造函数 114 Thread(Runnable target) 115 Thread(Runnable target, String name) 116 因为Thread实现类Runnable,所以可以传递Thread 117 如果多个new Thread()里面的Runnable是同一个线程对象,那么那个对象的变量时被这几个新的线程共享的变量。 118 启动线程 119 start() 含义:当前线程(即parent线程)同步告知java虚拟机,只要线程规划器空闲,应立即启动调用start()方法的线程 120 start()方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。这个过程其实就是让系统安排一个时间来调用run()方法,也就是使得线程得到运行,启动线程,具有异步执行的效果。而直接调用run方法不是异步而是同步 121 注意:执行start()方法的顺序不代表线程启动的顺序 122 注意:启动一个线程之前,最好起个名字 123 活动状态就是线程已经启动且尚未终止(isAlive为true),start()调用了就是活动状态了 124 理解中断 125 被其他线程中断: interrupt() 126 判断是否被中断:isInterrupted() 127 测试线程Thread对象是否已经被中断,但是不清除状态标志 128 中断表示复位:Thread.interrupted() 129 测试当前线程是否已经被中断,执行后具有将状态标志清除为false的功能 130 每次抛出InterruptedException之前,JVM会先将中断标识位清除 131 注意:不论是TERMINATED还是InterruptedException, isInterrupted都返回false 132 安全地终止线程 133 中断方式 Interrupt 134 标识位boolean true false 135 当run方法完成后线程终止 136 抛出异常 throw new InterruptedException 137 过期的 138 suspend() 暂停 139 调用后,线程不会释放已经占有的资源(比如锁),而是占有资源进入睡眠状态,容易引发死锁。 140 缺点是独占、数据不同步 141 resume() 恢复 142 缺点是独占、不同步 143 stop() 终止 144 在终结一个线程时不会保证线程的资源正常释放 145 使用stop释放锁将会给数据造成不一致的结果 146 会抛出java.lang.ThreadDeath异常 147 暴力停止线程 148 其他 149 sleep() 150 在指定的毫秒内让当前"正在执行的线程"休眠(暂停执行),即this.currentThread()返回的线程 151 currentThread() 152 构造方法 153 Thread.currentThread().getName() 154 main 155 this.getName() 156 Thread-0 157 直接调用run()方法 158 Thread.currentThread().getName() 159 main 160 this.getName() 161 Thread-0 162 调用start()方法 163 Thread.currentThread().getName() 164 Thread-1 || setName("A")--> A 。 165 this.getName() 166 Thread-0 167 isAlive() 168 线程处于正在运行或准备开始运行的状态就认为线程是存活的, start()之后就是活跃状态了 169 Thread.sleep() false 170 构造方法||直接调用run()方法||调用start()方法 171 Thread.currentThread().isAlive() 172 true 173 this.isAlive() 174 false 175 yield() 176 放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间,但放弃的时间不确定,有可能刚刚放弃,马上就又获得CPU时间片。 177 将CPU让给其他资源导致速度变慢 178 优先级 179 优先级具有继承性,比如A线程启动B线程,则B线程的优先级与A线程是一样的 180 优先级具有规则性,让CPU尽量将执行资源让给优先级比较高的线程。线程的优先级与代码的执行顺序(start()的先后顺序)无关 181 优先级具有随机性,优先级高的线程不一定每一次都先执行完 182 有副作用 183 用等待/通知机制代替 184 对象及变量的并发访问 185 synchronized 186 同步方法 187 方法内的变量为线程安全 188 实例变量非线程安全 189 多个对象多个锁 190 synchronized取得的锁都是对象锁 191 synchronized方法和锁对象 192 A线程先持有object对象的Lock锁,B线程可以异步的方式调用object对象中的非synchronized类型的方法。 193 A线程先持有object对象的Lock锁,B线程如果在这时调用object对象中的其他synchronized类型的方法则需要等待,也就是同步。 194 解决了脏读问题 195 可重入锁 196 自己可以再次获得自己的内部锁 197 当一个线程得到一个对象锁后,再次请求此对象锁时是可以再次得到该对象的锁的。如果不可锁重入的话,就会造成死锁。 198 在一个synchronized方法/块的内部调用本类的其他synchronized方法/块时,是永远可以得到锁的 199 可重入锁也支持在父子类继承的环境中 200 子类完全可以通过可重入锁调用父类的同步方法 201 出现异常,线程持有的锁会自动释放 202 同步不具有继承性,还得在子类的方法中添加synchronized关键字 203 有弊端的,假如A线程调用同步方法执行一个长时间任务,B线程则必须等待比较长的时间,这样的情况可以用synchronized语句块来解决,虽然能实现同步,但会受到阻塞,所以影响运行效率。 204 同步语句块 205 当两个并发线程访问同一个对象object的synchronized(this)同步代码块时,一段时间内只有一个线程被执行,另一个线程必须等待当前线程执行完这个代码块以后才能执行改代码块 206 当一个线程访问一个对象object的synchronized(this)同步代码块时,另一个线程仍然可以访问该object对象中的 非synchronized(this)同步代码块 207 一半同步一半异步 208 当一个线程访问一个对象object的synchronized(this)同步代码块时,另一个线程对同一个object对象中所有其他synchronized(this)同步代码块的访问将被阻塞,说明synchronized使用的对象监视器是一个 209 synchronized方法和synchronized(this)同步代码块都是锁定当前对象的 210 将任意对象作为对象监视器,synchronized(非this对象anyobject) 211 优点 212 synchronized(非this对象anyobject)代码块中的程序与同步方法是异步的 213 不与其他锁this同步方法争抢this锁,可以大大提高运行效率 214 结论 215 当多个线程同时执行synchronized(x)同步代码块时呈现同步效果 216 同步原因,使用了同一个对象监视器 217 当其他线程执行x对象中synchronized同步方法时呈现同步效果 218 当其他线程执行x对象方法里面的synchronized(this)同步代码块时呈现同步效果 219 针对X对象内部的同步方法和同步代码块 220 多个线程调用同一个方法是随机的 221 多个线程之间没有固定的顺序,随机的 222 静态同步synchronized方法和synchronized(class)代码块 223 应用在静态方法上 224 对当前的 *.java文件对应的Class类进行持锁 225 注意,一个是对象锁,一个是Class锁,会出现异步的情况,Class锁可以对类的所有对象实例起作用, 226 synchronized(class)对class上锁后,其他线程只能以同步的方式调用class2的静态同步方法 227 两个不同的对象,但是静态同步方法还是同步运行 228 只要对象不变,即使对象的属性被改变,运行的结果还是同步 229 synchronized(string) 230 String常量池特性,两个线程拥有相同的锁,造成另一个线程不能运行 231 注意:给string赋值另一个值,锁对象就变了 232 大多数情况下,同步synchronized代码块都不使用String作为锁对象,而改用new Object()实例化对象 233 多线程死锁 234 synchronized(lock1){ 235 synchronized(lock2){} 236 } 237 synchronized(lock2){ 238 synchronized(lock1){} 239 } 240 监测方法 241 1. jps查看运行的线程Run的id 242 2. jstack -l id 243 3. DeadThread 244 volatile 245 非原子:i++ 246 解决就是用synchronized或者AtomicInteger原子类 247 volatile v.s. synchronized 248 修饰 249 volatile只能用于修饰变量 250 synchronized可以修饰方法以及代码块 251 阻塞 252 多线程访问volatile不会发生阻塞 253 多线程访问synchronized会阻塞 254 原子性 255 volatile不能保证原子性,能保证可见性 256 synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步 257 作用 258 volatile解决的是变量在多个线程之间的可见性 259 synchronized解决的是多个线程之间访问资源的同步性 260 线程安全包含原子性和可见性两个方面,java的同步机制都是围绕这两个方面来保证线程安全的 261 Atomic 原子方法和原则方法虽然都是原子的,但是原子方法和原子方法之间的调用不是原子的,解决这样的问题必须用同步,synchronized 262 线程间通信 263 不使用等待/通知机制实现多个线程之间的通信 264 传统的使用sleep()+while(true)死循环来实现多个线程间的通信 265 浪费CPU资源 266 volatile & synchronized 267 共享内存和本地内存拷贝的同步更新问题,使得变量不一定能是最新的 268 volatile: 保证所有线程对变量的可见性 269 synchronized:保证线程对变量访问的可见性和排他性,获取monitor; 主要确保多个线程在同一时刻,智能有一个线程处于方法或者同步块中。 270 synchronized 271 Monitor.Enter--->get监视器Monitor 272 Enter成功--->锁定对象Object----->Monitor.Exit----->通知同步队列中的线程出队列 273 Enter失败---->线程进入同步队列SynchronizedQueue----->Monitor.Exit后通知,出队列 274 对象、监视器、同步队列和执行线程之间的关系 275 等待/通知机制 276 做什么和怎么做解耦,生产者/消费者模式 277 任意java对象所具备的 278 依托于同步机制,目的就是确保等待线程从wait()方法返回时能够感知到通知线程对变量作出的修改 279 等待通知机制:线程A调用了对象O的wait()方法进入了等待状态,而线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象的wait()与notify()或notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。 280 方法 281 notify(): 通知在一个对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁。 282 notyfyAll(): 通知所有等待在该对象上的线程 283 wait(): 调用该方法的线程进入waiting状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用wait()方法后,会释放对象的锁。在从wait方法返回前,线程与其他线程竞争重新获得锁。 284 wait(long): 超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回(没有线程对锁进行唤醒就自动唤醒)。 285 wait(long, n): 对于超时时间更细粒度的控制,可以达到纳秒。 286 注意 287 1)使用wait()、notify()、notifyAll()方法都需要先对调用对象加锁。如果没有持有适当的锁,也会抛出IllegalMonitorStateException 288 在调用condition.await()方法之前必须调用lock.lock()代码获得同步监视器,否则会报错IllegalMonitorStateException异常 289 2)调用wait()方法后,线程状态由RUNNING变为WAITTING,将锁释放,并将当前线程放到对象的等待队列。WaitingQueue 290 3)notify()或notifyAll()方法调用后,不会立刻释放锁,等待线程依旧不会从wait()返回,需要等待调用notify()、notifyAll()的线程释放锁之后,等待线程才可能会拿到锁,等待线程才有机会从wait()返回。 291 4)notify()方法将等待队列WaitingQueue中的一个等待线程从等待队列中移到同步队列SychronizedQueue中,而notifyAll()方法则是将等待队列WaitingQueue中的所有线程全部移动到同步队列SynchronizedQueue中,被移动的线程状态由WAITING变为BLOCKED。 292 5)从wait()返回的前提是获取调用对象的锁。 293 6) 执行notify()方法后,当前线程不会马上释放该对象锁,呈wait状态的线程也并不能马上获取该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出synchronized代码块后,当前线程才会释放锁,而呈wait状态所在的线程才可以获取该对象锁。 294 7) wait()方法锁释放,notify()锁不释放,sleep()锁不释放 295 8) 当线程呈wait()状态时,调用线程对象的interrupt()方法会出现InterruptedException异常,导致线程终止,锁也会被释放 296 实现 297 synchronized+wait/notify实现等待通知模式 298 被通知的线程是JVM随机选择的 299 所有的线程都会注册在一个对象上 300 condition + lock 实现等待通知模式 301 支持选择性通知,调度线程上更加灵活 302 Lock对象里面可以创建多个Condition实例(对象监视器) 303 等待/通知的经典范式 304 等待方(消费者) 305 原则 306 加锁:获取对象的锁 307 循环:如果条件不满足,那么调用对象的wait()方法,被通知后仍然要检查条件。 308 处理逻辑:条件满足则执行对应的逻辑 309 伪代码 310 synchronized(对象) { 311 while(条件不满足) { 312 对象.wait(); 313 } 314 对应的处理逻辑 315 } 316 通知方(生产者) 317 原则 318 获取对象的锁 319 改变条件 320 通知所有等待在对象上的线程notifyAll() 321 伪代码 322 synchronized(对象) { 323 改变条件 324 对象.notifyAll(); 325 } 326 一个生产者一个消费者 327 条件不同,一个满足条件,一个不满足条件 328 伪代码 329 synchronized(对象) { 330 if(条件满足) { 331 对象.wait(); 332 } 333 跟上面条件一致 334 对应的处理逻辑 335 对象.notify(); 336 } 337 操作数 338 synchronized代码块 339 操作栈 340 synchronized方法 341 多生产者多消费者 342 容易出现假死情况 343 假死状态的线程都呈WAITING状态 344 原因:notify唤醒的可能是异类,也可能是同类;比如生产者唤醒生产者 345 解决:将notify()改成notifyAll(); 不光通知同类,也通知异类 346 synchronized(对象) { 347 while(条件满足) { 348 对象.wait(); 349 } 350 对应的处理逻辑 351 跟上面条件一致 352 对象.notifyAll(); 353 } 354 操作数 355 synchronized代码块 356 操作栈 357 synchronized方法 358 等待超时模式 359 等待超时模式就是在等待/通知范式基础上增加了超时控制,避免执行时间过长,也不会“永久”阻塞调用者,而是按照调用者的要求返回。 360 超时等待:调用一个方法时,等待一段时间(一般给定一个时间段),如果该方法能够在给定的时间段内得到结果,那么将结果立刻返回,反之,超时返回默认结果。 361 伪代码 362 //对当前对象加锁 363 public synchronized Object get(lon mills) throws InterruptedException{ 364 long future = System.currentTimeMillis() + mills;//超时时间 365 long remaining = mills;//等待持续时间 366 //当超时大于0并且result返回值不满足要求则继续等待 367 //当时间到了或者返回结果满足要求则不再等待 368 while((result == null) && remaining > 0){ 369 wait(remaining); 370 remaining = future - System.currentTimeMillis(); 371 } 372 return result; 373 } 374 应用场景:针对昂贵资源(比如数据库的连接)的获取都应该加以超时限制,是系统的一种自我保护机制 375 管道输入/输出流 376 管道输入输出流与普通的文件输入输出流或者网络输入输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介是内存 377 一个线程发送数据到输出管道,另一个线程从输入管道中读取数据 378 4种具体的实现 379 PipedOutputStream 380 write(string.getBytes()) 381 PipedInputStream 382 read(byte[] byteArray) 383 PipedReader 384 read(char[] xx) 385 PipedWriter 386 write(string) 387 面向字节 388 面向字符 389 注意:out.connect(in); 对于Piped类型的流,必须先要进行绑定,也就是调用connect方法,如果没有将输入/输出流绑定起来,对于该流的访问将会抛出异常 390 读取线程启动后如果没有数据被写入,线程会阻塞在in.read()代码中,直到有数据被写入,才继续往下运行。 391 Thread.join()的使用 392 本质 393 涉及了等待/通知机制,等待前驱线程结束,接收前驱线程结束通知,源码本质也是wait()和notifyAll() 394 join方法的作用是等待线程对象销毁 395 while(isAlive()){ 396 wait(0);//表示永远等下去 397 } 398 指导join线程终止后,线程的this.notifyAll()方法会被调用,调用notifyAll()方法是在JVM里实现的,所以在JDK中看不到 399 CountDownLatch可以实现join的功能,并且比join的功能更多 400 方法 401 join(): 当前线程A等待 thread线程终止 之后才从 thread.join 返回 402 join(long millis): 如果在给定的时间内没有终止,那么将会从该超时方法中返回,毫秒 403 join(long millis,int nanos): 如果在给定的时间内没有终止,那么将会从该超时方法中返回,纳秒 404 join过程中,如果当前线程对象被中断,则当前线程出现InterruptedException异常 405 join v.s. synchronized 406 共同点 407 具有使线程排队运行的作用,有点类似同步的运行效果 408 区别 409 join在内部使用wait()方法进行等待,具有释放锁的特点 410 synchronized关键字使用的是 对象监视器原理作为同步 411 join(long) v.s. sleep(long) 412 join(long)在内部使用wait(long)方法进行等待,具有释放锁的特点 413 sleep(long)不释放锁 414 注意:join后面的代码提前运行可能会出现陷阱意外,原因在于join方法先运行抢到锁,然后释放锁;之后再次争抢锁,发现时间已经过去,就会释放锁执行后面的代码,导致出现意外。 415 ThreadLocal的使用 416 背景 417 变量值的共享可以使用public static变量的形式,所有的线程都使用同一个public static变量 418 threadLocal实现每一个线程都有自己的共享变量 419 ThreadLocal,即线程变量,是一个以ThreadLocal对象为键,任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。 420 ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用。作用:提供一个线程内公共变量,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度,或者为线程提供一个私有的变量副本,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。 421 方法 422 initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法。如果set()方法没有调用,第一次get()方法调用时会进行初始化 initialValue(),每个线程会调用一次。 423 get()方法是用来获取ThreadLocal在当前线程中保存的变量副本 424 set(T value)用来设置当前线程中变量的副本 425 remove()用来移除当前线程中变量的副本 426 工作原理 427 Thread类中有一个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的内部类),它是一个Map,他的key是ThreadLocal实例对象。 428 当为ThreadLocal类的对象set值时,首先获得当前线程的ThreadLocalMap类属性,然后以ThreadLocal类的对象为key,设定value,值时则类似。 429 ThreadLocal变量的活动范围为某线程,是该线程“专有的,独自霸占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。 430 由ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非是共享的, 431 存储结构的好处 432 1、线程死去的时候,线程共享变量ThreadLocalMap则销毁。 433 2、ThreadLocalMap<ThreadLocal,Object>键值对数量为ThreadLocal的数量,一般来说ThreadLocal数量很少,相比在ThreadLocal中用Map<Thread, Object>键值对存储线程共享变量(Thread数量一般来说比ThreadLocal数量多),性能提高很多。 434 弱引用GC 435 1、使用完线程共享变量后,显示调用remove方法清除线程共享变量可以及时清除 436 2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。 437 3、对于ThreadLocal变量,我们可以手动的将其置为Null,比如tl =null。那么这个ThreadLocal对应的所有线程的局部变量都有可能被回收。 438 解决Hash冲突方法 439 线性探测 440 应用场景: 用来解决 数据库连接、Session管理、AOP耗时统计等。 441 注意 442 不过有点遗憾的是只能放一个值,再次调用set设置值,会覆盖前一次set的值。如果要多个变量,新建多个ThreadLocal对象 443 是单个线程内函数和组件的共享变量,不是多线程的共享变量,线程隔离 444 覆盖initialValue()方法可以设置默认初始值,使得get()不会返回null 445 使用InheritableThreadLocal类可以让子线程从父线程中取得值,如果子线程在取得值得同时,主线程将InheritableThreadLocal中的值进行修改,那么子线程取到的值还是旧值 446 线程池技术 447 好处 448 1. 降低资源消耗 449 通过重复利用已经创建的线程消除了频繁创建和消亡线程的系统资源开销 450 2. 提高响应速度 451 当任务到达时,任务可以不需要等等待线程创建就能立即执行 452 3. 提高线程的可管理型 453 统一分配,调优和监控 454 4. 面对过量任务的提交能够平缓的劣化。 455 实现原理 456 处理流程 457 1. 提交任务 458 2. 判断核心线程池是否已满,否 创建线程执行任务,是下一步 459 3. 判断队列是否已满,否 将任务存储在队列里,是下一步 460 4. 判断线程池是否已满, 否 创建线程执行任务,是下一步 461 5. 按照饱和策略处理无法执行的任务 462 工作线程 463 线程池创建线程时,会将线程封装成工作线程worker,worker在执行完任务之后还会循环获取工作队列里的任务来执行 464 线程池的使用 465 见 ThreadPoolExecutor 466 线程池的本质就是使用了一个线程安全的工作队列连接工作者线程和客户端线程,客户端线程将任务放入工作队列后便返回,而工作者线程则不断从工作队列上取出工作并执行。当工作队列为空时,所有的工作者线程均等待在工作队列上,当有客户端提交了一个任务后会通知任意一个工作者线程,随着大量任务被提交,更多的工作者线程会被唤醒。 467 线程池的数量不是越多越好,具体的数量需要评估每个任务的处理时间,以及当前计算机的处理器能力和数量。使用的线程过少,无法发挥处理器的性能;使用的线程过多,将会增加系统的无故开销,起到相反的作用。 468 应用 469 数据库连接池 470 服务器线程池 471 更多参加java并发框架-Executor框架 472 Timer定时器 473 线程调度任务以供将来在后台线程中执行的功能。 任务可以安排一次执行,或定期重复执行。 474 定时计划任务 475 构造函数 476 Timer():创建一个新的计时器 477 Timer(boolean isDaemon):创建一个新的计时器,可以指定其关联的线程作为守护程序运行 478 Timer(String name):创建一个新的计时器,其关联的线程具有指定的名称 479 Timer(String name,boolean isDaemon):创建一个新的计时器,其关联的线程具有指定的名称,并且可以指定作为守护程序运行 480 这个类是线程安全的:多个线程可以共享一个单独的Timer对象,而不需要外部同步。内部使用多线程的方式进行处理 481 API 482 Timer.cancel() 483 终止此计时器,将任务队列里的全部任务清空 484 有时候不一定会停止执行计划任务,而是正常执行,因为有时候并没有抢到queue锁 485 TimerTask.cancel() 486 将自身从任务队列里清除,其他任务不受影响 487 purge() 488 从该计时器的任务队列中删除所有取消的任务。 489 schedule(TimerTask task, Date time) 490 在指定的日期,执行一次某任务 491 schedule(TimerTask task, Date firstTime, long period) 492 从指定的时间开始 ,按照执行的间隔周期性地无限循环地执行某一任务。 493 执行任务的时间晚于当前时间,在未来执行 494 执行任务的时间早于当前时间,立即执行 495 schedule(TimerTask task, long delay) 496 以当前时间为参考时间,延迟指定的毫秒数后执行一次制定任务 497 schedule(TimerTask task, long delay, long period) 498 以当前时间为参考时间,延迟指定的毫秒数后执行一次制定任务,再以某一个时间间隔无限次数地执行制定任务 499 scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 500 从指定的时间 开始 ,对指定的任务执行重复的 固定周期执行 。 501 scheduleAtFixedRate(TimerTask task, long delay, long period) 502 以当前时间为参考时间,在指定的延迟之后 开始 ,重新执行 固定周期的指定任务。 503 schedule V.S. scheduleAtFixedRate 504 延时 505 下一次任务的执行时间参考的是上一次任务的“结束”时的时间计算 506 不延时 507 schedule 508 下一次任务的执行时间参考的是上一次任务的“开始”时的时间计算 509 scheduleAtFixedRate 510 下一次任务的执行时间参考的是上一次任务的“结束”时的时间计算 511 追赶性 512 schedule 513 执行任务的时间早于当前时间,立即执行, 不填充 514 scheduleAtFixedRate 515 执行任务的时间早于当前时间,立即执行, 不补充 516 Timer中允许有多个TimerTask任务,以队列的形式被顺序执行,所以执行的时间和预期的时间不一致,因为前面的任务有可能消耗的时间太长,后面的任务运行的时间也会被延迟 517 Java 5.0引入了java.util.concurrent软件包,其中一个java.util.concurrent程序是ScheduledThreadPoolExecutor ,它是用于以给定速率或延迟重复执行任务的线程池。 这实际上是对一个更灵活的替代Timer / TimerTask组合,因为它允许多个服务线程,接受各种时间单位,并且不需要子类TimerTask (只实现Runnable )。 使用一个线程配置ScheduledThreadPoolExecutor使其等同于Timer 。 518 单例模式 519 立即加载/饿汉模式 520 静态实例初始化 521 私有构造方法 522 静态getInstance 523 延迟加载/懒汉模式 524 静态实例声明 525 私有构造方法 526 静态getInstance{ 527 if(是null){ 528 初始化 529 } 530 } 531 解决 532 声明synchronized关键字 533 尝试同步代码块 534 针对重要代码进行单独的同步 535 使用DCL双检查锁机制 536 使用静态内置类实现单例模式 537 序列化和反序列化的单例模式实现 538 readResolve() 539 使用static代码块来实现单例模式 540 使用enum枚举数据类型实现单例模式 541 SimpleDateFormat 542 负责日期的转换和格式化 543 非线程安全的 544 解决方法 545 每个线程一个SimpleDateFormat实例 546 ThreadLocal类
10. 参考网址
- 参考来源:http://cmsblogs.com/wp-content/resources/img/sike-juc.png
- 《Java并发编程的艺术》_方腾飞PDF 提取码:o9vr
- http://ifeve.com/the-art-of-java-concurrency-program-1/
- Java并发学习系列-绪论
- Java并发编程实战
- 死磕 Java 并发精品合集