1.继承Thread类 (真正意义上的线程类),是Runnable接口的实现。
2.实现Runnable接口,并重写里面的run方法
3 .应用程序可以使用Executor框架来创建线程池。Executor框架是juc里提供的线程池的实现。
4.实现Callable接口通过FutureTask包装器来创建Thread线程
通过 Runtime 类的 exec() 方法来创建进程
//打开计算器
Runtime runtime = Runtime.getRuntime();
Process process = runtime.exec("calc");
process.exitValue();
通过 ProcessBuilder 创建进程
//打开记事本
ProcessBuilder build = new ProcessBuilder("notepad");
build.start();
thread.start启动
方法一:在下一个线程start之前先执行前一个线程的join
public class Question2 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(Question2::action,"t1");
Thread t2 = new Thread(Question2::action,"t2");
Thread t3 = new Thread(Question2::action,"t3");
t1.start();
t1.join();
t2.start();
t2.join();
t3.start();
t3.join();
}
private static void action(){
System.out.println("线程正在执行:"+Thread.currentThread().getName());
}
}
方法二:通过自旋来验证线程是否执行完毕
private static void threadLoop(){
Thread t1 = new Thread(Question2::action,"t1");
Thread t2 = new Thread(Question2::action,"t2");
Thread t3 = new Thread(Question2::action,"t3");
t1.start();
while(t1.isAlive()){
}
t2.start();
while(t2.isAlive()){
}
t3.start();
while(t3.isAlive()){
}
}
方法三: 通过线程的wait方法
private static void threadWait(){
Thread t1 = new Thread(Question2::action,"t1");
Thread t2 = new Thread(Question2::action,"t2");
Thread t3 = new Thread(Question2::action,"t3");
threadStartAndWait(t1);
threadStartAndWait(t2);
threadStartAndWait(t3);
}
private static void threadStartAndWait(Thread thread){
if(Thread.State.NEW .equals(thread.getState())){
thread.start();
}
while(thread.isAlive()){
synchronized (thread){
try {
thread.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
设置退出标志,使线程正常退出,也就是当run()方法完成后线程终止
使用interrupt()方法中断线程
使用stop方法强行终止线程(不推荐使用,Thread.stop, Thread.suspend, Thread.resume 和Runtime.runFinalizersOnExit 这些终止线程运行的方法已经被废弃,使用它们是不安全的!)
java的Thread是一个包装,它由GC做垃圾回收,而JVM的Thread是一个OS Thread,由JVM管理。
当一个未捕获异常将造成线程中断的时候JVM会使用Thread.getUncaughtExceptionHandler()来查询线程的UncaughtExceptionHandler并将线程和异常作为参数传递给handler的uncaughtException()方法进行处理。
通过这种捕获的方式,在高并发场景下防止在日志文件打印过多的异常堆栈信息出来。
Thread.setDefaultUncaughtExceptionHandler((thread,throwable)->{
System.out.printf("线程[%s]遇到了异常,详细信息是:%s\n",
thread.getName(),throwable.getMessage());
});
通过覆盖ThreadPoolExecutor.afterExecute 方法,我们才能捕获到ThreadPoolExecutor任务的异常
ThreadPoolExecutor executorService = new ThreadPoolExecutor(
1,1,0,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>()){
@Override
protected void afterExecute(Runnable r,Throwable e){
System.out.printf("线程[%s]遇到了异常,详细信息是:%s\n",
Thread.currentThread().getName(),e.getMessage());
}
};
通过ThreadMXBean获取。
public static void main(String[] args) {
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
long[] threadIds = bean.getAllThreadIds();
for(long threadId:threadIds){
ThreadInfo threadInfo = bean.getThreadInfo(threadId);
System.out.println(threadInfo.toString());
}
}
使用com.sun.management.ThreadMXBean。
import com.sun.management.ThreadMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
public class Question5 {
public static void main(String[] args) {
ThreadMXBean bean = (ThreadMXBean) ManagementFactory.getThreadMXBean();
long[] threadIds = bean.getAllThreadIds();
for(long threadId:threadIds){
ThreadInfo threadInfo = bean.getThreadInfo(threadId);
System.out.println(threadInfo.toString());
long bytes = bean.getThreadAllocatedBytes(threadId);
long mbytes = bytes /1024/1024;
System.out.printf("线程[ID:%d]分配 %s MB内存\n",threadId,mbytes);
}
}
}
修饰一个类:其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象;
修饰一个方法:被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
修饰一个静态的方法:其作用的范围是整个方法,作用的对象是这个类的所有对象;
修饰一个代码块:被修饰的代码块称为同步语句块,其作用范围是大括号{}括起来的代码块,作用的对象是调用这个代码块的对象;
偏向锁只对synchronized 有用,而 ReentrantLock 已经实现了偏向锁。
Java中,任何对象都可以作为锁,既然wait是放弃对象锁,当然就要把wait定义在这个对象所属的类中。更通用一些,由于所有类都继承于Object,我们完全可以把wait方法定义在Object类中,这样,当我们定义一个新类,并需要以它的一个对象作为锁时,不需要我们再重新定义wait方法的实现,而是直接调用父类的wait(也就是Object的wait),此处,用到了Java的继承。
如果wait定义在Thread类里面,这样做有一个非常大的问题,一个线程完全可以持有很多锁,你一个线程放弃锁的时候,到底要放弃哪个锁?当然了,这种设计并不是不能实现,只是管理起来更加复杂。
wait(): 获得锁的对象,释放锁,当前线程又被阻塞,等同于Java 5 LockSupport 中的park方法
notify(): 已经获得锁,唤起一个被阻塞的线程,等同于Java 5 LockSupport 中的unpark()方法
不一定执行完毕
在jvm中增加一个关闭的钩子,当jvm关闭的时候,会执行系统中已经设置的所有通过方法addShutdownHook添加的钩子,当系统执行完这些钩子后,jvm才会关闭。所以这些钩子可以在jvm关闭的时候进行内存清理、对象销毁等操作。
使用场景:
程序正常退出
使用System.exit()
终端使用Ctrl+C触发的中断
系统关闭
使用Kill pid命令干掉进程
Spring 中 AbstractApplicationContext 的registerShutdownHook()
public class Question6 {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
runtime.addShutdownHook(new Thread(Question6::action, "Shutdown Hook Question"));
}
private static void action() {
System.out.printf("线程[%s] 正在执行...\n", Thread.currentThread().getName()); // 2
}
}
通过线程组的方式来处理
public class Question7 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(Question7::action,"t1");
Thread t2 = new Thread(Question7::action,"t2");
Thread t3 = new Thread(Question7::action,"t3");
t1.start();
t2.start();
t3.start();
Thread mainThread = Thread.currentThread();
ThreadGroup threadGroup = mainThread.getThreadGroup();
int count = threadGroup.activeCount();
Thread[] threads = new Thread[count];
threadGroup.enumerate(threads,true);
for(Thread thread: threads){
System.out.printf("当前活跃线程: %s\n", thread.getName());
}
}
private static void action(){
System.out.println("线程正在执行:"+Thread.currentThread().getName());
}
}
Java 集合框架: LinkedList、ArrayList、HashSet、TreeSet、HashMap
J.U.C 框架: CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentSkipListSet、ConcurrentSkipListMap、ConcurrentHashMap
通过 Collections#sychronized,等于外面包装了一层
public class Question8 {
public static void main(String[] args) {
// Java 9 的实现
List list = Arrays.asList(1, 2, 3, 4, 5);
// Java 9 + of 工厂方法,返回 Immutable 对象
list = List.of(1, 2, 3, 4, 5);
Set set = Set.of(1, 2, 3, 4, 5);
Map map = Map.of(1, "A");
// 以上实现都是不变对象,不过第一个除外
// 通过 Collections#sychronized* 方法返回
// Wrapper 设计模式(所有的方法都被 synchronized 同步或互斥)
list = Collections.synchronizedList(list);
set = Collections.synchronizedSet(set);
map = Collections.synchronizedMap(map);
list = new CopyOnWriteArrayList<>(list);
set = new CopyOnWriteArraySet<>(set);
map = new ConcurrentHashMap<>(map);
}
}
相同点:
不同点:
Vector 是同步的,任何时候不加锁。并且在设计中有个 interator ,返回的对象是 fail-fast;
CopyOnWriteArrayList 读的时候是不加锁;弱一致性,while true的时候不报错。
相同点:
不同点:
synchronizedSet、CopyOnWriteArraySet、ConcurrentSkipListSet
不存在。可以通过实现Set,然后在里面包装ConcurrentHashMap来实现
private static class ConcurrentHashSet implements Set {
private final Object OBJECT = new Object();
private final ConcurrentHashMap map = new ConcurrentHashMap<>();
private Set keySet() {
return map.keySet();
}
@Override
public int size() {
return keySet().size();
}
@Override
public boolean isEmpty() {
return keySet().isEmpty();
}
@Override
public boolean contains(Object o) {
return keySet().contains(o);
}
@Override
public Iterator iterator() {
return keySet().iterator();
}
...
}
不一定;Set 在传统实现中,会有fail-fast问题;而在J.U.C中会出现弱一致性,对数据的一致性要求较低,是可以给 Set 对象添加新的元素。
Hashtable: key、value值都不能为空; 数组结构上,通过数组和链表实现。
HashMap: key、value值都能为空;数组结构上,当阈值到达8时,通过红黑树实现。
ConcurrentHashMap: key、value值都不能为空;JDK 1.6中,采用分离锁的方式,在读的时候,部分锁;写的时候,完全锁。而在JDK 1.7、1.8中,读的时候不需要锁的,写的时候需要锁的。并且JDK 1.8中在为了解决Hash冲突,采用红黑树解决。
在 java 6 和 8 中,ConcurrentHashMap 写的时候,是加锁的,所以内存占得比较小,而 ConcurrentSkipListMap 写的时候是不加锁的,内存占得相对比较大,通过空间换取时间上的成本,速度较快,但比前者要慢,ConcurrentHashMap 基本上是常量时间。ConcurrentSkipListMap 读和写都是log N实现,高性能相对稳定。
BlockingQueue 继承了 Queue 的实现;put 方法中有个阻塞的操作(InterruptedException),当队列满的时候,put 会被阻塞;当队列空的时候,put方法可用。take 方法中,当数据存在时,才可以返回,否则为空。
LinkedBlockingQueue 是链表结构;有两个构造器,一个是(Integer.MAX_VALUE),无边界,另一个是(int capacity),有边界;ArrayBlockingQueue 是数组结构;有边界。
一个线程是否可多次获得同一个锁
方法a和方法b被相同可重入锁锁定,a方法里调用了b方法,线程1调用a方法,如果是不可重入锁,会在b方法处阻塞,而可重入锁,由于ab方法持有的锁和调用线程一样,所以可以无阻碍执行。
Lock()提供了无条件地轮询获取锁的方式,lockInterruptibly()提供了可中断的锁获取方式。
相同点:都是阻塞和释放
不同点: Java Thread 对象和实际 JVM 执行的 OS Thread 不是相同对象,JVM Thread 回调 Java Thread.run() 方法,同时 Thread 提供一些 native 方法来获取 JVM Thread 状态,当JVM thread 执行后,自动 notify()了。
while (thread.isAlive()) { // Thread 特殊的 Object
// 当线程 Thread isAlive() == false 时,thread.wait() 操作会被自动释放
synchronized (thread) {
try {
thread.wait(); // 到底是谁通知 Thread -> thread.notify();
// LockSupport.park(); // 死锁发生
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Semaphore,是JDK1.5的java.util.concurrent并发包中提供的一个并发工具类。
Semaphore经常用于限制获取某种资源的线程数量。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LegacyCountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 倒数计数 5
MyCountDownLatch latch = new MyCountDownLatch(5);
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
executorService.submit(() -> {
action();
latch.countDown(); // -1
});
}
// 等待完成
// 当计数 > 0,会被阻塞
latch.await();
System.out.println("Done");
// 关闭线程池
executorService.shutdown();
}
private static void action() {
System.out.printf("线程[%s] 正在执行...\n", Thread.currentThread().getName()); // 2
}
/**
* Java 1.5+ Lock 实现
*/
private static class MyCountDownLatch {
private int count;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private MyCountDownLatch(int count) {
this.count = count;
}
public void await() throws InterruptedException {
// 当 count > 0 等待
if (Thread.interrupted()) {
throw new InterruptedException();
}
lock.lock();
try {
while (count > 0) {
condition.await(); // 阻塞当前线程
}
} finally {
lock.unlock();
}
}
public void countDown() {
lock.lock();
try {
if (count < 1) {
return;
}
count--;
if (count < 1) { // 当数量减少至0时,唤起被阻塞的线程
condition.signalAll();
}
} finally {
lock.unlock();
}
}
}
/**
* Java < 1.5 实现
*/
private static class LegacyCountDownLatch {
private int count;
private LegacyCountDownLatch(int count) {
this.count = count;
}
public void await() throws InterruptedException {
// 当 count > 0 等待
if (Thread.interrupted()) {
throw new InterruptedException();
}
synchronized (this) {
while (count > 0) {
wait(); // 阻塞当前线程
}
}
}
public void countDown() {
synchronized (this) {
if (count < 1) {
return;
}
count--;
if (count < 1) { // 当数量减少至0时,唤起被阻塞的线程
notifyAll();
}
}
}
}
}
1.5:ThreadPoolExecutor、ScheduledThreadPoolExecutor
1.7:ForkJoinPool
corePoolSize:核心线程数量,当有新任务在execute()方法提交时,会执行以下判断:
maximumPoolSize:最大线程数量;
workQueue:等待队列,当任务提交时,如果线程池中的线程数量大于等于corePoolSize的时候,把该任务封装成一个Worker对象放入等待队列;
keepAliveTime: 超时时间。
threadFactory:它是ThreadFactory类型的变量,用来创建新线程。默认使用Executors.defaultThreadFactory() 来创建线程。使用默认的ThreadFactory来创建线程时,会使新创建的线程具有相同的NORM_PRIORITY优先级并且是非守护线程,同时也设置了线程的名称。
handler:它是RejectedExecutionHandler类型的变量,表示线程池的饱和策略。如果阻塞队列满了并且没有空闲的线程,这时如果继续提交任务,就需要采取一种策略处理该任务。线程池提供了4种策略:
覆写ThreadPoolExecutor的beforeExecute和afterExecute方法可以做到,但是不是很好,因为这两个方法被限定在ExecutorPool里面,因为有的框架(例如netty)并没有继承ThreadPoolExector这个类,而是继承了AbstractExecutorService,此时就没有before和after的方法。
下面来看另一种方法:通过ThreadFactory来实现
public class ThreadPoolExecutorThreadQuestion {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
Set threadsContainer = new HashSet<>();
setThreadFactory(executorService, threadsContainer);
for(int i=0;i<9;i++){
executorService.submit(()->{
});
}
// 线程池等待执行 3 ms
executorService.awaitTermination(3, TimeUnit.MILLISECONDS);
threadsContainer.stream()
.filter(Thread::isAlive)
.forEach(thread -> {
System.out.println("线程池创造的线程 : " + thread);
});
Thread mainThread = Thread.currentThread();
ThreadGroup mainThreadGroup = mainThread.getThreadGroup();
int count = mainThreadGroup.activeCount();
Thread[] threads = new Thread[count];
mainThreadGroup.enumerate(threads, true);
Stream.of(threads)
.filter(Thread::isAlive)
.forEach(thread -> {
System.out.println("线程 : " + thread);
});
// 关闭线程池
executorService.shutdown();
}
private static void setThreadFactory(ExecutorService executorService, Set threadsContainer) {
if (executorService instanceof ThreadPoolExecutor) {
ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;
ThreadFactory oldThreadFactory = threadPoolExecutor.getThreadFactory();
threadPoolExecutor.setThreadFactory(new DelegatingCountFactory(oldThreadFactory, threadsContainer));
}
}
private static class DelegatingCountFactory implements ThreadFactory {
private final ThreadFactory delegate;
private final Set threadsContainer;
private DelegatingCountFactory(ThreadFactory delegate, Set threadsContainer) {
this.delegate = delegate;
this.threadsContainer = threadsContainer;
}
@Override
public Thread newThread(Runnable r) {
Thread thread = delegate.newThread(r);
// cache thread
threadsContainer.add(thread);
return thread;
}
}
}
一旦我们拥有了一个ExecutorService对象,我们只需要调用submit()方法,把Callable作为参数传递给它。submit()方法会负责该任务的启动以及返回一个FutureTask对象。这个FutureTask对象是Future接口的一个实现。
不带参数的get方法是阻塞方法,只要线程为返回结果就会一直阻塞直到有结果为止。
get(long,TimeUnit) 是超时等待,若指定时间内还没有得到线程返回值,会抛出TimeoutException的异常
可以使用Future.cancel(boolean) 方法去告诉该executor停止操作并且中断它潜在的线程:
Future future = newSquareCalculator().calculate(4);
booleancanceled = future.cancel(true);
上述例子中的Future实例,不会结束他的运算操作。事实上,如果我们试图在调用cancel()方法之后,调用该实例的get()方法的话,将会产生一个CancelllationException异常。Future.isCancelled()将会告诉我们,是否Future已经被取消了。这对于避免CancellationException异常很有帮助。
在我们调用cancel()方法时,是有可能失败的。在这种情况下,它的返回值将是false.请注意:cancel()方法会接收一个Boolean值作为参数-它会控制正在执行该task的线程是否应该被中断。
volatile 既有可见性又有原子性,可见性是一定的,原子性是看情况的。对象类型和原生类型都是可见性,原生类型是原子性。原生类型例如int 、double等。用volatile修饰long和double可以保证其操作原子性。
是线程安全的
volatile的底层是通过lock前缀指令、内存屏障来实现的。
lock前缀指令其实就相当于一个内存屏障。内存屏障是一组CPU处理指令,用来实现对内存操作的顺序限制。volatile的底层就是通过内存屏障来实现的。
编译器和执行器 可以在保证输出结果一样的情况下对指令重排序,使性能得到优化。插入一个内存屏障,相当于告诉CPU和编译器先于这个命令的必须先执行,后于这个命令的必须后执行。
操作系统有 X86 和 X64,底层是用C语言实现的,在C语言中是没有boolean类型的,用的是0或1来表示boolean类型,0表示false,1表示true,也就是说0表示假,非0表示真。与其说在调用底层的时候将Java中的value(true和false)转换为0或1还不如在一开始记录value的时候,就将value用int型来记录,这样就省去转换的步骤,而且避免了不必要的错误。
synchronized更重。
单线程的时候,synchronized 更快;而多线程的时候则要分情况讨论。cas在竞争激烈的时候速度反而下降。不难想象反复的失败重试。
CAS 操作也是相对重的操作,它也是实现 synchronized 瘦锁(thin lock)的关键,偏向锁就是避免 CAS(Compare And Set/Swap)操作。
JAVA中的CAS操作都是通过sun包下Unsafe类实现,而Unsafe类中的方法都是native方法,由JVM本地实现。Unsafe中对CAS的实现是C++写的,最后调用的是Atomic:comxchg这个方法,这个方法的实现放在hotspot下的os_cpu包中,说明这个方法的实现和操作系统、CPU都有关系。
Linux的X86下主要是通过cmpxchgl这个指令在CPU级完成CAS操作的,但在多处理器情况下必须使用lock指令加锁来完成。