进程:在操做系统中可以独立运行,而且做为资源分配的基本单位。它表示运行中的程序。系统运行一个程序就是一个进程从建立、运行到消亡的过程。html
线程:是一个比进程更小的执行单位,可以完成进程中的一个功能,也被称为轻量级进程。一个进程在其执行的过程当中能够产生多个线程。java
【注】线程与进程不一样的是:同类的多个线程共享进程的堆和方法区资源,但每一个线程有本身的程序计数器、虚拟机栈和本地方法栈,因此系统在产生一个线程,或是在各个线程之间做切换工做时,负担要比进程小得多。git
为何程序计数器、虚拟机栈和本地方法栈是线程私有的呢?为何堆和方法区是线程共享的呢?算法
程序计数器为何是私有的?数据库
程序计数器主要有下面两个做用:小程序
- 字节码解释器经过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
- 在多线程的状况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候可以知道该线程上次运行到哪儿了。
(须要注意的是,若是执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。)数组
因此,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。安全
- 虚拟机栈和本地方法栈为何是私有的?
- 虚拟机栈: 每一个 Java 方法在执行的同时会建立一个栈帧用于存储局部变量表、操做数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
- 本地方法栈: 和虚拟机栈所发挥的做用很是类似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。
因此,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。服务器
堆和方法区是全部线程共享的资源,其中:多线程
- 堆是进程中最大的一块内存,主要用于存放新建立的对象 (全部对象都在这里分配内存)。
- 方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
即便单核处理器也支持多线程执行代码,CPU经过给每一个线程分配CPU时间片来实现这个机制。时间片是CPU分配给各个线程的时间,由于时间片很是短,因此CPU经过不停地切换线程执行,让咱们感受多个线程是同时执行的。(时间片通常是几十毫秒)
CPU经过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。可是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,能够再加载这个任务的状态。因此任务从保存到加载的过程就是一次上下文切换。上下文切换会影响多线程的执行速度。
并发指的是多个任务交替进行,并行则是指真正意义上的“同时进行”。
实际上,若是系统内只有一个CPU,使用多线程时,在真实系统环境下不能并行,只能经过切换时间片的方式交替进行,从而并发执行任务。真正的并行只能出如今拥有多个CPU的系统中。
Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不一样状态的其中一个状态:
初始状态、运行状态、阻塞状态、等待状态、超时等待状态、终止状态
线程在生命周期中并非固定处于某一个状态而是随着代码的执行在不一样状态之间切换:
start()
方法后开始运行,线程这时候处于 可运行状态(READY)。wait()
方法以后,线程进入 等待状态(WAITING),进入等待状态的线程须要依靠其余线程的通知才可以返回到运行状态【notify()】。 而 超时等待状态(TIME_WAITING)至关于在等待状态的基础上增长了超时限制,【sleep(long millis)/
wait(long millis)】,
当超时时间到达后 Java 线程将会返回到运行状态。run()
方法以后将会进入到 终止状态(TERMINATED)。多个线程同时被阻塞,它们中的一个或者所有都在等待某个资源被释放。因为线程被无限期地阻塞,所以程序不可能正常终止。
假如线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,因此这两个线程就会互相等待而进入死锁状态。
避免死锁的几个常见方法:
相同点:
二者均可以暂停线程的执行,都会让线程进入等待状态。
不一样点:
new 一个 Thread,线程进入初始状态;调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就能够开始运行了。 start() 会执行线程的相应准备工做,而后自动执行 run() 方法的内容,这是真正的多线程工做。 而直接执行 run() 方法,会把 run 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,因此这并非多线程工做。
总结: 调用 start 方法可启动线程并使线程进入就绪状态,而 run 方法只是 thread 的一个普通方法调用,仍是在主线程里执行。
使用多线程主要会带来如下几个问题:
线程安全问题指的是在某一线程从开始访问到结束访问某一数据期间,该数据被其余的线程所修改,那么对于当前线程而言,该线程就发生了线程安全问题,表现形式为数据的缺失,数据不一致等。
线程安全问题发生的条件:
1)多线程环境下,即存在包括本身在内存在有多个线程。
2)多线程环境下存在共享资源,且多线程操做该共享资源。
3)多个线程必须对该共享资源有非原子性操做。
线程安全问题的解决思路:
1)尽可能不使用共享变量,将没必要要的共享变量变成局部变量来使用。
2)使用synchronized关键字同步代码块,或者使用jdk包中提供的Lock为操做进行加锁。
3)使用ThreadLocal为每个线程创建一个变量的副本,各个线程间独立操做,互不影响。
线程的生命周期开销是很是大的,一个线程的建立到销毁都会占用大量的内存。同时若是不合理的建立了多个线程,cup的处理器数量小于了线程数量,那么将会有不少的线程被闲置,闲置的线程将会占用大量的内存,为垃圾回收带来很大压力,同时cup在分配线程时还会消耗其性能。
解决思路:
利用线程池,模拟一个池,预先建立有限合理个数的线程放入池中,当须要执行任务时从池中取出空闲的先去执行任务,执行完成后将线程归还到池中,这样就减小了线程的频繁建立和销毁,节省内存开销和减少了垃圾回收的压力。同时由于任务到来时自己线程已经存在,减小了建立线程时间,提升了执行效率,并且合理的建立线程池数量还会使各个线程都处于忙碌状态,提升任务执行效率,线程池还提供了拒绝策略,当任务数量到达某一临界区时,线程池将拒绝任务的进入,保持现有任务的顺利执行,减小池的压力。
1)死锁,假如线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,因此这两个线程就会互相等待而进入死锁状态。多个线程环形占用资源也是同样的会产生死锁问题。
解决方法:
想要避免死锁,能够使用无锁函数(cas)或者使用重入锁(ReentrantLock),经过重入锁使线程中断或限时等待能够有效的规避死锁问题。
2)饥饿,饥饿指的是某一线程或多个线程由于某些缘由一直获取不到资源,致使程序一直没法执行。如某一线程优先级过低致使一直分配不到资源,或者是某一线程一直占着某种资源不放,致使该线程没法执行等。
解决方法:
与死锁相比,饥饿现象仍是有可能在一段时间以后恢复执行的。能够设置合适的线程优先级来尽可能避免饥饿的产生。
3)活锁,活锁体现了一种谦让的美德,每一个线程都想把资源让给对方,可是因为机器“智商”不够,可能会产生一直将资源让来让去,致使资源在两个线程间跳动而没法使某一线程真正的到资源并执行,这就是活锁的问题。
阻塞是用来形容多线程的问题,几个线程之间共享临界区资源,那么当一个线程占用了临界区资源后,全部须要使用该资源的线程都须要进入该临界区等待,等待会致使线程挂起,一直不能工做,这种状况就是阻塞,若是某一线程一直都不释放资源,将会致使其余全部等待在这个临界区的线程都不能工做。当咱们使用synchronized或重入锁时,咱们获得的就是阻塞线程,如论是synchronized或者重入锁,都会在试图执行代码前,获得临界区的锁,若是得不到锁,线程将会被挂起等待,知道其余线程执行完成并释放锁且拿到锁为止。
解决方法:
能够经过减小锁持有时间,读写锁分离,减少锁的粒度,锁分离,锁粗化等方式来优化锁的性能。
临界区:
临界区是用来表示一种公共的资源(共享数据),它能够被多个线程使用,可是在每次只能有一个线程可以使用它,当临界区资源正在被一个线程使用时,其余的线程就只能等待当前线程执行完以后才能使用该临界区资源。
好比办公室办公室里有一支笔,它一次只能被一我的使用,假如它正在被甲使用时,其余想要使用这支笔的人只能等甲使用完这支笔以后才能容许另外一我的去使用。这就是临界区的概念。
参考 http://www.javashuo.com/article/p-vczuwcuu-gs.html
synchronized关键字能够保证被它修饰的方法或者代码块在任意时刻只能有一个线程执行。
synchronized关键字最主要的三种使用方式:修饰实例方法:、修饰静态方法、修饰代码块。
当一个线程试图访问同步代码块时,它首先必须获得锁,退出或抛出异常时必须释放锁。
synchronized在JVM里是怎么实现的?
synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执行 monitorenter 指令时,线程试图获取锁也就是获取 monitor的持有权。当计数器为0则能够成功获取,获取后将锁计数器设为1也就是加1。相应的在执行 monitorexit 指令后,将锁计数器设为0,代表锁被释放。若是获取对象锁失败,那当前线程就要阻塞等待,直到锁被另一个线程释放为止。
(monitor对象存在于每一个Java对象的对象头中,synchronized 锁即是经过这种方式获取锁的,也是为何Java中任意对象能够做为锁的缘由)
synchronized用的锁是存在哪里的?
synchronized用到的锁是存在Java对象头里的。
JDK1.6 对锁的实现引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性自旋锁、锁消除、锁粗化等技术来减小锁操做的开销。
锁主要存在四种状态,依次是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态,他们会随着竞争的激烈而逐渐升级。注意锁能够升级不可降级,这种策略是为了提升得到锁和释放锁的效率。
关于这几种优化的详细信息能够查看这篇文章:https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,所以不会致使死锁现象发生;而Lock在发生异常时,若是没有主动经过unLock()去释放锁,则极可能形成死锁现象,所以使用Lock时须要在finally块中释放锁;
3)Lock可让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不可以响应中断;
4)经过Lock能够知道有没有成功获取锁(tryLock()方法:若是获取锁成功,则返回true),而synchronized却没法办到。
5)Lock能够提升多个线程进行读操做的效率。
在性能上来讲,若是竞争资源不激烈,二者的性能是差很少的,而当竞争资源很是激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。因此说,在具体使用时要根据适当状况选择。
参考https://blog.csdn.net/qq_38200548/article/details/82943222
保证共享变量的“可见性”。可见性的意思是当一个线程修改一个共享变量时,另一个线程能读到这个修改的值。
把变量声明为volatile,这就指示 JVM每次使用它都到主存中进行读取。
能够建立(Executors.newXXX)3种类型的ThreadPoolExecutor:FixedThreadPool、SingleThreadExecutor、CachedThreadPool。
有4种方式:继承Thread类、实现Runnable接口、实现Callable接口、使用Executor框架来建立线程池。
public class MyThread extends Thread {//继承Thread类
//重写run方法
public void run(){
}
}
----------------------------------------------------------------------------------
public class Main {
public static void main(String[] args){
new MyThread().start(); //建立并启动线程
}
}
public class MyThread2 implements Runnable {//实现Runnable接口
//重写run方法
public void run(){
}
}
------------------------------------------------------------------------------------------
public class Main {
public static void main(String[] args){
//建立并启动线程
MyThread2 myThread=new MyThread2();
Thread thread=new Thread(myThread);
thread().start();
//或者 new Thread(new MyThread2()).start();
}
}
无论是继承Thread仍是实现Runnable接口,多线程代码都是经过运行Thread的start()方法来运行的。
与实现Runnable接口相似,和Runnable接口不一样的是,Callable接口提供了一个call() 方法做为线程执行体,call()方法比run()方法功能要强大:call()方法能够有返回值、call()方法能够声明抛出异常。
public class Main {
public static void main(String[] args){
MyThread3 th=new MyThread3();
//使用Lambda表达式建立Callable对象
//使用FutureTask类来包装Callable对象
FutureTask<Integer> future=new FutureTask<Integer>(
(Callable<Integer>)()->{
return 5;
}
);
new Thread(task,"有返回值的线程").start();//实质上仍是以Callable对象来建立并启动线程
try{
System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回
}catch(Exception e){
ex.printStackTrace();
}
}
}
经过Executors的以上四个静态工厂方法得到 ExecutorService实例,然后能够执行Runnable任务或Callable任务。
经过Executors的以上四个静态工厂方法得到 ExecutorService实例,然后调用该实例的execute(Runnable command)方法便可。一旦Runnable任务传递到execute()方法,该方法便会自动在一个线程上。
public class TestCachedThreadPool{ public static void main(String[] args){ ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++){ executorService.execute(new TestRunnable()); System.out.println("************* a" + i + " *************"); } executorService.shutdown(); } } class TestRunnable implements Runnable{
//重写run方法 public void run(){ System.out.println(Thread.currentThread().getName() + "线程被调用了。"); }
当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,而且会返回执行结果Future对象。一样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,而且会返回执行结果Future对象,可是在该Future对象上调用get方法,将返回null。
public class CallableDemo{ public static void main(String[] args){ ExecutorService executorService = Executors.newCachedThreadPool(); List<Future<String>> resultList = new ArrayList<Future<String>>(); //建立10个任务并执行 for (int i = 0; i < 10; i++){ //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中 Future<String> future = executorService.submit(new TaskWithResult(i)); //将任务执行结果存储到List中 resultList.add(future); } //遍历任务的结果 for (Future<String> fs : resultList){ try{ while(!fs.isDone);//Future返回若是没有完成,则一直循环等待,直到Future返回完成 System.out.println(fs.get()); //打印各个线程(任务)执行的结果 }catch(InterruptedException e){ e.printStackTrace(); }catch(ExecutionException e){ e.printStackTrace(); }finally{ //启动一次顺序关闭,执行之前提交的任务,但不接受新任务 executorService.shutdown(); } } } } class TaskWithResult implements Callable<String>{ private int id; public TaskWithResult(int id){ this.id = id; } // 重写call()方法 public String call() throws Exception { System.out.println("call()方法被自动调用!!! " + Thread.currentThread().getName()); //该返回结果将被Future的get方法获得 return "call()方法被自动调用,任务返回的结果是:" + id + " " + Thread.currentThread().getName(); } }
Runnable接口或Callable接口实现类均可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor执行。二者的区别在于 Runnable 接口不会返回结果可是 Callable 接口能够返回结果。
1) execute()
方法用于提交不须要返回值的任务,因此没法判断任务是否被线程池执行成功与否;
2) submit()
方法用于提交须要返回值的任务。线程池会返回一个Future类型的对象,经过这个Future对象能够判断任务是否执行成功,而且能够经过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)
方法则会阻塞当前线程一段时间后当即返回,这时候有可能任务没有执行完。
①corePoolSize:线程池的基本大小,当提交一个任务到线程池时,线程池会建立一个线程来执行任务,即便其余空闲的基本线程可以执行新任务也会建立线程,等到须要执行的任务数大于线程池基本大小时就再也不建立。说白了就是,即使是线程池里没有任何任务,也会有corePoolSize个线程在候着等任务。
②maximumPoolSize:最大线程数,无论你提交多少任务,线程池里最多工做线程数就是maximumPoolSize。
③keepAliveTime:线程的存活时间。当线程池里的线程数大于corePoolSize时,若是等了keepAliveTime时长尚未任务可执行,则线程退出。
⑤unit:这个用来指定keepAliveTime的单位,好比秒:TimeUnit.SECONDS。
⑥workQueue:用于保存等待执行任务的阻塞队列,提交的任务将会被放到这个队列里。
⑦threadFactory:线程工厂,用来建立线程。主要是为了给线程起名字,默认工厂的线程名字:pool-1-thread-3。
⑧handler:拒绝策略,即当线程和队列都已经满了的时候,应该采起什么样的策略来处理新提交的任务。默认策略是AbortPolicy(抛出异常),其余的策略还有:CallerRunsPolicy(只用调用者所在线程来运行任务)、DiscardOldestPolicy(丢弃队列里最近的一个任务,并执行当前任务)、DiscardPolicy(不处理,丢弃掉)
任务被提交到线程池,会先判断当前线程数量是否小于corePoolSize,若是小于则建立线程来执行提交的任务,不然将任务放入workQueue队列,若是workQueue满了,则判断当前线程数量是否小于maximumPoolSize,若是小于则建立线程执行任务,不然就会调用handler,以表示线程池拒绝接收任务。