java-线程池、线程组、内存

什么是线程池

创建线程要花费昂贵的资源和时间,如果任务来了才创建线程那么响应时间会变长,而且一个进程能创建的线程数有限。为了避免这些问题,在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程。
从JDK1.5开始,Java API提供了Executor框架可以创建不同的线程池。比如单线程池,每次处理一个任务;数目固定的线程池或者是缓存线程池(一个适合很多生存期短的任务的程序的可扩展线程池)

new Thread的弊端

每次new Thread新建对象性能差。
线程缺乏统一管理,可能无限制新建线程,相互之间竞争及可能占用过多系统资源导致死机或oom。
缺乏更多功能,如定时执行、定期执行、线程中断。

OutOfMemoryError即OOM的可能原因?

  • 数据库的cursor没有及时关闭
  • 未关闭InputStream outputStream
  • Bitmap 使用后未调用recycle()
  • static等关键字
  • 非静态内部类持有外部类的引用context泄露
  • 流量/数据量峰值:应用程序在设计之初均有用户量和数据量的限制,某一时刻,当用户数量或数据量突然达到一个峰值,并且这个峰值已经超过了设计之初预期的阈值,那么以前正常的功能将会停止,并触发java.lang.OutOfMemoryError: Java heap space异常。
  • 内存泄漏:特定的编程错误会导致你的应用程序不停的消耗更多的内存,每次使用有内存泄漏风险的功能就会留下一些不能被回收的对象到堆空间中,随着时间的推移,泄漏的对象会消耗所有的堆空间,最终触发java.lang.OutOfMemoryError: Java heap space错误。

四种线程池的好处

  • 重用存在的线程,减少对象创建、消亡的开销,性能佳
  • 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
  • 提供定时执行、定期执行、单线程、并发数控制等功能。

线程池的工作原理

1、线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则执行第二步。
2、线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里进行等待。如果工作队列满了,则执行第三步
3、线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务
在这里插入图片描述

线程池一般流程

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long
keepAliveTime,TimeUnit unit, BlockingQueue<Runnable> workQueue)
  • corePoolSize线程池的核心大小
  • maximumPoolSize线程池中允许的最大线程数
  • workQueue阻塞任务队列

在这里插入图片描述

// 创建线程池
ExecutorService es = Executors.newCachedThreadPool();
// 提交任务到线程池中
Future f = es.submit(() -> {
int sum = 0;
for (int i = 0; i <= 100; i++)
sum += i;
return sum;
});
Object res = f.get();
System.out.println(res);

常见问题总结

一般创建线程对象时不建议使用extends Thread方法? **单根继承

一般没有返回结果时建议使用Runnable接口,如果有返回值一般建议使用Callable接口

如果使用线程比较频繁建议使用线程池

daemon线程

正常情况下,非守护线程的执行时间和主线程无关,即使主线程已经结束,也不会影响子线程的运行。
守护线程是为其它线程提供服务的线程

  • 守护进程的特点是当程序中主线程结束时,守护线程会自动中止如何将一个子线程设置为守护线程
  • 在一个线程调用start启动之前,调用方法thread.setDaemon(true);就可以将thread线程设置为守护线程.守护线程一般应该是一个独立的线程,它的run()方法是一个无限循环
  • 守护线程与其它线程的区别是,如果守护线程是唯一运行着的线程,程序会自动退出
System.out.println("开始程序.......");
Thread t=new Thread(){
public void run(){
while(true)
System.out.println("dddd.....");
}
};
t.setDaemon(true);
t.start();
System.out.println("结束程序......");

线程组

Thread线程类中toString方法的定义

public String toString() {
ThreadGroup group = getThreadGroup();//获取当前线程对象所属的线程组
if (group != null) {
return "Thread[" + getName() + "," + getPriority() + "," + group.getName() + "]";
} else {
return "Thread[" + getName() + "," + getPriority() + "," + "" + "]";
}
}

所有线程都隶属于一个线程组。那可以是一个默认线程组【main】,亦可是一个创建线程时明确指定的组说明

  • 在创建之初,线程被限制到一个组里,而且不能改变到一个不同的组
  • 若创建多个线程而不指定一个组,它们就会与创建它的线程属于同一个组
    public Thread(ThreadGroup group, Runnable target)

public Thread(ThreadGroup group, String name)

public Thread(ThreadGroup group, Runnable target, String name)

ThreadGroup myThreadGroup = new ThreadGroup("My Group");//创建线程组
Thread myThread = new Thread(myThreadGroup,"a thread"); //定义线程时指定对应的线程组
theGroup = myThread.getThreadGroup();//获取线程对象所属的线程组
ThreadGroup group=new ThreadGroup("yan1");
Thread t1=new Thread(group,
()->{
for(int i=0;i<100;i++)
System.err.println(Thread.currentThread());
}
);
t1.start();
System.out.println(Thread.currentThread());
//线程组的用途
ThreadGroup tg=Thread.currentThread().getThreadGroup();
int cc=tg.activeCount();//获取所有活跃线程线程数
Thread[] list=new Thread[cc];
tg.enumerate(list);//获取所有获取线程,并存放在Thread[]中
for(Thread temp:list)
System.out.println(temp);
tg.list();//以树装结构显示当前的所有线程组

主要通途:可以通过线程组,对线程组中一组线程进行统一管理,而不是一个一个的管理。注意显示线程时,如果
线程显示为null表示线程已经消亡(执行结束)。在具体开发中很少使用

多线程编程细节

Thread类

Thread类实现了Runnable接口,所以Thread对象也是可运行Runnable对象,同时Thread类也是线程类
构造器

Thread()//一般用于在Thread类中覆盖定义run方法,可以使用匿名内部类进行定义
Thread(Runnable)//使用最多的情况,run方式是由Runnable参数对象提供
Thread(String name) //自定义线程名称
Thread(Runnable,String name)
… …
//常见简化写法
Thread t = new Thread(()->{
System.out.println(Thread.currentThread());
});
t.start();

常见方法:
在这里插入图片描述

Runnable接口

Runnable接口只定义了一个方法public void run(),这个方法要求实现Runnable接口的类实现,Runnable对象称为可运行对象,一个线程的运行就是执行该对象的run()方法
run()方法没有返回值void,而且不能抛出异常

class MyRunnable implements Runnable{
@Override
public void run()throws Exception {//语法报错,这里不允许抛出异常,如果其中有异常则需要使用
try.catch处理
//没有返回值,如果需要记录处理结果,需要自己编程处理
}
}
//简化写法
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("左手画一条龙...");
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();

Callable接口

继承Thread或实现Runnable接口这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。

如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。

call()方法有返回值,这个返回值可以通过泛型进行约束,允许抛出异常

class MyRunnable implements Callable<Number> {
// <>中用于指定返回值类型,必须使用引用类型,不能使用简单类型
public Number call() throws Exception {//允许抛出异常
return null;
}
}
//简化写法
new Thread(new FutureTask<>(()->{
for(int i=0;i<10;i++){
System.out.println("右手画彩虹");
Thread.sleep(30);//因为call方法允许抛出异常
}
return null;
})).start();

Future接口

Future表示一个任务的生命周期,并提供了方法来判断是否已经完成或取消以及获取任务的结果和取消任务等

Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

  • cancel方法用来取消任务,如果取消任务成功则返回true,如果取消任务失败则返回false。参数
    mayInterruptIfRunning表示是否允许取消正在执行却没有执行完毕的任务,如果设置true,则表示可以取消
    正在执行过程中的任务。如果任务已经完成,则无论mayInterruptIfRunning为true还是false,此方法肯定返
    回false,即如果取消已经完成的任务会返回false;如果任务正在执行,若mayInterruptIfRunning设置为
    true,则返回true,若mayInterruptIfRunning设置为false,则返回false;如果任务还没有执行,则无论
    mayInterruptIfRunning为true还是false,肯定返回true
  • isCancelled方法表示任务是否被取消成功,如果在任务正常完成前被取消成功,则返回 true
  • isDone方法表示任务是否已经完成,若任务完成,则返回true
  • get()方法用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回
  • get(long timeout, TimeUnit unit)用来获取执行结果,如果在指定时间内,还没获取到结果,就抛出TimeoutException超时异常。
Future f = new FutureTask(() -> {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread() + "...start..." + i);
Thread.sleep(10);
System.out.println(Thread.currentThread() + "...end..." + i);
}
return null;
});
if (f instanceof Runnable)
new Thread((Runnable) f).start();
int counter=0;
while (true) {
Thread.sleep(20);
System.out.println("任务是否被取消:" + f.isCancelled()+"--"+counter);
System.out.println("任务是否执行完毕:" + f.isDone());
counter++;
if(counter>10)
f.cancel(true);//取消任务的执行
if(counter>12)
break;
}
Future f = new FutureTask(() -> {
int res=0;
for (int i = 0; i < 1000; i++) {
Thread.sleep(10);
res+=i;
}
return res;
});
if (f instanceof Runnable){
new Thread((Runnable) f).start();
int counter=0;
long start=System.currentTimeMillis();
// Object obj=f.get();
Object obj=f.get(5,TimeUnit.SECONDS);//参数1为超时时长,参数2为时长的单位,是一个枚举类型
数据,超时TimeoutException
long end=System.currentTimeMillis();
System.out.println("get...执行时间为:"+(end-start)+"ms");
System.out.println("线程执行结果为:"+obj);
}

FutureTask

源代码

public class FutureTask<V> implements RunnableFuture<V>

具体使用

public class FutureTask<V> implements RunnableFuture<V>

FutureTask实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

FutureTask是一个可取消的异步计算,FutureTask 实现了Future的基本方法,提供start cancel 操作,可以查询计算是否已经完成,并且可以获取计算的结果。结果只可以在计算完成之后获取,get方法会阻塞当计算没有完成的时候,一旦计算已经完成, 那么计算就不能再次启动或是取消。

一个FutureTask 可以用来包装一个 Callable 或是一个Runnable对象。因为FurtureTask实现了Runnable方法,所以一个 FutureTask可以提交(submit)给一个Excutor执行(excution). 它同时实现了Callable, 所以也可以作为Future得到Callable的返回值。

ThreadPoolExecutor

先有一个大概了解,在集合框架后深入认识

ThreadPoolExecutor是线程池框架的一个核心类,线程池通过线程复用机制,并对线程进行统一管理

  • 降低系统资源消耗。通过复用已存在的线程,降低线程创建和销毁造成的消耗;
  • 提高响应速度。当有任务到达时,无需等待新线程的创建便能立即执行;
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗大量系统资源,还会降低系统的稳定性,使用线程池可以进行对线程进行统一的分配、调优和监控。

线程池的运行状态总共有5种,其值和含义分别如下:

  • RUNNING: 高3位为111,接受新任务并处理阻塞队列中的任务
  • SHUTDOWN: 高3位为000,不接受新任务但会处理阻塞队列中的任务
  • STOP:高3位为001,不会接受新任务,也不会处理阻塞队列中的任务,并且中断正在运行的任务
  • TIDYING: 高3位为010,所有任务都已终止,工作线程数量为0,线程池将转化到TIDYING状态,即将要执行terminated()结束钩子方法
  • TERMINATED: 高3位为011,terminated()方法已经执行结束

构造器中各个参数的含义:

1.corePoolSize

线程池中的核心线程数。当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。

2.maximumPoolSize

线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize。

3.keepAliveTime

线程空闲时的存活时间。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,keepAliveTime参数也会起作用,直到线程池中的线程数为0。

4.unit

keepAliveTime参数的时间单位。

5.workQueue

任务缓存队列,用来存放等待执行的任务。如果当前线程数为corePoolSize,继续提交的任务就会被保存到任务缓存队列中,等待被执行。

一般来说,这里的BlockingQueue有以下三种选择:

  • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则
    插入操作一直处于阻塞状态。因此,如果线程池中始终没有空闲线程(任务提交的平均速度快于被处理的速度),可能出现无限制的线程增长。
  • LinkedBlockingQueue:基于链表结构的阻塞队列,如果不设置初始化容量,其容量Integer.MAX_VALUE,
    即为无界队列。因此,如果线程池中线程数达到了corePoolSize,且始终没有空闲线程(任务提交的平均速度快于被处理的速度),任务缓存队列可能出现无限制的增长。
  • ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务。
6.threadFactory

线程工厂,创建新线程时使用的线程工厂。

7.handler

任务拒绝策略,当阻塞队列满了,且线程池中的线程数达到maximumPoolSize,如果继续提交任务,就会采取任
务拒绝策略处理该任务,线程池提供了4种任务拒绝策略:

  • AbortPolicy:丢弃任务并抛出- - -RejectedExecutionException异常,默认策略;
  • CallerRunsPolicy:由调用execute方法的线程执行该任务;
  • DiscardPolicy:丢弃任务,但是不抛出异常;
  • DiscardOldestPolicy:丢弃阻塞队列最前面的任务,然后重新尝试执行任务(重复此过程)。、

当然也可以根据应用场景实现- RejectedExecutionHandler接口自定义饱和策略,如记录日志或持久化存储不能处
理的任务。

Executors创建线程池

  • newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收重用时则新建线程
    ----- 用来创建一个可以无限扩大的线程池,适用于服务器负载较轻,执行很多短期异步任务
  • newFixedThreadPool 创建一个固定大小的定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
    ----- 因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于可以预测线程数量的业务中,或者服务器负载较重,对当前线程数量进行限制
  • newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行
    ----- 可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements
ScheduledExecutorService
  • newSingleThreadExecutor创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
    ----- 适用于需要保证顺序执行各个任务,并且在任意时间点,不会有多个线程是活动的场景
  • newWorkStealingPool:创建一个拥有多个任务队列的线程池,可以减少连接数
    ----- 创建当前可用cpu数量的线程来并行执行,适用于大耗时的操作,可以并行来执行

提交任务的方式

线程池框架提供了两种方式提交任务,submit()和execute(),通过submit()方法提交的任务可以返回任务执行的结果,通过execute()方法提交的任务不能获取任务执行的结果。

关闭线程池

shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表

shutdown:当调用shutdown后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务

ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(()->{
for(int i=0;i<10;i++){
System.out.println("Hello "+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("main......");
ExecutorService es = Executors.newFixedThreadPool(2);
Future f = es.submit(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("Hello " + i);
Thread.sleep(200); }
return 100;
});
Object obj=f.get(); 阻塞当前main线程
System.out.println("main......");

Java内存模型

Java内存模型定义了一种多线程访问Java内存的规范。

  • Java内存模型将内存分为了主内存和工作内存。类的状态也就是类之间共享的变量,是存储在主内存中的,每次Java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在自己的工作内存中有一份拷贝,运行自己线程代码的时候,用到这些变量,操作的都是自己工作内存中的那一份。在线程代码执行完毕之后,会将最新的值更新到主内存中去
  • 定义了几个原子操作,用于操作主内存和工作内存中的变量
  • 定义了volatile变量的使用规则
  • happens-before即先行发生原则,定义了操作A必然先行发生于操作B的一些规则,比如在同一个线程内控制流前面的代码一定先行发生于控制流后面的代码、一个释放锁unlock的动作一定先行发生于后面对于同一个锁进行锁定lock的动作等等,只要符合这些规则,则不需要额外做同步措施,如果某段代码不符合所有的happens-before规则,则这段代码一定是线程非安全的

硬件模型

在这里插入图片描述
线程读取主内存的数据到CPU缓冲中,当数据放在不同位置时,会有两个问题:可见性与静态条件
在这里插入图片描述
多个线程之间是可以使用PipedInputStream/PipedOutputSteam互相传递数据通信的,它们之间的沟通只能通过共享变量来进行

  • Java内存模型JMM规定了JVM有主内存,主内存是多个线程共享的
  • 当new一个对象时,也是被分配在主内存中,每个线程都有自己的工作内存,工作内存存储了主存的某些对象的副本,当然线程的工作内存大小是有限制的
public class T1 {
private int num;
public static void main(String[] args) {
T1 t=new T1();
t.pp();
}
public void pp(){
//通过对num的操作实现了t1和t2之间的通信,这里目前不保证输出的正确性.可以通过同步锁
synchronized保证数据的正确性
Thread t1=new Thread(()->{
for(int i=0;i<100;i++)add();
});
new Thread(()->{
for(int i=0;i<100;i++)sub();
}).start();
t1.start();
}
public void add(){
num++;
System.out.println(Thread.currentThread()+"加法:"+num);
}
public void sub(){
num--;
System.out.println(Thread.currentThread()+"减法:"+num);
}
}

Java中堆和栈有什么不同

每个线程都有自己的栈内存(栈帧),用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。

而堆是所有线程共享的一片公用内存区域

JDK1.6+引入了逃逸分析,对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值

如何在Java中获取线程堆栈

对于不同的操作系统,有多种方法来获得Java进程的线程堆栈。当获取线程堆栈时,JVM会把所有线程的状态存到日志文件或者输出到控制台。在Windows可以使用Ctrl + Break组合键来获取线程堆栈,Linux下用kill -3命令。也可以用jstack这个工具来获取,它对线程id进行操作,可以用jps这个工具找到id。

通过使用 jps 检查当前正在运行的JAVA进程的 PID。jps –lvm

使用明确的 PID 作为 jstack 的参数来获取 thread dumps。jstack -f 5824

一般用于死锁的分析和线程执行速度很慢时的分析

JVM中哪个参数是用来控制线程的栈堆栈小的

-Xss参数用来控制线程的堆栈大小

线程操作某个对象的执行顺序

  • 从主存赋值变量到当前工作内存read and load
  • 执行代码,改变共享变量值use and assign
  • 用工作内存数据刷新主存相关内容store and write
    在这里插入图片描述

volatile关键字

volatile是java提供的一种同步手段,只不过它是轻量级的同步,为什么这么说,因为volatile只能保证多线程的内存可见性,不能保证多线程的执行原子性。而最彻底的同步要保证有序性、可见性和原子性的synchronized

任何被volatile修饰的变量,都不拷贝副本到工作内存,任何修改都及时写在主存。因此对于volatile修饰的变量的修改,所有线程马上就能看到,但是volatile不能保证对变量的修改是原子的

public class VolatileTest{
public volatile int a;
public void add(int count){
a=a+count; }
}

volatile存在的意义是,任何线程对a的修改,都会马上被其他线程读取到,因为直接操作主存,没有线程对工作内存和主存的同步。所以,volatile的使用场景是有限的,在有限的一些情形下可以使用 volatile 变量替代锁

要使 volatile 变量提供理想的线程安全,必须同时满足两个条件

  • 对变量的写操作不依赖于当前值
  • 该变量没有包含在具有其他变量的不变式中

volatile特性

  • 保证可见性:当写一个 volatile 变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存,使其他线程立即可见
  • 保证有序性:当变量被修饰为 volatile 时,JMM 会禁止读写该变量前后语句的大部分重排序优化,以保证变
    量赋值操作的顺序与程序中的执行顺序一致
  • 部分原子性:对任意单个 volatile 变量的读/写具- 有原子性,但类似于 volatile++ 这种复合操作不具有原子性

volatile的认识

public class Test1 {
private static boolean flag=false;
private static int i=0;
public static void main(String[] args) {
new Thread(()->{
try {
TimeUnit.MILLISECONDS.sleep(100);
flag=true;
System.out.println("flag changed...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
while(!flag){
i++;
}
System.out.println("progress end...");
}
}

程序不能执行结束,会进入死循环状态。
解决方案:ag上添加关键字volatile

总结

Java内存模型规定和指引Java程序在不同的内存架构、CPU和操作系统间有确定性地行为。它在多线程的情况下尤其重要。Java内存模型对一个线程所做的变动能被其它线程可见提供了保证,它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。

1、线程内的代码能够按先后顺序执行,这被称为程序次序规则。
2、对于同一个锁,一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前,也叫做管程锁定规则。
3、前一个对volatile的写操作在后一个volatile的读操作之前,也叫volatile变量规则。
4、一个线程内的任何操作必需在这个线程的start()调用之后,也叫作线程启动规则。
5、一个线程的所有操作都会在线程终止之前,线程终止规则。
6、一个对象的终结操作必需在这个对象构造完成之后,也叫对象终结规则。
7、可传递性。如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论

基础练习题

1. 多线程使用的优缺点?

优点
1)多线程技术使程序的响应速度更快
2)当前没有进行处理的任务可以将处理器时间让给其它任务
3)占用大量处理时间的任务可以定期将处理器时间让给其它任务
4)可以随时停止任务
5)可以分别设置各个任务的优先级以及优化性能
缺点:
1)等候使用共享资源时造成程序的运行速度变慢 2)对线程进行管理要求额外的cpu开销
3)可能出现线程死锁情况。即较长时间的等待或资源竞争以及死锁等症状。

2. start()方法和run()方法简介和区别?

start()方法:
1)用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完
毕而直接继续执行下面的代码。
2)通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪
(可运行)状态,并没有运行,一旦得到CPU时间片,就开始执行run()方法。
run()方法:
1)run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条。

总结:
1)调用start方法方可启动线程,
2)而run方法只是thread的一个普通方法调用,还是在主线程里执行。
3)把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用run()方法,这是由jvm的内存机制规定的。
4)并且run()方法必须是public访问权限,返回值类型为void.。

3. Runnable接口和Callable接口的相同点和不同点?

相同点: 都是接口,都可以用于Executors 不同点:

  • Callable要实现call方法,Runnable是run方法
  • call方法可以有返回值,run方法没有返回值
  • call方法可以抛出异常,run方法不能
  • Runnable属于老接口,从1.1就有了;Callable是JDK1.5才引入的
4、volatile关键字的作用是什么?

1)多线程使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,一定是最新的数据

2)Java代码执行中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,当然这也一定程度上降低了代码执行效率

5、FutureTask是什么?

FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值