多线程
1、 多线程相关的概念
1、程序(Program)
为了实现一个功能,完成一个任务而选择一种编程语言编写的一组指令的集合。
软件 = 程序 + 程序运行所需要的一些资源文件。 一个软件中可能会有很多个程序构成。
2、进程(Process)
程序的一次运行。操作系统会给这个进程分配资源(例如:内存)。
进程是操作系统分配资源的最小单位,进程与进程之间的内存是独立,无法直接共享。最早的DOS操作系统是单任务的,同一时间只能运行一个进程。后来现在的操作系统都是支持多任务的,可以同时运行多个进程。两个进程之间进行来回切换,通信(交换数据)等操作时,成本比较高。
3、线程(Thread)
线程是进程中的其中一条执行路径。一个进程中至少有一个线程,也可以有多个线程。有的时候也把线程称为轻量级的进程。同一个进程的多个线程之间有些内存是可以共享的(方法区、堆),也有些内存是独立的(栈(包括虚拟机栈和本地方法栈)、程序计数器)。 因为线程之间可能使用共享内存,那么在数据交换成本上就比较低。而且线程之间的切换相对进程对于CPU和操作系统来说,成本比较低。
一个进程中至少有一个线程。
4、并行: 多个处理器同时可以执行多条执行路径。
要求同时进行。针对CPU多核,甚至多个CPU,同时运行多个线程任务。
有同时处理多个任务的能力,并行。
5、并发:多个任务同时执行,但是可能存在先后关系。
有处理多个任务的能力,不一定要同时。
如果某个系统支持两个或者多个动作(Action)同时存在,那么这个系统就是一个并发系统。如果某个系统支持两个或者多个动作同时执行,那么这个系统就是一个并行系统。并发系统与并行系统这两个定义之间的关键差异在于**“存在”**这个词。
在并发程序中可以同时拥有两个或者多个线程。这意味着,如果程序在单核处理器上运行,那么这两个线程将交替地换入或者换出内存。这些线程是同时“存在”的——每个线程都处于执行过程中的某个状态。如果程序能够并行执行,那么就一定是运行在多核处理器上。此时,程序中的每个线程都将分配到一个独立的处理器核上,因此可以同时运行。《并发的艺术》 — 〔美〕布雷谢斯
看能不能同时被(多个)cpu执行,如果可以就说明是并行,而并发是多个线程被(一个)cpu 轮流切换着执行。
并发(Concurrency)是同时处理很多事情(dealing with lots of things at once),并行(Parallelism)是同时执行很多事情(doing lots of things at once);并行是并发的子集。
CPU:一个CPU同一个时间只能够运行一个线程的任务。
如何实现多个线程同时运行的呢?
是因为CPU是非常快,这个速度远远高于内存、硬盘、人的大脑反应的速度。
那么CPU会在多个线程之间,快速的切换,人是感觉不到。
交替执行 多核同时执行
2、两种实现多线程的方式
Java中如何去实现多线程?
(1)Java的程序入口是main,其实也是main线程,主线程。
线程是进程的其中一条执行路径,即一个进程至少有一个线程。那么main线程就是Java程序进程的第一个线程了。
(2)如何开启main线程以外的其他线程呢?
这里写2种,还有其他方式的。
1、继承Thread类
步骤:
(1)编写线程类,去继承Thread类(java.lang.Thread)
(2)重写public void run(){}
在run()中需要编写这个线程需要完成的任务。
(3)创建线程对象
(4)启动线程:start(),是调用start()方法。
class MyThread extends Thread {
@Override
public void run(){
//...
}
}
class Test{
public static void main(String[] args){
MyThread my = new MyThread();
my.start();//有名字的线程对象启动
new MyThread().start();//匿名线程对象启动
//匿名内部类的匿名对象启动
new Thread(){
public void run(){
//...
}
}.start();
//匿名内部类,但是通过父类的变量多态引用,启动线程
Thread t = new Thread(){
public void run(){
//...
}
};
t.start();
}
}
public class TestThread2 {
public static void main(String[] args) {
System.out.println("hello thread");
MyThread my = new MyThread();
//顺序执行 如果my.run()没my.start() 就是顺序执行了
// my.run();//这么调用,就不是开启多线程,而是普通对象调用方法
my.start();//从父类Thread中继承的
//打印main方法的
for (int i = 1; i <=100; i++) {
System.out.println("main:" + i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
//例如:这个线程要完成的任务是,打印1-100之间的数字
for (int i = 1; i <= 100; i++) {
System.out.println("自定义线程:" + i);
}
}
}
可能 你执行一下 我执行一下
2、实现Runnable接口
步骤:
(1)编写线程类,实现Runnable接口
(2)重写public void run(){}
(3)创建线程对象
(4)借助Thread类的对象启动线程
class MyRunnable implements Runnable{
public void run(){
//...
}
}
class Test {
public static void main(String[] args){
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
t1.start();
t2.start();
//两个匿名对象
new Thread(new MyRunnable()).start();
//匿名内部类的匿名对象作为实参直接传给Thread的构造器
new Thread(new Runnable(){
public void run(){
//...
}
}).start();
}
}
public class TestRunnable {
public static void main(String[] args) {
MyRunnable my = new MyRunnable();
// my.start();
//借人家的
Thread t = new Thread(my);
t.start();
for (int i = 1; i <=100; i++) {
System.out.println("main:" + i);
}
}
}
class MyRunnable implements Runnable {
// abstract Runnable
// public abstract void run();
@Override
public void run() {
// 例如:这个线程要完成的任务是,打印1-100之间的数字
for (int i = 1; i <= 100; i++) {
System.out.println("自定义线程:" + i);
}
}
}
3、 线程的生命周期
线程的生命周期:
(1)新建/出生
new好了一个线程对象,此时它和普通的Java对象并没有区别。
新建线程 new一个线程对象
(2)就绪
就绪状态的线程是具备被CPU调用的能力和状态,也只有这个状态的线程才能被CPU调用。即线程调用了start()。
就绪状态的线程是具备被CPU调用的能力和状态
(3)运行
运行状态就是当前线程正在被CPU调度执行。
运行 当前线程被CPU调度执行中
运行->就绪 ①时间到②yield()
(4)阻塞
从运行状态到阻塞状态有几种情况:
①sleep()
②wait()
③join()
④没锁
⑤suspend()已过时
从阻塞回到就绪状态
①sleep()时间到,sleep()被打断interrupt()
②notify()
③加塞的线程结束
④占用锁的线程释放锁
⑤resume()已过时
(5)死亡
从运行到死亡:①run()正常结束②run()遇到异常但是没处理③其他线程把你stop()(已过时)
4、参考资料
记录 - 搞定Java核心技术
从Hello到goodbye