什么是线程?
线程本质来说就是一串代码,我们将它交给操作系统来执行。
通常来说我们的CPU任何时候一个核只能处理一个线程,但是目前手机都已经是多核了,顾名思义就是可以同时处理多个线程,也就是能同时处理多件事情。
什么是线程?
这里有人或许会说我们老师说过一个CPU也可以处理多个线程任务,的确大学时老师说过这个,但是我这里是有区别的,在这里会涉及到多核处理与单核多任务处理。
多核处理与单核多任务处理
- 单核多任务处理原理图
图解:单核CPU可已处理多个线程任务,但是首先要通过时间片的划分,比如 任务A执行到a1 ,便停止执行任务A开始执行任务B的b1。所以这里实际上CPU 在同一时间永远只执行一个线程,由于电脑的处理速度够快所以给我们感觉是三个线程都在同时执行,千万别被迷惑了。
- 多核处理
这个相信完全了可以理解了,同一时间CPU A 和CPU B 都在处理着线程。
线程:线程本质来说就是一串代码,我们将它交给操作系统来执行。
进程:进程是一个“执行中的程序”。
什么是多线程?
多线程:指的是这个程序(一个进程)运行时产生了不止一个线程
用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。说这个话其实只有一半对,因为反应“多角色”的程序代码,最起码每个角色要给他一个线程吧,否则连实际场景都无法模拟,当然也没法说能用单线程来实现:比如最常见的“生产者,消费者模型”。
这里我引用知乎上的说法:
1.单进程单线程:一个人在一个桌子上吃菜。
2.单进程多线程:多个人在同一个桌子上一起吃菜。
3.多进程单线程:多个人每个人在自己的桌子上吃菜。
好了以上都是让我们加深印象,更容易理解下面的东东。
多线程的实现——Thread和Runnable
对于Android的多线程来说,我们最早学习到的都是Thread和Runnable,通常我们用下面代码启动一个新的线程:
private void startNewThread() {
new Thread() {
@Override
public void run() {
super.run();
}
}.start();
}
或者
private void startNewThread() {
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
}
这似乎没什么区别,可是这两个的确是有去别的。
首先Thread 也是一个Runnable,它实现了Runnable接口,在Thread类中有一个 Runnable类型的target字段代表了要被执行的子线程中的任务。
public class Thread implements Runnable {
....
/* What will be run. */
private Runnable target;
/* The group of this thread */
private ThreadGroup group;
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
}
由此可知,实际上最终被线程执行的任务是 Runnable ,而非Thread。Thread只是对Runnable的包装。
这里有个比较有意思的实例我们来研究下:
售票问题,假设现在有三个窗口进行售票(并发执行)。
首先试试Thread
public class MyClass {
public static void main(String[] args) {
MyThread1 mt1=new MyThread1("窗口1");
MyThread1 mt2=new MyThread1("窗口2");
MyThread1 mt3=new MyThread1("窗口3");
mt1.start();
mt2.start();
mt3.start();
}
}
class MyThread1 extends Thread{
private int ticket=10;
private String name;
MyThread1(String name)
{
this.name=name;
}
@Override
public void run()
{
for(int i=0;i<20;i++)
{
if(this.ticket>0) {
System.out.println(name+ "卖票:ticket " + this.ticket--);
}
}
}
}
------------------------------------------------------------------------------------
窗口2卖票:ticket 10
窗口1卖票:ticket 10
窗口1卖票:ticket 9
窗口1卖票:ticket 8
窗口1卖票:ticket 7
窗口1卖票:ticket 6
窗口1卖票:ticket 5
窗口1卖票:ticket 4
窗口1卖票:ticket 3
窗口1卖票:ticket 2
窗口1卖票:ticket 1
窗口3卖票:ticket 10
窗口3卖票:ticket 9
窗口3卖票:ticket 8
窗口2卖票:ticket 9
窗口3卖票:ticket 7
窗口2卖票:ticket 8
窗口3卖票:ticket 6
窗口2卖票:ticket 7
窗口3卖票:ticket 5
窗口2卖票:ticket 6
窗口3卖票:ticket 4
窗口2卖票:ticket 5
窗口3卖票:ticket 3
窗口3卖票:ticket 2
窗口3卖票:ticket 1
窗口2卖票:ticket 4
窗口2卖票:ticket 3
窗口2卖票:ticket 2
窗口2卖票:ticket 1
------------------------------------------------------------------------------------
再看看用Runnable
public class MyClass {
public static void main(String[] args) {
MyThread1 mt=new MyThread1();
Thread t1=new Thread(mt,"窗口1");
t1.start();
Thread t2=new Thread(mt,"窗口2");
t2.start();
Thread t3=new Thread(mt,"窗口3");
t3.start();
}
}
class MyThread1 implements Runnable{
private int ticket=10;
@Override
public void run()
{
for(int i=0;i<20;i++)
{
if(this.ticket>0) {
System.out.println(Thread.currentThread().getName()+ "卖票:ticket " + this.ticket--);
}
}
}
}
------------------------------------------------------------------------------------
窗口1卖票:ticket 10
窗口2卖票:ticket 9
窗口1卖票:ticket 8
窗口3卖票:ticket 7
窗口1卖票:ticket 5
窗口2卖票:ticket 6
窗口1卖票:ticket 3
窗口3卖票:ticket 4
窗口1卖票:ticket 1
窗口2卖票:ticket 2
------------------------------------------------------------------------------------
Runnable和Thread区别
实际开发中我们通常采用Runnable接口来实现多线程。因为实现Runnable接口比继承Thread类有如下好处:
- 避免继承的局限,一个类可以继承多个接口,但是类只能继承一个类。
- Runnable接口实现的线程便于资源共享。而通过Thread类实现,各自线程的资源是独立的,不方便共享。
线程的wait、sleep、join、和yield
对于Thread来说使用比较简单,并没有什么成本,但是通常来说,我们使用的线程基本上都是复写Run函数,然后调用start()函数来启动线程。这些通常在面试时会偶尔的提到,但是线程的wait、sleep、join、和yield可是面试题目中最常提到的。
函数名 | 作用 |
---|---|
wait() | 当wait()方法执行时,它就进入到一个和该对象相关的等待池中同时释放了机锁使得其它的县城可以访问,用户可以Notify、notifyAll或者指定时间来唤醒该线程 |
sleep | 该函数是Thread的静态函数,作用是使线程进入睡眠状态,但是对象的机锁并没有释放,其他线程无法访问这个对象。 |
join | 等待目标线程执行完成之后再继续执行 |
yield | 线程礼让,调用线程由运行状态 |
下一篇我们就直接进入主题进行讲解,这里只是为了理解概念。