一、前言
大家好,我是卷心菜,大二学生一枚。这篇文章是介绍Java基础知识——多线程的,读完这篇文章,各位小伙伴们可以收获哪些呢?
程序、进程、线程三者的区别
创建线程的四种方式
线程的常用方法
线程的死锁、释放锁
线程的同步原理、生命周期等等
废话不多说,干货满满,赶快来看看吧~
二、基本概念
什么是程序?
程序是为完成特定任务、用某种语言编写的一组指令的集合。大白话来讲,就是我们写的代码
什么是进程?
进程是程序的一次执行过程,或是正在运行的一个程序。例如运行中的QQ、微信,运行中的MP3播放器等等,有它自身的产生、存在和消亡的过程
什么是线程?
线程是由进程创建的,是进程的一个实体,一个进程可以拥有多个线程,也可以有单个线程
三、创建线程
1、方式一
继承Thread类,看代码:
public class Thread_one { public static void main(String[] args) throws InterruptedException { Person person = new Person(); person.start(); //当启动线程时,子线程和主线程会交替执行 //主线程结束,子线程不一定结束 //person.run()就是一个普通的方法,没有真正的启动一个线程 for (int i = 0; i < 20; i++) { System.out.println("main线程执行"); Thread.sleep(1000); } } } class Person extends Thread { @Override public void run() { int times = 0; while (true) { try { System.out.println("大家好,我是卷心菜~" + (++times) + "号" + "线程名是——" + Thread.currentThread().getName()); Thread.sleep(2000); if (times == 50) break; } catch (InterruptedException e) { e.printStackTrace(); } } } }
该代码的功能是:启动一个子线程,在控制台打印50次“大家好,我是卷心菜”,主线程在控制台打印“main线程执行”,运行的部分结果如下:
我们来重点剖析一下这种创建方式,因为后面三种方式本质上区别不大。
当我们Person类继承Thread类以后,就要重写run(),方法体内写上自己需要的代码逻辑。语句Thread.sleep(2000);意思是,每打印一句话在控制台后,子线程就要“休息”2秒,在这“休息”的时间,主线程就开始执行自己的语句;同样,在主线程“休息”的时间,子线程也开始执行自己的语句。
总之,子线程和主线程会交替执行,多次运行可以发现,主线程结束,子线程不一定结束。这里需要特别注意。
爱思考的小伙伴们可能就要问了:我们在Person类中重写了run(),但是怎么使用线程的时候,调用的是start(),那run()谁来执行的呢?别着急,让我们进去源码看一看。
我们从源码中可以发现,start()调用了start0(),而start0()是一个本地方法,由JVM调用,只是我们看不到而已。
2、方式二
方式一明白了,再看看第二种方式,代码如下:
public class Thread_two { public static void main(String[] args) { Animal animal = new Animal(); Thread thread = new Thread(animal); thread.start(); } } class Animal implements Runnable { int times = 0; @Override public void run() { while (true) { System.out.println("大家好,我是Tomcat~"); try { Thread.sleep(2000); ++times; if (times == 10) break; } catch (InterruptedException e) { e.printStackTrace(); } } } }
此代码逻辑也很好理解,这里就不过多讲述了。我们来分析一下:为什么要使用类实现接口的方式来创建线程?
学过基础的小伙伴们都知道,我们Java是单继承的。因此,在某些情况下一个类可能已经继承了某个类,这时再用继承Thread类方法来创建线程就不可以了,所以我们有了Runnable接口。
3、方式三
再来看看方式三,代码如下:
public class Thread_six { public static void main(String[] args) throws Exception { D d = new D(); FutureTask futureTask = new FutureTask(d); Thread thread = new Thread(futureTask); thread.start(); Object o = futureTask.get(); System.out.println(o); } } class D implements Callable { @Override public Object call() throws Exception { int sum = 0; for (int i = 0; i < 5; i++) { System.out.println(i + 1); sum += i; } return sum; } }
来看看运行结果:
与Runnable类相比,类Callable功能更强大些,具体有如下优点:
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果
4、方式四
最后来看看第四种方式,使用线程池,也是最推荐使用的方式,代码如下:
public class Thread_seven { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(10); // service 可以理解为池子 service.execute(new F()); //适用于Runnable // service.submit(); //适用于Callable service.shutdown();//关闭线程池 } } class F implements Runnable { @Override public void run() { for (int i = 0; i < 5; i++) { System.out.println(i + 1); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
运行结果没有任何问题:
四、常用方法
1、第一组
public Thread(Runnable target):返回对当前正在执行的线程对象的引用
public synchronized void start():开启线程
public final synchronized void setName(String name):设置线程名
public final void setPriority(int newPriority):设置线程的优先级别
public static native void sleep(long millis):设置线程的睡眠时间
public void interrupt():中断线程,但并没有结束线程,一般用于中断正在休眠的线程
这么多的方法,用代码实践一下:
public class Thread_five { public static void main(String[] args) { A a = new A(); Thread thread = new Thread(a); thread.setName("棒棒糖线程"); thread.setPriority(Thread.MIN_PRIORITY); thread.start(); thread.interrupt(); for (int i = 0; i < 20; i++) { System.out.println("主线程开始行动"); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } } } class A implements Runnable { @Override public void run() { while (true) { try { for (int i = 0; i < 10; i++) { System.out.println("我要吃" + (i + 1) + "棵棒棒糖,所处的线程是:" + Thread.currentThread().getName()); } System.out.println("吃多了,要消化20秒"); Thread.sleep(20000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
带大家看一下线程的优先级:
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5
截取一部分运行结果:
解析一下:由于把子线程的优先级设置为最低,主线程拿到了优先执行的语句,输出"主线程开始行动",然后在主线程睡眠时间时,子线程开始执行,当执行完:吃多了,要消化20秒时,本应该等待20秒,但是interrupt()打断了子线程,所以就会出现上图的情况。
如果把语句thread.interrupt();删去,就会出现下图的结果: