在讲解java高并发时必需要先聊聊线程,那么什么是线程呢?下面是网上的答案:java
- 1.线程:进程中负责程序执行的执行单元
- 线程自己依靠程序进行运行
- 线程是程序中的顺序控制流,只能使用分配给程序的资源和环境
- 2.进程:执行中的程序
- 一个进程至少包含一个线程
- 3.单线程:程序中只存在一个线程,实际上主方法就是一个主线程
- 4.多线程:在一个程序中运行多个任务
- 目的是更好地使用CPU资源
我这里通俗一点讲其实就是:一个程序里头不一样的执行路径,能够放在不一样的cpu里面同步运行git
//在java.lang包中定义, 继承Thread类必须重写run()方法 class MyThread extends Thread{ private static int num = 0; public MyThread(){ num++; } @Override public void run() { System.out.println("主动建立的第"+num+"个线程"); } } /*建立好了本身的线程类以后,就能够建立线程对象了,而后经过start()方法去启动线程。 注意,不是调用run()方法启动线程,run方法中只是定义须要执行的任务,若是调用run方法, 即至关于在主线程中执行run方法,跟普通的方法调用没有任何区别, 此时并不会建立一个新的线程来执行定义的任务。*/ public class Test { public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); MyThread thread1 = new MyThread("thread1"); thread1.start(); MyThread thread2 = new MyThread("thread2"); thread2.run(); } } class MyThread extends Thread{ private String name; public MyThread(String name){ this.name = name; } @Override public void run() { System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId()); }
//在Java中建立线程除了继承Thread类以外,还能够经过实现Runnable接口来实现相似的功能。 //实现Runnable接口必须重写其run方法。 //下面是一个例子: public class Test { public static void main(String[] args) { System.out.println("主线程ID:"+Thread.currentThread().getId()); MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start(); } } class MyRunnable implements Runnable{ public MyRunnable() { } @Override public void run() { System.out.println("子线程ID:"+Thread.currentThread().getId()); } } //Runnable的中文意思是“任务”,顾名思义,经过实现Runnable接口,咱们定义了一个子任务, //而后将子任务交由Thread去执行。注意,这种方式必须将Runnable做为Thread类的参数, //而后经过Thread的start方法来建立一个新线程来执行该子任务。若是调用Runnable的run方法的话, //是不会建立新线程的,这根普通的方法调用没有任何区别。 //事实上,查看Thread类的实现源代码会发现Thread类是实现了Runnable接口的。 //在Java中,这2种方式均可以用来建立线程去执行子任务,具体选择哪种方式要看本身的需求。 //直接继承Thread类的话,可能比实现Runnable接口看起来更加简洁,可是因为Java只容许单继承, //因此若是自定义类须要继承其余类,则只能选择实现Runnable接口。
多线程后续会讲到,这里暂时先知道一下有这种方法便可。编程
/** * 有返回值的线程 */ @SuppressWarnings("unchecked") public class Test { public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println("----程序开始运行----"); Date date1 = new Date(); int taskSize = 5; // 建立一个线程池 ExecutorService pool = Executors.newFixedThreadPool(taskSize); // 建立多个有返回值的任务 List<Future> list = new ArrayList<Future>(); for (int i = 0; i < taskSize; i++) { Callable c = new MyCallable(i + " "); // 执行任务并获取Future对象 Future f = pool.submit(c); // System.out.println(">>>" + f.get().toString()); list.add(f); } // 关闭线程池 pool.shutdown(); // 获取全部并发任务的运行结果 for (Future f : list) { // 从Future对象上获取任务的返回值,并输出到控制台 System.out.println(">>>" + f.get().toString()); } Date date2 = new Date(); System.out.println("----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】"); } } class MyCallable implements Callable<Object> { private String taskNum; MyCallable(String taskNum) { this.taskNum = taskNum; } public Object call() throws Exception { System.out.println(">>>" + taskNum + "任务启动"); Date dateTmp1 = new Date(); Thread.sleep(1000); Date dateTmp2 = new Date(); long time = dateTmp2.getTime() - dateTmp1.getTime(); System.out.println(">>>" + taskNum + "任务终止"); return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】"; } } /*代码说明: 上述代码中Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。 public static ExecutorService newFixedThreadPool(int nThreads) 建立固定数目线程的线程池。 public static ExecutorService newCachedThreadPool() 建立一个可缓存的线程池,调用execute 将重用之前构造的线程(若是线程可用)。 若是现有线程没有可用的,则建立一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。 public static ExecutorService newSingleThreadExecutor() 建立一个单线程化的Executor。 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) 建立一个支持定时及周期性的任务执行的线程池,多数状况下可用来替代Timer类。 ExecutoreService提供了submit()方法,传递一个Callable,或Runnable, 返回Future。若是Executor后台线程池尚未完成Callable的计算, 这调用返回Future对象的get()方法,会阻塞直到计算完成。*/
start()
方法, 等待CPU进行调度run()
方法当须要新起一个线程来执行某个子任务时,就建立了一个线程。可是线程建立以后,不会当即进入就绪状态,由于线程的运行须要一些条件(好比内存资源,在前面的JVM内存区域划分一篇博文中知道程序计数器、Java栈、本地方法栈都是线程私有的,因此须要为线程分配必定的内存空间),只有线程运行须要的全部条件知足了,才进入就绪状态。 当线程进入就绪状态后,不表明马上就能获取CPU执行时间,也许此时CPU正在执行其余的事情,所以它要等待。当获得CPU执行时间以后,线程便真正进入运行状态。 线程在运行状态过程当中,可能有多个缘由致使当前线程不继续运行下去,好比用户主动让线程睡眠(睡眠必定的时间以后再从新执行)、用户主动让线程等待,或者被同步块给阻塞,此时就对应着多个状态:time waiting(睡眠或等待必定的事件)、waiting(等待被唤醒)、blocked(阻塞)。 当因为忽然中断或者子任务执行完毕,线程就会被消亡。缓存
在有些教程上将blocked、waiting、time waiting统称为阻塞状态,这个也是能够的,只不过这里我想将线程的状态和Java中的方法调用联系起来,因此将waiting和time waiting两个状态分离出来。安全
注:sleep和wait的区别:多线程
sleep
是Thread
类的方法,wait
是Object
类中定义的方法.Thread.sleep
不会致使锁行为的改变, 若是当前线程是拥有锁的, 那么Thread.sleep
不会让线程释放锁.Thread.sleep
和Object.wait
都会暂停当前的线程. OS会将执行时间分配给其它线程. 区别是, 调用wait
后, 须要别的线程执行notify/notifyAll
才可以从新得到CPU执行时间.
对于单核CPU来讲(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程当中转去运行另一个线程,这个叫作线程上下文切换(对于进程也是相似)。并发
因为可能当前线程的任务并无执行完毕,因此在切换时须要保存线程的运行状态,以便下次从新切换回来时可以继续切换以前的状态运行。举个简单的例子:好比一个线程A正在读取一个文件的内容,正读到文件的一半,此时须要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,咱们不但愿线程A又从文件的开头来读取。ide
所以须要记录线程A的运行状态,那么会记录哪些数据呢?由于下次恢复时须要知道在这以前当前线程已经执行到哪条指令了,因此须要记录程序计数器的值,另外好比说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候须要知道以前挂起时变量的值时多少,所以须要记录CPU寄存器的状态。因此通常来讲,线程上下文切换过程当中会记录程序计数器、CPU寄存器状态等数据。高并发
说简单点的:对于线程的上下文切换实际上就是 存储和恢复CPU状态的过程,它使得线程执行可以从中断点恢复执行。测试
虽然多线程可使得任务执行的效率获得提高,可是因为在线程切换时一样会带来必定的开销代价,而且多个线程会致使系统资源占用的增长,因此在进行多线程编程时要注意这些因素。
编号 | 方法 | 说明 |
---|---|---|
1 | public void start() |
使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
2 | public void run() |
若是该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;不然,该方法不执行任何操做并返回。 |
3 | public final void setName(String name) |
改变线程名称,使之与参数 name 相同。 |
4 | public final void setPriority(int priority) |
更改线程的优先级。 |
5 | public final void setDaemon(boolean on) |
将该线程标记为守护线程或用户线程。 |
6 | public final void join(long millisec) |
等待该线程终止的时间最长为 millis 毫秒。 |
7 | public void interrupt() |
中断线程。 |
8 | public final boolean isAlive() |
测试线程是否处于活动状态。 |
9 | public static void yield() |
暂停当前正在执行的线程对象,并执行其余线程。 |
10 | public static void sleep(long millisec) |
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操做受到系统计时器和调度程序精度和准确性的影响。 |
11 | public static Thread currentThread() |
返回对当前正在执行的线程对象的引用。 |
中止线程是在多线程开发时很重要的技术点,掌握此技术能够对线程的中止进行有效的处理。
中止一个线程可使用Thread.stop()方法,但最好不用它。该方法是不安全的,已被弃用。
在Java中有如下3种方法能够终止正在运行的线程:
interrupt()方法
在操做系统中,线程能够划分优先级,优先级较高的线程获得的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。
设置线程优先级有助于帮“线程规划器”肯定在下一次选择哪个线程来优先执行。
设置线程的优先级使用setPriority()方法,此方法在JDK的源码以下:
public final void setPriority(int newPriority) { ThreadGroup g; checkAccess(); if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) { throw new IllegalArgumentException(); } if((g = getThreadGroup()) != null) { if (newPriority > g.getMaxPriority()) { newPriority = g.getMaxPriority(); } setPriority0(priority = newPriority); } } //在Java中,线程的优先级分为1~10这10个等级,若是小于1或大于10, //则JDK抛出异常throw new IllegalArgumentException()。 //JDK中使用3个常量来预置定义优先级的值,代码以下: public final static int MIN_PRIORITY = 1; public final static int NORM_PRIORITY = 5; public final static int MAX_PRIORITY = 10; /*线程优先级特性: 继承性 好比A线程启动B线程,则B线程的优先级与A是同样的。 规则性 高优先级的线程老是大部分先执行完,但不表明高优先级线程所有先执行完。 随机性 优先级较高的线程不必定每一次都先执行完。 */
在Java线程中有两种线程,一种是User Thread(用户线程),另外一种是Daemon Thread(守护线程)。
Daemon的做用是为其余线程的运行提供服务,好比说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来讲去没啥区别的,惟一的区别之处就在虚拟机的离开:若是User Thread所有撤离,那么Daemon Thread也就没啥线程好服务的了,因此虚拟机也就退出了。
守护线程并不是虚拟机内部能够提供,用户也能够自行的设定守护线程,方法:public final void setDaemon(boolean on) ;可是有几点须要注意:
码云地址:https://gitee.com/zhangzeli/java-concurrent