多线程概述和两种实现方式
1.进程和线程的区别
进程就是一个应用程序。生活化之后就是一个个的不同的公司,像阿里巴巴、京东......
而线程是进程的一个执行单元。像阿里巴巴中的马云、童文红......
一个进程中可以存在一个或者多个线程。
进程A和进程B之间的内存是独立不共享的。像阿里和京东的资源肯定是不共享的。
而线程A和线程B之间栈内存独享(多个线程多个栈),堆和方法区内存共享。像马云和童文红都有自己独立的办公地点,但是他们也会共享公司中的一些公共区域,像茶水间、卫生间等等。
并且每个线程的执行是独立的,栈与栈之间各自执行自己的,即多个线程会同时执行,即多线程并发。
所以在多线程并发执行的情况下,main方法执行结束并不代表整个程序的结束。
在Java中,在Dos窗口执行了java Hello,那么此时就有一个Java进程,但是这个进程中又包含了main主线程、垃圾回收线程等等......
多线程有什么好处呢?
多线程并发执行可以提高程序的执行效率。
生活中很简单的例子,在之前还需要去火车站人工售票窗口购买火车票时,多线程就好比是多个售票窗口,那肯定可以增加火车站的运行效率,如果只有一个售票窗口,所有的人都在这一个售票窗口排队买票,可想而知这是多么糟糕的买票体验。
这里又存在了一个问题:对于单核cpu来说,可以做到真正的多线程并发吗?
对于单核的计算机来说,在某个节点只能做一件事情,但是因为计算机处理的速度非常非常的快,并且在多个线程之间频繁的来回切换,就会给人感觉是多件事情同时执行的,但是,这也仅仅是感觉而已。单核cpu是不能做到真正的多线程并发的。
2.实现线程的两种方式
- 1.继承Thread类
1.书写一个继承java.lang.Thread方法的线程类,重写run方法(需要自己选择重写)
2.创建线程对象:new 线程类
3.启动线程:xxx.start();(会自动的调用重写的run())
在执行到start()时,会启动一个分支线程,在JVM中新开辟一个空间(此时有两个栈),该代码完成该任务之后会瞬间结束!
启动成功的线程会自动的调用重写的run()
run()与main()一样在栈的底部,run()和main()同级 此时两个栈是各自独立运行的,但是控制台只有一个,所以它们会争相的输出到控制台,但是顺序每次执行都是不一定的。
例:
package com.dh.thread;
public class Thread01 {
public static void main(String[] args) {
//2.创建一个线程对象
MyThread t = new MyThread();
//3.启动一个线程
/*
重点:
在执行到start()时,会启动一个分支线程,在JVM中新开辟一个空间(此时有两个栈),该代码完成该任务之后,会瞬间结束!
启动成功的线程会自动的调用重写的run();
run()与main()一样在栈的底部,run()和main()同级;
此时两个栈是各自独立运行的,但是控制台只有一个,所以它们会争相的输出到控制台,但是顺序每次执行都是不一定的。
*/
t.start();
//主线程中的代码
for (int i = 0; i < 100; i++) {
System.out.println("主线程"+i);
}
}
}
//1.书写一个继承Thread类的线程类
class MyThread extends Thread{
//重写run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程"+i);
}
}
}
结果:
可以看到,主线程和子线程是交替输出到控制台的,且每次执行,结果都是不一样的。
注意:
只有start()才能开启一个子线程,如果在main中直接调用run(),这就只是一个简单的方法调用,而不是多线程了!而且也必须重写run(),它会在一个栈的底部。
- 2.实现Runnable接口
1.书写一个类,实现java.lang.Runnable接口,重写run方法(会强制提示重写)
2.创建线程对象:先new一个实现接口的类对象作为参数,然后new Thread(参数)
3.启动线程:xxx.start()
例:
package com.dh.thread;
public class Thread02 {
public static void main(String[] args) {
//2.利用实现了Runnable接口的类的对象做为参数创建一个线程
Thread t = new Thread(new MyThread01());
//3.1启动线程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程"+i);
}
}
}
//1.书写一个类实现Runnable接口,此时该类就是一个普通的运行类,不是线程类
class MyThread01 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程"+i);
}
}
}
结果:
此种方式是面向接口的方式,是更为常用的一种方法。
因为既可以实现多个接口,又可以继承类,更加的灵活。
- 匿名内部类的实现:(本质就是实现Runnable接口)
package com.dh.thread;
public class Thread03 {
public static void main(String[] args) {
//1.采用匿名内部类的形式创建一个线程
Thread t = new Thread(new Runnable() {
//2.重写run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程"+i);
}
}
});
//3.启动线程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程"+i);
}
}
}
水平有限,若有错误,望指正~