多线程这块一直是面试的重点,也是开发当中的重点,因而下决定把这一块的内容吃透它。整个系列的基础文章最少就在60篇以上,由于这块的知识看一两篇确实感受没什么做用,须要有一个体系的才好。这也是第一篇文章。点到为止。java
1、认识线程面试
一、概念安全
什么是线程呢?服务器
线程是进程划分红的更小的运行单位。就比如电脑QQ是一个进程,里面还有各类子模块,好比QQ空间,个性皮肤等子功能。微信
这里出现了另一个名词进程。多线程
进程是系统运行程序的基本单位。就比如是一个个应用程序QQ、微信等等。并发
看概念确实是一脸懵逼,举个例子就明白了。咱们打开电脑的任务管理器,会发现上面就有进程,点击这个进程咱们就能看到一个个应用程序,这就是进程。异步
那什么是线程呢?不知道咱们注意到了没有,每个进程最左边都有一个小箭头>,咱们打开来看看:ide
这些小的模块就比如是一个个线程。有了这个印象咱们从新来认识一下线程和进程就容易多了。函数
线程:
线程是一个比进程小的执行单位,也被称为轻量级进程。一个进程能够产生多个线程。多个线程共享同一块内存和系统资源,CPU在这多个线程间来回切换去执行。
进程:
进程是系统运行程序的基本单位,也就是一个程序。系统运行一个程序便是一个进程从建立,运行到消亡的过程。
有了对线程的基本认识接下来咱们就能够去理解一下,这一系列文章经常使用到的一些概念了。
二、并行与并发
其实他们俩区分很容易区分。
并发是多个任务交替使用CPU,同一时刻仍是只有一个任务在跑,并行是多个任务同时跑。举个例子就明白了。
桌子上有三个馒头,每一时刻,小明只能咬一个馒头。
桌子上有三个馒头,每一时刻,小明、小红、小华三我的同时咬三个馒头。
明白了吧。
三、同步和异步
这两个名词咱们在学习的时候常常会遇到,举个例子去理解,
对于同步:咱们去餐厅吃饭,只有一个客户的时候商家比较容易处理,可是当有两我的三我的的时候,这时候就须要排队了,也就是拥塞了,这就是同步。换个例子来讲,就是咱们请求服务器的时候,必需要等到服务器的反馈咱们才可以去作其余的事。
对于异步:就比如微信聊天,咱们只管把信息发送给对方,无论对方有没有回复咱们,咱们均可以去作其余的事,也就是说执行完函数以后,没必要等到反馈就能够去作其余的事。
四、死锁
和操做系统里面的死锁意思同样,也就是说多我的同时竞争一个资源,
例子一:比如多个男孩追求同一个女孩,这时候女孩就不知道该嫁给谁了。
例子二:咱们去图书馆借书,发现这本书被借走了,咱们只能等到那我的把书还到图书馆才能够看。
五、原子变量与原子操做
所谓原子操做,就是“不可中断的一个或一系列操做”。就比如你高考考试的时候,就算天塌下来也要把卷子作完。再急的事也不能抢夺他的优先权。
原子变量实际上是一个抽象的意思,由于本质上并无严格意义上的原子变量,可是在这里,咱们能够这样理解,原子变量提供原子操做,就比如变量a,多个线程对其操做改变时,每一次只能有一个线程拿到他的执行权进行操做。
在这里咱们基本上列举了一些基本的概念,但其实还有不少,咱们在遇到的时候再去分析和理解会比较容易。咱们说了这么久的线程,下面咱们来认识一下java中的线程。
2、基础案例
咱们以一个生活中的案例来解释说明,好比咱们敲代码的同时还想听音乐。
java中建立一个线程有两种方式,继承Thread类和实现runnable接口。咱们两种都实现一下。
一、继承Thread类
在这里咱们定义两个线程
public class ThreadTest01 extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++)
System.out.println("听音乐");
}
}
复制代码
还有一个线程能够敲代码
public class ThreadTest02 extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++)
System.out.println("敲代码");
}
}
复制代码
最后咱们就能够一边敲代码一边听音乐了
public class ThreadTest {
public static void main(String[] args) {
ThreadTest01 thread1=new ThreadTest01();
ThreadTest02 thread2=new ThreadTest02();
thread1.start();
thread2.start();
}
}
复制代码
咱们运行一边会发现,敲代码和听音乐是交叉输出的。这就体现了多线程的含义,由于要是平时输出,确定就是谁在前先输出谁,好比说先输出十个听音乐,在输出十个敲代码。不会交叉输出。
固然咱们也可使用一个线程类去演示,在这里,首先咱们建立了两个类ThreadTest01和ThreadTest02,而且都继承了Thread,而后再测试类中,咱们只须要调用相应的start方法便可。
使用一个线程类和使用多个线程类的区别你能够这样理解,一个是多个不一样的线程分别完成本身的任务,一个是多个相同的线程共同完成一个任务。
二、实现Runnable接口
咱们一样拿上面的例子进行说明
public class MyRunnable01 implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++)
System.out.println("听音乐");
}
}
复制代码
而后还能够敲代码
public class MyRunnable02 implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++)
System.out.println("敲代码");
}
}
复制代码
最后咱们能够测试一下了
public class ThreadTest {
public static void main(String[] args) {
MyRunnable01 runnable01=new MyRunnable01();
MyRunnable02 runnable02=new MyRunnable02();
Thread thread1=new Thread(runnable01);
Thread thread2=new Thread(runnable02);
thread1.start();
thread2.start();
}
}
复制代码
每次运行的时候,在控制台你都会看到不同的效果。不过多运行几回依然可以发现交叉运行的效果。
其实呢还有一种方式也能够实现线程,那就是实现Callable接口,不过不多用到,这种方式在之后的文章中再进行详细的介绍。毕竟这是第一篇文章。只是认识了解一下线程。
经过Runnable接口的方式,咱们依然发现,建立了两个MyRunnable,而后直接赋给Thread便可,调用的时候一样是使用start方法来启动。这就是简单的使用一下线程。
不知道咱们注意到没有,java其实为咱们已经提供了Thread,咱们能够直接进行实例化。有时候咱们会常用匿名内部类的方式来建立一个线程,好比说下面这种
new Thread("th1") {
@Override
public void run() {
System.out.println( "匿名内部类");
}
}.start();
复制代码
注意:上面的两种建立线程的方式中,明明都是重写的run方法,为何要去调用start启动线程呢?并且这两种方式有什么区别呢?在这里先留一个悬念,下一篇文章将会介绍道。
3、分析多线程
一、使用线程有什么好处呢?
好处你已经可以看到了,就是咱们能够同时作好几件事,在玩游戏的时候能够听歌,还能够看电影等等,多方便。
二、使用线程有什么坏处嘛?
坏处其实也不少,好比说对于单核 CPU,CPU 在一个时刻只能运行一个线程,当在运行一个线程的过程当中转去运行另一个线程,这个叫作线程上下文切换,上下文切换是一个复杂的过程,好比要记录程序计数器、CPU寄存器的状态等信息,耗时又耗空间。
为了解决这个问题,才有了如今的多核CPU。咱们常常会听到手机或者是电脑是八核的,就是减小上下文切换带来的时间空间损耗,提升程序运行的效率。
三、多线程带来一个问题
从上面的例子其实咱们发现只是各干各的事,相互之间互不干扰。还有一种状况是一个资源被多个线程所用到了,这就带来了线程安全问题。咱们使用例子来演示一下这个问题,
public class ThreadTest {
private int value=0;
public int getValue() {
return value++;
}
public static void main(String[] args) throws InterruptedException {
final ThreadTest test = new ThreadTest();
//咱们的本意多是th1执行后value变为1
new Thread("th1") {
@Override
public void run() {
System.out.println( test.getValue()+" "+super.getName());
}
}.start();
//而后th2执行后value变为2
new Thread("th2") {
@Override
public void run() {
System.out.println(test.getValue()+" "+super.getName());
}
}.start();
}
}
复制代码
在上面,咱们直接建立了两个线程,在第一个线程执行完以后value,在第二个线程执行完以后value变为2。可是结果与咱们想的每每不同,我测试了N屡次,结果老是0 th2和1 th1。或者是反过来,再或者是00、11等等。这就是线程不安全的例子。如何去确保线程安全呢?方式其实有不少种,咱们一点一点深刻以后再去解决。
下一篇文章,咱们将对线程的生命周期,经常使用API、以及源码(构造函数等)进行一个讲解,正式开始咱们的多线程之旅。感谢支持。