多线程juc,JUC并发编程与源码分析
00-1010进程和线程进程线程同步异步串行并行执行时间创建和运行线程之间的线程关系线程和可运行原理分析查看进程线程运行原理线程上下文切换启动和运行方法睡眠方法中断加入方法中断方法守护进程线程状态Java API级别摘要
目录
00-1010程序由指令和数据组成,但是如果要运行这些指令和读写数据,就必须将指令加载到CPU中,将数据加载到内存中。在指令操作的过程中,还需要磁盘、网络等设备。进程用于加载指令、管理内存和管理IO。当一个程序运行并且这个程序的代码从磁盘加载到内存中时,一个进程就开始了。一个进程可以被看作一个程序的实例。大多数程序可以同时运行多个实例进程(如记事本、绘图、浏览器等。),而有些程序只能启动一个实例进程(如网易云音乐、360安全卫士等)。)
00-1010线程主要负责运行指令,进程主要负责加载指令。
一个进程可以分成一个或多个线程。
线程是一个指令流,它把指令流中的指令按照一定的顺序交给CPU执行。
在Java中,线程是最小的调度单元,进程是最小的资源分配单元。在windows中,进程是不活动的,就像线程的容器一样。
00-1010你需要等待结果返回后才能继续运行,这是同步的。你可以继续运行而不用等待结果返回,这是异步的。
00-1010多核cpu并行执行可以明显提高执行效率。
串行执行时间=每个线程的累加和汇总时间=并行执行时间=最慢线程时间汇总时间注:单核还是一个并发的思路(就是cpu轮流执行线程,微观上还是串行的)。单核多线程可能比单核单线程慢,因为多线程上下文切换反而浪费时间。
00-1010 1.使用线程
public static void main(string[]args){//Create Thread object Thread t=new Thread( Thread 1 ){ public void run(){//要执行的任务log.debug (thread 1已启动);} };//启动线程t . Start();log . debug( test );}2.对线程使用Runnable
public static void main(string[]args){ runnable runnable=new runnable(){ public void run(){//要执行的任务log.debug(线程1已启动);} };//创建一个线程对象Thread t=new Thread(runnable);T.setName(“线程1”);//启动线程t . Start();log . debug( test );} Runnable这里是一个接口。接口中只有一个抽象方法,我们将提供实现。在实现中包含线程的代码就足够了。
@ functional interface public interface Runnable {/* * *当使用实现接口codeRunnable/code的对象*来创建线程时,启动该线程会导致在该单独执行的*线程中调用该对象的* coderun/code方法。* p *方法coderun/code的一般约定是它可以*采取任何行动。* * @see java.lang.Thread#run() */
public abstract void run();}
Thread 与 Runnable 的关系原理分析
方法1原理分析
方法2是使用runnable对象,当成参数传给Thread构造方法,其中又调用了init方法,下面是Thread构造方法的源码
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
继续跟踪查看runnable对象传到哪里去了,可以看到又传给了另一个重载的init,如下
private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); }private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); }
再次跟踪可以看到是把runnable对象传给了一个thread的一个成员变量
//省略部分代码this.target = target;
那么这个成员变量在哪里在使用了呢,经过查找可以发现是在run方法里面,只不过Thread发现有runnable对象就会先采用runnable的run方法。
@Override public void run() { if (target != null) { target.run(); } }
方法2原理分析
通过创建一个子类去重写Thread类的run方法,这样就不会执行父类的run方法。
1.用 Runnable 更容易与线程池等高级 API 配合
2.用 Runnable 让任务类脱离了 Thread 继承体系,更灵活
方法3 FutureTask配合Thread创建线程
Future接口中含有get方法来返回结果的
//省略部分代码V get() throws InterruptedException, ExecutionException;V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
而runnable本身是没有返回结果的,runnable不能将结果传给其他线程。
public interface Runnable { public abstract void run();}
要注意到FutureTask也实现了Runnable接口,也可以传给Thread的有参构造里面。
创建线程的代码
public static void main(String[] args) throws ExecutionException, InterruptedException { // 创建任务对象 FutureTask<Integer> task3 = new FutureTask<>(new Callable<Integer>() { @Override public Integer call() throws Exception { log.debug("线程1被执行了"); return 666; } }); // 参数1 是任务对象; 参数2 是线程名字,推荐 new Thread(task3, "线程1").start(); // 主线程阻塞,同步等待 task 执行完毕的结果 Integer result = task3.get(); log.debug("结果是:{}", result); }
查看进程
任务管理器可以查看进程和线程数,也可以用来杀死进程,也可以在控制台使用tasklist
查看进程taskkill
杀死进程jconsole 远程监控配置来查看
线程运行原理
JVM 中由堆、栈、方法区所组成,其中栈就是给线程使用的。
方法调用时,就会对该方法产生一个栈帧,方法的局部变量都会在栈帧中存储。栈是后进先出,当method2执行完就会回收,在执行完同时会记录返回地址,然后在method1中继续执行。
线程之间的栈帧是相互独立的,之间互不干扰。
线程上下文切换
当上下文切换时,要保存当前的状态,因为可能是时间片用完了,此时线程还没有结束。Java中对应的就是程序计数器
start与run方法
启动一个线程必须要用start方法,如果直接调用类里面的run方法实际走的是main主线程。
线程start前getState()
得到的是NEW
线程start后getState()
得到的是RUNNABLE
public static void main(String[] args) { Thread t1 = new Thread("t1") { @Override public void run() { log.debug("t1被启动"); } }; System.out.println(t1.getState()); t1.start(); System.out.println(t1.getState()); }
sleep方法
在sleep期间调用getState()
方法可以得到TIMED_WAITING
public static void main(String[] args) { Thread t1 = new Thread("线程1") { @Override public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }; t1.start(); log.debug("线程1 state: {}", t1.getState()); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } log.debug("线程1 state: {}", t1.getState()); }
sleep打断
sleep可以使用interrupt
方法打断,打断后会触发InterruptedException
异常
public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread("t1") { @Override public void run() { log.debug("进入睡眠"); try { Thread.sleep(2000); } catch (InterruptedException e) { log.debug("被唤醒"); e.printStackTrace(); } } }; t1.start(); Thread.sleep(1000); log.debug("打断"); t1.interrupt(); }
sleep防止cpu使用100%
在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield或 sleep 来让出cpu的使用权给其他程序
yield方法会把cpu的使用权让出去,然后调度执行其它线程。线程的调度最终还是依赖的操作系统的调度器。
join方法
该方法会等待线程的结束
static int r = 11; public static void main(String[] args) throws InterruptedException { test1(); } private static void test1() throws InterruptedException { log.debug("主线程开始"); Thread t1 = new Thread(() -> { sleep(1); r = 888; },"线程1"); t1.start();// t1.join(); log.debug(String.valueOf(r)); log.debug("主线程线程结束"); }
join没有使用时,返回的是11,若是使用join返回的是888,是主线程在同步等待线程1。
当然还有其他的方法等待
1.sleep方法等待线程1结束
2.利用FutureTask的get方法
join(long n)方法可以传入参数,等待线程运行结束,最多等待 n 毫秒,假如执行时间大于等待的时间,就会不再等待。那么该线程会直接结束吗?答案是不会。
如下代码
public class Test10 { static int r = 11; public static void main(String[] args) throws InterruptedException { test1(); } private static void test1() throws InterruptedException { log.debug("主线程开始"); Thread t1 = new Thread(() -> { sleep(2); r = 888; log.debug("线程1结束"); },"线程1"); t1.start(); t1.join(1000); log.debug(String.valueOf(r)); log.debug("主线程线程结束"); }}
输出结果,可以看到这里只是主线程不再等待。
16:28:53.360 c.Test10 [main] - 主线程开始16:28:54.411 c.Test10 [main] - 1116:28:54.411 c.Test10 [main] - 主线程线程结束16:28:55.404 c.Test10 [线程1] - 线程1结束
interrupt 方法
interrupt可以用来打断处于阻塞状态的线程。在打断后,会有一个打断标记(布尔值)会提示是否被打断过,被打断过标记为true
否则为false
.但是sleep、wait和join可以来清空打断标记。
代码如下
public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { log.debug("sleep..."); try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } },"t1"); t1.start(); Thread.sleep(1000); log.debug("interrupt"); t1.interrupt(); log.debug("打断标记:{}", t1.isInterrupted()); }
线程被打断后并不会结束运行,有人就会问了,那我们如何在打断线程后关闭线程呢?答案就是利用打断标记去实现。
可以在线程的死循环之中加入一个判断去实现。
boolean interrupted = Thread.currentThread().isInterrupted(); if(interrupted) { log.debug("退出循环"); break; }
守护进程
Java 进程通常需要所有线程都运行结束,才会结束。
但是存在一种守护进程,只要其他非守护进程结束,守护进程就会结束。垃圾回收器就使用的守护进程。
线程的状态
操作系统层面(早期进程的状态)
初始状态 在语言层面创建了线程对象,还未与操作系统线程关联可运行状态(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行任务。运行状态 获取了 CPU 时间片运行中的状态调用阻塞api使运行状态转为阻塞状态终止状态 表示线程已经执行完毕
Java API 层面
1、新建状态(New)
Thread t1 = new Thread("t1") { @Override public void run() { log.debug("running..."); } };log.debug("t1 state {}", t1.getState());
2、就绪状态(Runnable)与运行状态(Running)
Thread t2 = new Thread("t2") { @Override public void run() { while(true) { // runnable } } };t2.start();log.debug("t2 state {}", t2.getState());
3、阻塞状态(Blocked)
用一个线程拿到锁,使得当前线程没拿到锁会出现阻塞状态。
Thread t6 = new Thread("t6") { @Override public void run() { synchronized (TestState.class) { // blocked try { Thread.sleep(90000); } catch (InterruptedException e) { e.printStackTrace(); } } } };t6.start();
4、等待状态(Waiting)
等待一个未执行完成的线程
t2.join(); //等待状态
5、超时等待(Time_Waiting)
可以在指定的时间自行返回的。
6、终止状态(TERMINATED)
线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 终止的线程不可再次复生。
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注盛行IT的更多内容!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。