一、前言
Java
中的线程,是以轻量级进程来实现的;其他语言就不一定是这样的(如:go
语言就是以协程的方式来实现的—>用户自己写程序来实现线程以及调度线程
)
二、线程是什么?
一个线程就是一个 “执行流”,Java
中,线程以轻量级进程的方式实现,也就具有进程的特征;
表现为,需要系统调度cpu
来执行:
(1)并发
:一个cpu
以时间片轮转调度的方式,依次执行多个线程;
(2)并行
:多个cpu
在一个时间点,同时执行多个线程;
但线程到底是就绪状态还是运行状态,是由操作系统调度决定的,程序是不清楚的;
Java中所说的并发编程,其实既包含多线程的并发,也包含多线程的并行;
三、线程与进程关系
线程与进程关联关系:
-
进程包含线程,一个进程至少包含一个线程;
-
进程是系统分配资源的最小单位(基本单位),线程是操作系统调度
cpu
执行的最小单位(基本单位); -
进程状态的改变需要耗费较多时间,线程的效率更高;
对于进程与线程来说,有以下过程状态:
线程和进程一样,系统也会创建PCB来管理;
就绪:线程PCB
不在队列的首部;
运行:线程PCB
处于CPU
执行的时间片;
状态改变比较耗时:以下三种状态的改变都是比较耗费时间的,只是线程相对于进程来说,耗时少一点;
(1)创建
(2)销毁
(3)进入阻塞
- 进程占用独立的虚拟内存空间,同一个进程内的多个线程可以共享这个进程的内存;
表现在:
(1) 一个进程要想访问另一个进程的数据,需要使用通信的方式(
代价高
);同一个进程的线程,可以直接使用共享变量;
(2)一个进程挂掉不会影响其他进程;但一个进程中的线程挂掉,就可能影响整个进程挂掉;
四、创建一个线程
Java
中,创建一个线程:
首先Java
程序要运行,必须先有一个main
方法的入口类,先执行main
方法(这里也会创建一个main
线程---->Java
虚拟机会自动创建)
创建线程的方式:
方式1:继承 Thread
,重写run
方法;
代码如下:
package creat;
public class one_Thread {
public static void main(String[] args) {
//方式1:继承Thread,使用Thread重写run的方式
//方法:写一个类继承thread,重写run
Thread t = new MyThread();
//调用start方法,才会真正创建操作系统中的线程并申请系统调度执行
t.start();
}
//采用静态内部类
private static class MyThread extends Thread{
@Override
public void run() {
//run 方法中描述线程要执行的任务
System.out.println("MyThread run()");
}
}
}
方式2:实现 Runnable
接口,重写run
方法;
代码如下:
package creat;
public class two_Runnable {
public static void main(String[] args) {
//方式2:创建一个Runnable对象,传入Thread的构造方法
Thread t = new Thread(new MyRunnable());
t.start();
}
//Runnable接口,表示定义线程的任务对象(Thread才是线程本身)
private static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable run()");
}
}
}
方式3: 本质也是继承Thread
,只是采用了匿名内部类方式;
代码如下:
package creat;
public class one_Thread {
public static void main(String[] args) {
//匿名内部类:new出来的其实是一个没有类名的匿名类(继承了Thread)
Thread t2 = new Thread(){
@Override
public void run() {
System.out.println("匿名内部类 run()");
}
};
t2.start();
}
}
方式4:本质实现 Runnable
接口,采用匿名内部类;
代码如下:
package creat;
public class two_Runnable {
public static void main(String[] args) {
//Runnable也可以使用匿名内部类的写法
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Runnable匿名内部类 run()");
}
});
t2.start();
}
}
方式5 :lamda
写法:
代码如下:
Thread t3 = new Thread(() ->
System.out.println("Runnable匿名内部类 run()"));
t3.start();
在我们理解上,
new Thread
只是创建一个Java
中的线程对象,调用start
才会创建系统的线程并申请系统调度执行,Runnable
只是一个描述任务的对象,不是线程;
简单整理下,创建线程的方式就是
(1) 继承 Thread
,重写run
方法;
(2) 实现 Runnable
接口,重写run
方法;
(3)实现 Callable
接口;
五、多线程作用/优势
优势
:使用多线程可以提高执行效率
示例1
:不使用多线程
代码如下:
package creat;
public class advertage_No {
//此处为不使用多线程的方式
public static void main(String[] args) {
//执行2次操作,每次循环++10亿次
int num = 10_0000_0000;
//计录执行时间
//返回该行代码执行时,从1970-01-01经过的毫秒数
long start = System.currentTimeMillis();
for(int i=0;i<100;i++){
for(int j=0;i<num;j++){
}
}
long end = System.currentTimeMillis();
System.out.printf("执行时间:%s\n",end-start);
}
}
执行时间:
示例2
:使用多线程
代码如下:
package creat;
public class advertage_Yes {
public static void main(String[] args) {
//分别使用两个线程,每个执行10亿次++
//开始的执行时间
long start = System.currentTimeMillis();
for(int i=0; i<100; i++){
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int j=0;j<10_0000_0000;j++){
}
}
});
}
//这里不能执行记录结束时间(注意:要使用debug运行,不能使用run)
//以下代码的意思,是等两个thread线程执行完,main线程再往下执行
//线程有main,两个new Thread创建的线程,
while(Thread.activeCount() > 1){//activeCount返回活跃线程数
Thread.yield();//main就让步(从运行态转为就绪态)
}
long end = System.currentTimeMillis();
System.out.printf("执行时间:%s\n", end-start);
}
}
运行时间:
可以看出,++
次数很多时,使用多线程效率高一些(由于电脑配置不一样,没有实现次数更多的情况
);
六、理解多线程并发现象
代码如下:
package creat;
public class bingfaPhenomenon {
public static void main(String[] args) {
//设计两个线程,每个循环10次,每次打印一个语句
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
//Thread.currentThread返回这行代码运行时所在的线程引用
//thread.getName返回线程的名称
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.printf("线程: %s, 执行第 %s 次\n", name, i);
}
}
}, "t1");//创建线程时设置第二个参数,表示设置的线程名称
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//Thread.currentThread返回这行代码运行时所在的线程引用
//thread.getName返回线程的名称
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.printf("线程: %s, 执行第 %s 次\n", name, i);
}
}
}, "t2");//创建线程时设置第二个参数,表示设置的线程名称
t1.start();
t2.start();
}
}
显示如下:
可以看出,多个线程代码之间的执行顺序,就是并发并行的执行(也就是具有随机性)