全文提纲:
1.线程是什么?(上)
2.线程和进程的区别和联系(上)
3.创建多线程的方法(上)
4.线程的生命周期(上)
5.线程的控制(上)
6.线程同步(下)
7.线程池(下)
8.ThreadLocal的基本用法(下)
9.线程安全(下)
线程是什么?进程是什么?
这么说可能有点懵逼,举个栗子吧:
A工厂是一个生产汽车的工厂,今天员工张三去送货,员工李四去进货。这里的
从例子可以看出
进程是指运行中的程序(没运行的程序,系统是不会为之分配资源的),每个进程都有自己独立的内存空间,当一个程序进入内存运行时,程序内部可能包含多个程序执行流,这个程序执行流就是一个线程。
几乎所有操作系统都支持多线程并发,就像我们平时上班用的电脑,我们可能习惯打开eclipse写代码,同时打开网易云音乐听课,而且还要打开有道翻译时刻准备着把我们的中文转成英文…
可见我们的电脑可以支持多个应用程序同时执行的,但实际上,而对于每个CPU来说,它在一个时间点内,只能执行一个程序,也就是一个进程,那为什么我们同时打开这么多程序运行没问题呢?
那是因为现代电脑都不止一个CPU啦。当然这个是一个原因。
最主要的是因为在程序运行过程中,CPU在不同程序之间高速的来回切换执行,因此所谓的“并发执行”实际上并不是多个程序在同时执行,而是系统对程序的执行做了调度,让视觉上看起来是同时执行了。
所以线程中的并发:
是指多个进程被CPU快速的轮换执行,而不是同时执行
通过上面的原理讲述已经能看出区别了,最主要有2点
1public class ThreadTest extends Thread { 2 @Override 3 public void run() { 4 // 业务逻辑 5 super.run(); 6 } 7 8 public static void main(String[] args) { 9 new ThreadTest().run(); 10 new ThreadTest().run(); 11 } 12} 复制代码
1public class ThreadTest implements Runnable { 2 @Override 3 public void run() { 4 //业务逻辑 5 } 6 7 public static void main(String[] args) { 8 new ThreadTest().run(); 9 new ThreadTest().run(); 10 } 11} 复制代码
Callable接口是jdk5之后的新接口,它提供了一个call方法作为线程执行体,和thread的run方法类似,但是它的功能更强大:
1public static void main(String[] args) { 2 3 //1)创建一个Callable实现类,并实现call方法 4 //2)用FutrueTask来包装类的实例 5 FutureTask ft=new FutureTask<>(new Callable<Integer>() { 6 @Override 7 public Integer call() throws Exception { 8 System.out.println("执行了"); 9 try { 10 Thread.sleep(1000*5); 11 } catch (InterruptedException e1) { 12 // TODO Auto-generated catch block 13 e1.printStackTrace(); 14 } 15 16 return 12; 17 } 18 }); 19 //使用FutureTask对象作为target来创建并且启动线程 20 new Thread(ft).start(); 21 22 //阻塞方式获取线程返回值 23 try { 24 System.out.println("返回值:"+ft.get()); 25 } catch (InterruptedException e) { 26 // TODO Auto-generated catch block 27 e.printStackTrace(); 28 } catch (ExecutionException e) { 29 // TODO Auto-generated catch block 30 e.printStackTrace(); 31 } 32 //带有超时方式获取线程返回值 33 try { 34 System.out.println("返回值:"+ft.get(2,TimeUnit.SECONDS)); 35 } catch (InterruptedException | ExecutionException | TimeoutException e) { 36 // TODO Auto-generated catch block 37 e.printStackTrace(); 38 } 39 40 } 复制代码
线程创建之后,不会立即处于运行状态,根据前面对并发的定义理解:即使他启动了也不会永远都处于运行状态,如果它一直处于运行状态,就会一直占据着CPU资源,线程之间的切换也就无从谈起了。因此,线程是有生命周期的,他的生命周期包括以下几种状态:
当在程序中用new创建一个线程之后,它就处于新建状态,此时它和程序中其它对象一样处于初始化状态(分配内存、初始化成员变量)。
当程序调用了start方法之后,程序就处于就绪状态,jvm会为它创建方法调用栈和程序计数器,此时的线程状态为可运行状态,并没有运行,而是需要线程调度器的调度决定何时运行。
当就绪的线程获得CPU之后,就会执行线程执行体(run方法),这时候线程就处于了运行状态。
处于运行的状态的线程,除非执行时间非常非常非常短,否则它会因为系统对资源的调度而被中断进入阻塞状态。操作系统大多采用的是抢占式调度策略,在线程获得CPU之后,系统给线程一段时间来处理任务,当到时间之后,系统会强制性剥夺线程所占资源,而分配别的线程,至于分配给谁,这个取决于线程的优先级。
处于运行状态的线程,当它主动或者被动结束,线程就处于死亡状态。至于结束的形式,通常有以下几种:
Java提供了线程在其生命周期中的一些方法,便于开发者对线程有更好的控制。
主要有以下方法:
当某个线程执行流中调用其他线程的join()方法时,调用线程将被阻塞,知道被join()方法加入的join()线程执行完成为止。
乍一看,怎么也理解不了,这句话,我们来写一个程序测试一下:
1public class ThreadTest extends Thread { 2 @Override 3 public void run() { 4 System.out.println(getName()+"运行..."); 5 for(int i=0;i<5;i++){ 6 System.out.println(getName()+"执行:"+i); 7 } 8 } 9 public ThreadTest(String name){ 10 super(name); 11 } 12 public static void main(String[] args) { 13 //main方法--主线程 14 //线程1 15 new ThreadTest("子线程1").start(); 16 //线程2 17 ThreadTest t2=new ThreadTest("子线程2"); 18 t2.start(); 19 20 try { 21 t2.join(1000); 22 } catch (InterruptedException e) { 23 e.printStackTrace(); 24 } 25 //线程3 26 new ThreadTest("子线程3").start(); 27 } 28} 复制代码
看输出结果:
1子线程1运行... 2子线程2运行... 3子线程2执行:0 4子线程2执行:1 5子线程2执行:2 6子线程2执行:3 7子线程1执行:0 8子线程2执行:4 9子线程1执行:1 10子线程1执行:2 11子线程1执行:3 12子线程1执行:4 13子线程3运行... 14子线程3执行:0 15子线程3执行:1 16子线程3执行:2 17子线程3执行:3 18子线程3执行:4 复制代码
可以看到,线程1和2在并发执行着,而线程3则在他们都执行完之后才开始。
由此可知:
join()方法调用之后,后面的线程必须等待前面执行完之后才能执行,而不是并发执行
当线程调用了setDaemon(true)之后,它就转入为后台线程,为前台线程提供服务,而当前台所有线程死亡时,后台线程也会接受到JVM的通知而自动死亡。
1ThreadTest t2=new ThreadTest("子线程2"); 2//这是为后台线程,但必须在start前设置,因为前台线程死亡JVM会通知 3//后台线程死亡,但接受指令到响应需要时间。因此要自爱start前就设置 4 t2.setDaemon(true); 5 t2.start(); 复制代码
当需要某个处于运行状态的线程暂停执行并且进入阻塞状态时,调用Thread.sleep既可。
当需要某个处于运行状态的线程暂停执行并且进入就绪状态,调用
Thread.yield()即可
前面说到,系统分配CPU给哪个线程的执行,取决于线程的优先级,因此每个线程都有一定的优先级,优先级高的线程会获得更多的执行机会,默认情况下,每个线程的默认优先级都与创建它的父线程优先级一致。
当我们需要某个线程或者更多的执行机会时,调用
Thread.currentThread().setPriority(int newPriority);
方法即可,newPriority的范围在1~10。
关于多线程的揭秘,上集都讲到这里,更高级的使用多线程,尽在下集 请关注我哦。
觉得本文对你有帮助?请分享给更多人
关注「编程无界」,提升装逼技能