线程与进程的区别java
1. 进程是资源分配的最小单元,线程是CPU调度的最小单元。全部与进程相关的资源,均被记录再PCB中。程序员
2. 线程隶属于某一个进程,共享全部进程的资源。线程只由堆栈寄存器、程序计数器和TCB构成。算法
3. 进程能够看做独立的应用,线程不能看做独立的应用。编程
4. 进程有独立的地址空间,相互不影响,而线程只是进程的不一样执行路径,若是线程挂了,进程也就挂了。因此多进程的程序比多线程程序健壮,可是切换消耗资源多。缓存
Java中进程与线程的关系多线程
1. 运行一个程序会产生一个进程,进程至少包含一个线程。app
2. 每一个进程对应一个JVM实例,多线线程共享JVM中的堆。框架
3. Java采用单线程编程模型,程序会自动建立主线程。ide
4. 主线程能够建立子线程,原则上要后于子线程完成执行。函数
线程中start方法和run方法的区别
Java中建立线程的方式有两种,无论使用继承Thread的方法是hiRunnable接口的方法,都须要重写run方法。调用start方法会建立一个新的线程并启动,run方法只是启动线程后的回调函数,若是调用run方法,那么执行run方法的线程不会是新建立的线程,而若是使用start方法,那么执行run方法的线程就是咱们刚刚启动的那个线程。
public class Main { public static void main(String[] args) { Thread thread = new Thread(new SubThread()); thread.run(); thread.start(); } } class SubThread implements Runnable{ @Override public void run() { // TODO Auto-generated method stub System.out.println("执行本方法的线程:"+Thread.currentThread().getName()); } }
运行结果:
执行本方法的线程:main
执行本方法的线程:Thread-0
Thread和Runnable的关系
区别:
Thread是一个类,而Runnable是一个接口,Runnable接口中只有一个没有实现的run方法,因此Runnable并不能独立开启一个线程,而是依赖Thread类去建立线程,执行本身的run方法,去执行相应的业务逻辑,才能让这个类具备多线程的特性。
使用继承Thread类方法建立子线程
public class Main extends Thread{ public static void main(String[] args) { Main main = new Main(); main.start(); } @Override public void run() { System.out.println("经过继承Thread接口方式建立子线程成功,当前线程名:"+Thread.currentThread().getName()); } }
运行结果:
经过继承Thread接口方式建立子线程成功,当前线程名:Thread-0
使用实现Runnable接口方法建立子线程
public class Main{ public static void main(String[] args) { SubThread subThread = new SubThread(); Thread thread = new Thread(subThread); thread.start(); } } class SubThread implements Runnable{ @Override public void run() { // TODO Auto-generated method stub System.out.println("经过实现Runnable接口建立子线程成功,当前线程名:"+Thread.currentThread().getName()); } }
运行结果:
经过实现Runnable接口建立子线程成功,当前线程名:Thread-0
使用匿名内部类方法建立子线程
public class Main{ public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub System.out.println("使用匿名内部类方式建立线程成功,当前线程名:"+Thread.currentThread().getName()); } }); thread.start(); } }
运行结果:
使用匿名内部类方法建立线程成功,当前线程名:Thread-0
Thread和Runnable关系
1. Thread是实现了Runnable接口的类,使得run支持多线程。
2. 因类的单一继承原则,推荐使用Runnable接口,可使程序更加灵活。
如何实现处理多线程的返回值
1. 经过让主线程等待,直到子线程运行完毕为止。
public class Main{ static String str; public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { str="子线程执行完毕"; } }); thread.start(); //若是子线程还未对str进行赋值,则一直轮转 while(str==null) {} System.out.println(str); } }
2. 使用Thread中的join()方法
//join()方法能够阻塞当前线程以等待子线程处理完毕。
public class Main{ static String str; public static void main(String[] args) { Thread thread = new Thread(new Runnable() { @Override public void run() { str="子线程执行完毕"; } }); thread.start(); //若是子线程还未对str进行赋值,则一直轮转 try { thread.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(str); } }
join()方法能作到比主线程等待法更精准的控制,可是join方法的控制力度并不够细。好比,咱们须要控制子线程将字符串赋一个特定的值时,再执行主线程,这种操做join方法是没办法作到的。
3. 经过Callable接口实现:经过FutureTask或者线程池获取
在JDK1.5以前,线程是没有返回值的,一般程序员须要获取子线程返回值颇费周折,如今Java有了一个的返回值线程,即实现了Callable接口的线程,执行了实现Callable接口的线程以后,能够得到一个Future对象,在该对象上调用一个get方法,就能够执行子线程的逻辑并获取返回的Object。
public class Main implements Callable<String>{ @Override public String call() throws Exception { String str = "我是带返回值的子线程"; return str; } public static void main(String[] args) { Main main = new Main(); try { String str = main.call(); System.out.println(str); } catch (Exception e) { e.printStackTrace(); } } }
运行结果:
我是带返回值的子线程
使用FutureTask
public class Main implements Callable<String>{ @Override public String call() throws Exception { String str = "我是带返回值的子线程"; return str; } public static void main(String[] args) { FutureTask<String> task = new FutureTask<String>(new Main()); new Thread(task).start(); try { if(!task.isDone()) { System.out.println("任务没有执行完成"); } System.out.println("等待中..."); Thread.sleep(3000); System.out.println(task.get()); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } }
运行结果:
任务没有执行完成
等待中...
我是带返回值的子线程
使用线程池配合Future获取
public class Main implements Callable<String>{ @Override public String call() throws Exception { String str = "我是带返回值的子线程"; return str; } public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService newCacheThreadPool = Executors.newCachedThreadPool(); Future<String> future = newCacheThreadPool.submit(new Main()); if(!future.isDone()) { System.out.println("线程还没有执行结束"); } System.out.println("等待中"); Thread.sleep(300); System.out.println(future.get()); newCacheThreadPool.shutdown(); } }
运行结果:
线程还没有执行结束
等待中
我是带返回值的子线程
线程的状态
Java线程主要分为如下六个状态:新建态(new)、运行态(Runnable)、无限期等待(Waiting)、限期等待(TimeWaiting)、阻塞态(Blocked)、结束(Terminated)
新建(new)
新建态是线程处于已被建立但没有被启动的状态,在该状态下的线程只是被建立出来了,但并无开始执行其内部逻辑。
运行(Runnable)
运行态分为Ready和Running,当线程调用start方法后,并不会当即执行,而是去争夺CPU,当线程没有开始执行时,其状态就是Ready,而当线程获取CPU时间片后,从Ready
态转为Running态。
等待(Waiting)
处于等待状态的线程不会自动苏醒,而只有等待被其余线程唤醒,在等待状态中该线程不会被CPU分配时间,将一直被阻塞。如下操做会形成线程的等待:
1. 没有设置timeout参数的Object.wait()方法。
2. 没有设置timeout参数的Thread.join()方法。
3. LockSupport.park()方法(实际上park方法并非LockSupport提供的,而是在Unsafe中,LockSupport只是对其作了一层封装)
限期等待(TimeWaiting)
处于限期等待的线程,CPU一样不会分配时间片,但存在于限期等待的线程无需被其余线程显式唤醒,而是在等待时间结束后,系统自动唤醒。如下操做会形成线程限期等待:
1. Thread.sleep() 方法
2. 设置了timeout参数的Object.wait()方法
3. 设置了timeout参数的Thread.join()方法
4. LockSupport.parkNanos()方法
5. LockSupport.parkUntil()方法
阻塞(Blocked)
当多个线程进入同一块共享区域时,例如Synchronized块,ReentrantLock控制的区域等,会去争夺锁,成功获取锁的线程继续往下执行,而没有获取锁的线程将进入阻塞状态,等待获取锁。
结束(Terminated)
已终止线程的线程状态,执行已结束执行。
Sleep和Wait的区别
1. sleep方法由Thread提供,而wait方法由Object提供。
2. sleep方法能够在任何地方使用,而wait方法只能在synchronized块或synchronized方法中使用(由于必须获wait方法会释放锁,只有获取锁了才能释放锁)。
3. sleep方法指挥让出CPU,不会释放锁,而wait方法不只会让出CPU,还会释放锁。
public class Main{ public static void main(String[] args) { Thread threadA = new Thread(new ThreadA()); Thread threadB = new Thread(new ThreadB()); threadA.setName("threadA"); threadB.setName("threadB"); threadA.start(); threadB.start(); } public static synchronized void print() { System.out.println("当前线程:"+Thread.currentThread().getName()+"执行Sleep"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("当前线程:"+Thread.currentThread().getName()+"执行Wait"); try { Main.class.wait(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("当前线程:"+Thread.currentThread().getName()+"执行完毕"); } } class ThreadA implements Runnable{ @Override public void run() { // TODO Auto-generated method stub Main.print(); } } class ThreadB implements Runnable{ @Override public void run() { // TODO Auto-generated method stub Main.print(); } }
执行结果:
当前线程:threadA执行Sleep
当前线程:threadA执行Wait
当前线程:threadB执行Sleep
当前线程:threadB执行Wait
当前线程:threadA执行完毕
当前线程:threadB执行完毕
从上面的结果能够分析出:当线程A执行sleep后,等待一秒被唤醒后继续持有锁,执行以后的代码,而执行wait以后,当即释放了锁,不只让出了CPU还让出了锁,然后线程B当即持有锁开始执行,和线程A执行了一样的步骤,当线程B执行了wait方法以后,释放锁,而后线程A拿到了锁打印了第一个执行完毕,而后线程B打印执行完毕。
notify和notifyAll的区别
notify能够唤醒一个处于等待状态的线程
public class Main{ public static void main(String[] args) { Object lock = new Object(); Thread threadA = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } print(); } } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { print(); lock.notify(); } } }); threadA.setName("threadA"); threadB.setName("threadB"); threadA.start(); threadB.start(); } public static void print() { System.out.println("当前线程:"+Thread.currentThread().getName()+"执行print"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("当前线程:"+Thread.currentThread().getName()+"执行完毕"); } }
运行结果:
当前线程:threadB执行print
当前线程:threadB执行完毕
当前线程:threadA执行print
当前线程:threadA执行完毕
解释:线程A在开始执行时当即调用wait进入无限等待状态,若是没有别的线程来唤醒它,它将一直等待下去,因此此时B持有锁开始执行,而且在执行完毕时调用了notify()方法,该方法能够唤醒wait状态的A线程,因而A线程苏醒,开始执行剩下的代码。
notiftAll能够用于唤醒全部等待的线程,使全部处于等待状态的线程都变为ready状态,并从新争夺锁。
public class Main{ public static void main(String[] args) { Object lock = new Object(); Thread threadA = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { try { lock.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } print(); } } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { synchronized (lock) { print(); lock.notifyAll(); } } }); threadA.setName("threadA"); threadB.setName("threadB"); threadA.start(); threadB.start(); } public static void print() { System.out.println("当前线程:"+Thread.currentThread().getName()+"执行print"); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("当前线程:"+Thread.currentThread().getName()+"执行完毕"); } }
运行结果:
当前线程:threadB执行print
当前线程:threadB执行完毕
当前线程:threadA执行print
当前线程:threadA执行完毕
notify和notifyAll的区别
要说清楚他们的区别,首先要简单的说如下Java synchronized的一些原理,在openjdk中查看java的源码能够看到,Java对象中存在monitor锁,monitor对象中包含锁池和等待池。
锁池:假设有多个对象进入synchronized块争夺锁,而此时已经有一个对象获取到了锁,那么剩下争夺锁的对象将直接进入锁池中。
等待池:假设某个线程调用了对象的wait方法,那么这个线程将直接进入等待池,而等待池中的对象不会去争夺锁,而是等待被唤醒。
notifyAll会让全部处于等待池中的线程所有进入锁池去争夺锁,而notify只会随机让其中一个线程去争夺锁。
yield方法
/** * A hint to the scheduler that the current thread is willing to yield * its current use of a processor. The scheduler is free to ignore this * hint. * * <p> Yield is a heuristic attempt to improve relative progression * between threads that would otherwise over-utilise a CPU. Its use * should be combined with detailed profiling and benchmarking to * ensure that it actually has the desired effect. * * <p> It is rarely appropriate to use this method. It may be useful * for debugging or testing purposes, where it may help to reproduce * bugs due to race conditions. It may also be useful when designing * concurrency control constructs such as the ones in the * {@link java.util.concurrent.locks} package. */ public static native void yield();
yield源码上有一段很长的注释,其大意是:当前线程调用yield方法时,会给当前线程调度器一个暗示,当前线程愿意让出CPU的使用,可是它的做用应结合详细的分析和测试来确保已经达到了预期的效果,由于调度器可能会无视这个暗示,使用这个方法是不那么合适的,或许在测试环境中使用它会比较好。
public class Main{ public static void main(String[] args) { Thread threadA = new Thread(new Runnable() { @Override public void run() { System.out.println("ThreadA正在执行yield"); Thread.yield(); System.out.println("ThreadA执行yield方法完成"); } }); Thread threadB = new Thread(new Runnable() { @Override public void run() { System.out.println("ThreadB正在执行yield"); Thread.yield(); System.out.println("ThreadB执行yield方法完成"); } }); threadA.setName("threadA"); threadB.setName("threadB"); threadA.start(); threadB.start(); }
ThreadA正在执行yield
ThreadB正在执行yield
ThreadA执行yield方法完成
ThreadB执行yield方法完成
ThreadA正在执行yield
ThreadB正在执行yield
ThreadB执行yield方法完成
ThreadA执行yield方法完成
能够看出,存在不一样的测试结果,这里选出两张。
第一种结果:线程A执行玩yield方法,让出CPU给线程B执行。而后两个线程继续执行剩下的代码。
第二种结果:线程A执行玩yield方法,让出CPU给线程B执行,可是线程B执行yield方法后并无让出CPU,而是继续往下执行,此时就是系统无视了这个暗示。
interrupt方法
停止线程
interrupt函数能够中断一个线程,在interrupt以前,一般使用stop方法来终止一个线程,可是stop方法过于暴力,它的特带你是,不论被中断的线程以前处于一个什么样的状态,都无条件中断,这回致使被中断的线程后续的一些清理工做没法顺利完成,引起一些没必要要的一场和隐患,还有可能引起数据不一样步的问题。
温柔的interrupt方法
intterrupt方法的原理与stop方法相比就显得温柔的多,当调用interrupt方法去终止一个线程时,它并不会暴力地强制终止线程,而是通知这个线程应该要被中断了,和yield同样,这也是 一种暗示,至因而否应该中断,由被中断的线程本身去决定。当对一个线程调用interrupt方法时:
1. 若是该线程处于被阻塞状态,则当即退出阻塞状态,抛出InterruptedException异常。
2. 若是该线程处于running状态,则将该线程的中断标志位设置为true,被设置的线程继续执行,不受影响,当运行结束时由线程决定是否被中断。
线程池
线程池的引入是用来解决在平常开发的多线程开发中,若是开发者须要使用到很是多的线程,那么这些线程在被频繁的建立和销毁时,会对系统形成必定的影响,有可能系统在建立和销毁这些线程所消耗的时间会比完成实际需求的时间还要长。
另外,在线程不少的状况下,对线程的管理就造成了一个很大的问题,开发者一般要将注意力从功能上转移到对杂乱无章的线程进行管理上,这项动做其实是很是消耗精力的。
利用Executors建立不一样的线程池知足不一样场景的需求
newFixThreadPool( int nThreads )
指定工做线程数量的线程池
newCachedThreadPool( )
处理大量中断事件工做任务的线程池
1. 试图缓存线程并重用,当无缓存线程可用事,就会建立新的工做线程。
2. 若是线程闲置的事件超过阈值,则会被终止并移出缓存。
3. 系统长时间闲置的时候,不会消耗什么资源。
newSingleThreadExecutor()
建立惟一的工做线程来执行任务,若是线程异常结束,会有另外一个线程取代它。可保证顺利执行任务。
newSingleThreadScheduledExecutor()
定时或周期性工做调度,二者的区别在于前者是单一工做线程,后者是多线程
newWorkStrelingPool()
内部构建ForkJoinPool,利用working-stealing算法,并行地处理任务,不保证处理顺序。
Fork/Join框架:把大任务分割成若干个小任务并执行,最终汇总每个小任务后获得大任务结果的框架。
为何要使用线程池
线程是稀缺资源,若是无限制地建立线程,会消耗系统资源,而线程池能够代替开发者管理线程,一个线程在结束运行后,不会销毁线程,而是将线程归还线程池,由线程池再进行管理,这样就能够对线程进行服用。
因此线程池不但能够下降资源的消耗,还能够提升线程的可管理性。
使用线程池启动线程
public class Main{ public static void main(String[] args) { ExecutorService newFixThreadPool = Executors.newFixedThreadPool(10); newFixThreadPool.execute(new Runnable() { @Override public void run() { // TODO Auto-generated method stub System.out.println("经过线程池启动线程成功"); } }); newFixThreadPool.shutdown(); } }
新任务execute执行后的判断
要知道这个点首先要先说说ThreadPoolExecutor的构造函数,其中有几个参数:
1. corePoolSize:核心线程数量。
2. maximumPoolSize:线程不够用时能建立的最大线程数。
3. workQueue:等待队列。
那么新任务提交后会执行下列判断:
1. 若是运行的线程少于corePoolSize,则建立新线程来处理任务,即便线程池中的其余线程是空闲的阿。
2. 若是线程池中的数量大于等于corePoolSize且小于maximumPoolSize,则只有当workQueue满时,才建立新的线程去处理任务。
3. 若是设置的corePoolSie和maximumPoolSize相同,则建立的线程池大小是固定的,若是此时有新任务提交,若workQueue未满,则放入wordQueue,等待被处理
4. 若是运行的线程数大于等于maximumPoolSize,maximumPoolSize,这时若是workQueue已经满了,则经过handler所指定的策略来处理任务
handler线程池饱和策略
线程池的大小若是选的
固然这个也不能彻底依赖这个公式,更多的是要依赖平时的经验来操做,这个公式也只是仅供参考而已。