一:认识线程:
每一个线程就是一个“执行流”。每个线程之间都可以按照顺序执行自己的代码,多个线程之间可以同时执行自己的代码。比如我们之前一直写的程序都只是在main线程中写的代码,以前写的代码都是单个线程的。
那么为什么会出现线程?主要有2个原因:
1)并发编程的需要
单核CPU的发展已经到了瓶颈,现在已经到了多核CPU的时代,而并发编程可以让CPU的资源得到充分的利用。
2)进程太“重”了。
虽然进程也可以实现并发编程,但是进程还是太“重”了。
进程在创建时开销非常大
进程在销毁是开销非常大
进程在调度的时候也开销非常大!
我们这里所说的“重”指的是“资源分配/回收”。
二、线程的优点:
所以我们的线程就应运而生。相对于进程而言。线程就更加的“轻量化”了,所以线程也被称为“轻量化进程”。这里说的轻量是因为线程在创建、销毁和调度的时候的开销都要比进程低。而线程之所以开销低的原因又在于线程省去了一些“申请资源和释放资源的步骤”。
比如这样的一个例子:
小明家开了一个工厂来生产冰箱,经过了一段时间的经营,发现生产的冰箱非常的受欢迎,销量很高,于是小明想到了两种方案来提高生产力:
方案一(多进程方案):再寻找一个工业场地,购进一批同样的机器来进行生产。
方案二(多线程方案):把原工厂的地方拾掇拾掇,腾出一块地方,购进一批新的机器放机器进行生产
显然我们可以看出,第二种方案(多线程)的开销要明显低于第一种方案(多进程)。因为第二种方案没有进行更大的资源开销,而是在原工厂的基础上进行了复用,也就是还是利用了原场地。这样下来的开销就大大减少了。
三、进程和线程的区别(面试题):
1.、进程包含线程。一个进程中可以有一个线程,也可以有多个线程。
2、进程和线程都可以解决并发编程问题,但是进程在频繁的创建和销毁的时候开销更大,线程更小。(线程比进程更加的轻量)
3、进程是操作系统资源分配的基本单位,线程是操作系统进行调度的基本单位。
4、进程与进程之间不共享内存空间,同一个进程中的线程共享内存空间。(这就可能会出现一个线程崩了,别的也会收到影响,而进程之间不会影响。也就是说进程比线程更安全)
四、第一个多线程程序:
线程是操作系统里面的概念,Java标准库中的Thread类可以视为是对操作系统提供的API进行了抽象和封装。
通过第一个多线程的程序感受一下多线程和单一线程的区别。
1. package Thread; 2. 3. class myThread extends Thread{ 4. //使用一个boolean类型进行判断,让该线程只打印100次 5. private boolean flag=true; 6. private int count=1; 7. @Override 8. public void run() { 9. 10. while(flag){ 11. if(count==100){ 12. flag=false; 13. } 14. try { 15. Thread.sleep(1000); 16. } catch (InterruptedException e) { 17. throw new RuntimeException(e); 18. } 19. System.out.println("hello Thread"); 20. count++; 21. } 22. 23. } 24. } 25. public class ThreadDemo2 { 26. public static void main(String[] args) throws InterruptedException { 27. Thread t=new myThread(); 28. t.start(); 29. //主线程打印50次 30. for (int i = 0; i <50 ; i++) { 31. Thread.sleep(500); 32. System.out.println("hello main"); 33. } 34. 35. 36. } 37. }
该程序并没有先执行其中一个线程,在执行另一个的线程,这就是多线程的魅力所在。
我们从上述结果中也可以看到一个现象就是main线程和t线程谁先执行是不确定的,是完全由我们的系统自己决定的。所以线程的调度是“抢占式执行,随机调度”。
五、创建线程的方式:
我们创建线程可以有五种方式:
1、创建子类继承Thread类,重写run方法。
1. package Thread; 2. 3. class myThread1 extends Thread{ 4. @Override 5. public void run() { 6. System.out.println("hello Thread"); 7. } 8. } 9. public class ThreadDemo3 { 10. public static void main(String[] args) { 11. Thread t1=new myThread1(); 12. t1.start(); 13. } 14. }
run方法描述了该线程要执行的任务内容,即要执行的代码,而调用start方法才是真正创建了线程,才会执行任务。
2、实现Runnable接口,重写run方法。
1. package Thread; 2. 3. class myRunnable implements Runnable{ 4. 5. @Override 6. public void run() { 7. System.out.println("hello Thread"); 8. } 9. } 10. public class ThreadDemo4 { 11. public static void main(String[] args) { 12. myRunnable runnable=new myRunnable(); 13. Thread t=new Thread(runnable); 14. t.start(); 15. 16. } 17. }
这个方法是通过实现Runnable接口,创建出一个Runnable的实例,把这个实例传给Thread对象,通过这个实例来描述要执行的任务。
3、匿名内部类创建Thread子类对象。
1. package Thread; 2. 3. public class ThreadDemo5 { 4. public static void main(String[] args) { 5. Thread t=new Thread(){ 6. @Override 7. public void run() { 8. System.out.println("hello Thread"); 9. } 10. }; 11. 12. t.start(); 13. 14. } 15. }
4、匿名内部类创建Runnable子类对象
1. package Thread; 2. 3. public class ThreadDemo6 { 4. public static void main(String[] args) { 5. Thread t = new Thread(new Runnable() { 6. @Override 7. public void run() { 8. System.out.println("hello Thread"); 9. } 10. }); 11. t.start(); 12. } 13. }
5、使用lambda表达式(最推荐写法)
1. package Thread; 2. 3. public class ThreadDemo7 { 4. public static void main(String[] args) { 5. Thread t=new Thread(()->{ 6. System.out.println("hello Thread"); 7. }); 8. t.start(); 9. } 10. }