java并发基础及原理
java并发基础知识导图
一 java线程用法
1.1 线程使用方式
1.1.1 继承Thread类
继承Thread类的方式,无返回值,且由于java不支持多继承,继承Thread类后,无法再继承其他类。
1 /** 2 * 继承Thread类的方式创建线程 3 */ 4 public class ThreadExtendTest extends Thread{ 5 6 @Override 7 public void run() { 8 System.out.println("create thread by thread extend"); 9 } 10 11 public static void main(String[] args) { 12 new ThreadExtendTest().start(); 13 } 14 15 }
1.1.2 实现Runnable接口
无返回值,但由于实现的是接口,可以继承其他类。
1 /** 2 * 实现Runnable接口的方式创建线程,无返回值 3 */ 4 public class ThreadRunnableTest implements Runnable{ 5 @Override 6 public void run() { 7 System.out.println("create thread by runnable implements"); 8 } 9 10 public static void main(String[] args) { 11 new Thread(new ThreadRunnableTest()).start(); 12 } 13 }
1.1.3 实现Callable接口
有返回值,且可以继承其他类。
1 /** 2 * 实现Callable接口,和FutureTask结合,创建线程,有返回值 3 */ 4 public class ThreadCallableTest implements Callable<String> { 5 @Override 6 public String call() throws Exception { 7 return "create thread by implements Callable"; 8 } 9 10 public static void main(String[] args) throws ExecutionException, InterruptedException{ 11 FutureTask<String> future1 = new FutureTask<String>(new ThreadCallableTest()); 12 Thread thread1 = new Thread(future1); 13 thread1.start(); 14 System.out.println(future1.get()); 15 } 16 }
1.1.4 Runnable或Callable和Future结合的线程池方式
1 /** 2 * 线程池的方式使用线程 3 */ 4 public class ThreadPoolTest implements Callable<String> { 5 @Override 6 public String call() throws Exception { 7 return "create thread by thread pool"; 8 } 9 10 public static void main(String[] args) throws ExecutionException, InterruptedException{ 11 ExecutorService executorService = Executors.newFixedThreadPool(1); 12 Future<String> future1 = executorService.submit(new ThreadPoolTest()); 13 System.out.println(future1.get()); 14 executorService.shutdown(); 15 } 16 }
java线程的四种用法,本质其实分为两类,一个是继承方式,一个是实现接口方式,由于java只支持单继承,继承Thread类后,不能再继承其他类,而使用实现接口的方式,还可以再继承其它有用的类。Runnable方式无返回值,Callable用于需要返回值的场景。利用线程池创建线程,实际上依然是借助Runnable或者Callable。
1.2 线程状态
java线程状态分为NEW、RUNNABLE、BLOCKED、TIME_WAITING、WAITING、TERMINATED六种状态,线程状态间可以转换,状态转换图如下:
图1.1 java线程状态转换图 当进程出现问题时,可查看进程各线程的运行状况。
- ps -aux | grep java查看进程pid,如图1.2。
- top -Hp pid,查看进程各线程cpu、内存等占用情况,如图1.3。
- jstack pid,查看进程各线程的堆栈信息,包括运行状态等,如图1.4。
图1.2 查找进程pid
图1.3 查看进程各线程资源占用
图1.4 查看线程状态
1.3 安全终止线程
1.3.1 volatile变量+轮询
1 /** 2 * volatile变量+轮询安全终止线程 3 */ 4 class VolatileThread implements Runnable{ 5 private volatile boolean cancelled; 6 @Override 7 public void run() { 8 while(!cancelled){ 9 System.out.println("VolatileThread"); 10 } 11 System.out.println("thread stop by volatile variable"); 12 } 13 public void cancel() { cancelled = true; } 14 }
1.3.2 中断+轮询
1 //中断+轮询安全终止线程 2 class InterruptThread implements Runnable{ 3 @Override 4 public void run() { 5 while(!Thread.currentThread().isInterrupted()){ 6 System.out.println("InterruptThread"); 7 } 8 System.out.println("thread stop by interrupt thread"); 9 } 10 11 }
1.4 线程通信
1.4.1 等待通知机制
等待/通知经典范式: 等待方遵循如下原则。
- 获取对象锁
- 如果条件不满足,调用对象wait方法,被通知后仍要检查条件
- 条件满足则执行对应的逻辑
synchronized (对象){ while(条件不满足){ 对象.wait(); } 对应的处理逻辑; } 通知方遵循如下原则:
- 获得对象的锁。
- 改变条件。
- 通知所有等待在对象上的线程。
synchronized (对象){ 改变条件; 对象.notifyAll(); } 用法示例:
1 /** 2 * 线程通信,等待/通知机制 3 */ 4 public class ThreadCommunication { 5 public static void main(String[] args) { 6 final Object lock = new Object(); 7 8 new Thread(new Runnable() { 9 @Override 10 public void run() { 11 System.out.println("thread A is waiting to get lock"); 12 synchronized (lock) { 13 try { 14 System.out.println("thread A get lock"); 15 TimeUnit.SECONDS.sleep(1); 16 System.out.println("thread A do wait method"); 17 lock.wait(); 18 System.out.println("thread A wait end"); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 } 23 } 24 }).start(); 25 26 new Thread(new Runnable() { 27 @Override 28 public void run() { 29 System.out.println("thread B is waiting to get lock"); 30 synchronized (lock) { 31 System.out.println("thread B get lock"); 32 try { 33 TimeUnit.SECONDS.sleep(5); 34 } catch (InterruptedException e) { 35 e.printStackTrace(); 36 } 37 lock.notify(); 38 System.out.println("thread B do notify method"); 39 } 40 } 41 }).start(); 42 43 } 44 }
wait能让当前线程阻塞,并释放拥有此对象的monitor(锁),当notify或者notifyAll被调用时,一个或所有线程被唤醒去竞争锁,调用notify/notifyAll方法的线程不会立即释放锁,只有当主动释放锁时,其它被唤醒的线程才能获得锁。
1.4.2 wait/notify底层实现
wait/notify都是native方法,底层是c++实现。每一对象都对应一个ObjectMonitor对象(synchronized重量级锁,对象头会保存指向ObjectMonitor对象的指针),ObjectMonitor对象如下图,有两个队列分别为_WaitSet和_EntryList,用来保存ObjectWaiter对象列表,处于wait状态的线程,会被加入到wait set,处于等待锁block状态的线程,会被加入到entry set。_owner指向获得ObjectMonitor的线程。在调用某一对象的wait或notify方法前,需对这一对象加synchronized锁,加锁成功的线程会设置ObjectMonitor对象的_owner指针。当加锁成功但调用对象的wait方法,会将线程封装成ObjectWaiter对象,并放入_WaitSet队列,线程调用park方法阻塞。当对象调用notify时,会将_WaitSet头节点移入_EntryList。当锁被释放时,会调用_EntryList中线程的unpark方法竞争锁。
图1.5 ObjectMonitor对象结构
二 java线程常用方法含义及原理
2.1 daemon线程
daemon线程主要用作程序中后台调度以及支持性工作,Thread.setDaemon(true)将线程设置为守护线程,当java虚拟机不存在非Daemon线程的时候,java虚拟机将会退出。虚拟机退出时,Daemon线程会立即终止,不能依靠finally块中的内容来确保关闭或清理资源。
2.2 join方法
thread.join()方法,会等待其它线程thread执行完,继续执行当前线程。join方法源码如下:
1 public final synchronized void join(long millis) 2 throws InterruptedException { 3 long base = System.currentTimeMillis(); 4 long now = 0; 5 6 if (millis < 0) { 7 throw new IllegalArgumentException("timeout value is negative"); 8 } 9 10 if (millis == 0) { 11 while (isAlive()) { 12 wait(0); 13 } 14 } else { 15 while (isAlive()) { 16 long delay = millis - now; 17 if (delay <= 0) { 18 break; 19 } 20 wait(delay); 21 now = System.currentTimeMillis() - base; 22 } 23 } 24 }
Thread.join()方法通过wait/notify模式实现,调用join方法,线程对象最终会调用wait阻塞。线程执行完,会调用notify方法唤醒。
2.3 sleep()方法
sleep方法是native方法,其最终是利用内核提供的sleep系统调用实现。linux中的sleep()大致流程如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <signal.h> 4 #include <unistd.h> 5 ///时钟编程 alarm() 6 void wakeUp() 7 { 8 printf("please wakeup!!/n"); 9 } 10 int main(void) 11 { 12 printf("you have 4 s sleep!/n"); 13 signal(SIGALRM,wakeUp); 14 alarm(4); 15 //将进程挂起 16 pause(); 17 printf("good morning!/n"); 18 return EXIT_SUCCESS; 19 }
linux中的sleep,利用进程信号通信,注册信号SIGALRM的信号处理函数,并调用alarm设置定时器,接着调用pause()将进程挂起,将进程状态改为挂起状态,内核会重新分配CPU时间片,当alarm设置的时间到达时,操作系统会发送SIGALRM信号,此挂起的进程会执行WarkUp信号处理函数,pause函数会在进程捕捉到信号,并进行信号处理后返回。
2.4 yield()方法
yield方法是native方法,最终调用Linux内核的sched_yield系统调用,它会使当前线程放弃CPU使用权,加入同等优先级队列的队尾,和其它线程一起重新竞争CPU。sleep当传入参数为0时,和yield相同。
wait、sleep、yield都会让当前线程暂停执行,但使用场景、方式和原理各不相同。wait方法用于线程间通信,使用wait方法需先获得对象的锁,wait会释放线程拥有的锁;sleep方法用于短时间暂停当前线程,无强制要求必须先获取对象锁,且sleep不会释放线程拥有的锁。yield只是出让CPU使用权,让线程进入就绪调度队列重新调度,可能马上又重新获取CPU运行。
2.5 ThreadLocal用法及源码解析
ThreadLocal为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量,而不会出现冲突的问题,但由于每个线程都会有一个变量的副本,会导致消耗的内存变多。ThreadLocal类的get方法源码如下:
1 public T get() { 2 Thread t = Thread.currentThread(); 3 ThreadLocalMap map = getMap(t);//返回线程内部变量t.threadLocals 4 if (map != null) { 5 ThreadLocalMap.Entry e = map.getEntry(this); 6 if (e != null) { 7 @SuppressWarnings("unchecked") 8 T result = (T)e.value; 9 return result; 10 } 11 } 12 return setInitialValue();//调用t.threadLocals = new ThreadLocalMap(this, firstValue); 13 }
get方法会获得当前线程的map结构,map不为空,则以ThreadLocal变量为健,获取此变量的值,当ThreadLocalMap变量为空时,则会为线程创建一个ThreadLocalMap变量,可以看到,每个线程都有一个map的结构,当访问ThreadLocal变量时,会去当前访问线程的线程栈中取各自的threadLocalsb变量,从而达到多线程副本的目的。
参考文献
- Java并发学习之四种线程创建方式的实现与对比.
- 如何使用jstack分析线程状态.
- 如何安全地终止线程?
- Java 并发:线程间通信与协作.
- JVM源码分析之Object.wait/notify实现.
- sleep实现原理.
- pause()函数.
- Java中Wait、Sleep和Yield方法的区别.
- Java线程源码解析之yield和sleep.
- Java并发编程:深入剖析ThreadLocal.
原文地址: https://www.cnblogs.com/killianxu/p/11703824.html
本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
相关文章