Java多线程概述与实现
1. 进程与线程
1.1 进程与线程定义
-
进程:一个程序的执行周期;
-
线程:一个程序执行多个任务,每一个任务就叫做一个线程;
-
两者区别
- 开销:进程:当打开一个应用程序,比如打开多个IDEA,每次打开都需要消耗内存和耗费时间;线程:打开一个应用程序,线程是在这个进程中消耗资源,所以开销较小;
- 数据共享:线程是在一个进程中创建的,所以线程之间可以共享数据,通信更加方便、有效;进程之间进行通信需要通过网络中的TCP协议;
- 多线程的应用:比如打开一个浏览器,你可以边下载东西,边浏览网页;
- 高并发(多个线程):一个系统有大量用户访问;
- 高并发带来的问题:服务器内存不够用,程序资源竞争,无法处理新的请求。
1.2 线程状态
(1)当创建一个线程,不会立即执行,因为它要获得CPU的时间片,这要依靠操作系统的系统调度;
(2)比如:当创建一个线程,先进入就绪状态,若CPU时间片为1微妙,这时候线程没有执行完,则由运行状态转换为就绪状态继续等待系统调度;
(3)当系统缺少某个资源时,比如存在输入输出时,线程则由运行状态变为阻塞状态,阻塞解除变为就绪状态,等待CPU分配时间片。
2. Java多线程的实现
2.1 直接继承Thread类启动线程
java.lang.Thread是一个线程操作的核心类。新建一个线程最简单的方法就是直接继承Thread类,然后覆写该类中的run()方法。
public class MyThread extends Thread{
private String message;
public MyThread(String message){
this.message = message;
}
@Override
public void run() { //覆写Thread中的run()方法
System.out.println(message);
}
public static void main(String[] args) {
MyThread myThreadA = new MyThread("线程A");
MyThread myThreadB = new MyThread("线程B");
MyThread myThreadC = new MyThread("线程C");
myThreadA.start(); //调用run()方法
myThreadB.start();
myThreadC.start();
}
}
/*
线程A
线程C
线程B
*/
== 为什么要通过start()方法来调用run()方法,而不是run()直接执行?==
通过查看start源码分析:
- start()方法中先调用了start0()方法,而这个方法是一个只声明未实现的方法,并且使用native关键字进行定义;native指的是调用本机的原生系统函数。
private native void start0();
- Thread类有个registerNatives 本地方法,该方法主要的作用就是注册一些本地方法供Thread使用,如start0(),stop()等。这个方法放在一个static语句块中,当该类被加载到JVM中的时候,它就会被调用,进而注册相应的本地方法。
所以在Java线程调用run()方法,实际上会调用JVM_StartThread方法。 - 因此,通过以上,可知Java线程创建流程如下:
2.2 Runnable()接口实现多线程
Runnable接口中有一个抽象方法run(),Thread类中的run()方法就是实现的Runnable接口。Thread类提供了构造方法可以接受Runnable接口对象。
举例:使用Runnable方法实现多线程
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("自定义的Runnable接口实现类:"+LocalDateTime.now());
}
public static void main(String[] args) {
Runnable runnable = new MyRunnable();
Thread threadA = new Thread(runnable);
threadA.start();
Thread threadB = new Thread(runnable);
threadB.start();
}
}
/**
自定义的Runnable接口实现类:2019-03-25T22:31:15.476
自定义的Runnable接口实现类:2019-03-25T22:31:15.476
*/
通常情况下,对于Runnable接口对象可以采用匿名内部类或者Lambda表达式来定义。
举例:使用匿名内部类和Lambda表达式定义
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("自定义的Runnable接口实现类:"+LocalDateTime.now());
}
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread1");
}
});
thread1.start();
Thread thread2 = new Thread(()-> System.out.println("Thread2"));
thread2.start();
}
}
/**
Thread1
Thread2
*/
2.3 Thread与Runnable区别
- 使用Thread类 :只能单继承
- 使用Runnable,可以解决单继承的缺陷,因为Runnable是一个接口,所以可以实现数据共享
举例:利用Thread实现一个买票
public class MyTickThread extends Thread {
private int tick = 10;
@Override
public void run() {
while (this.tick > 0){
this.tick--;
System.out.println("剩余票数:"+this.tick);
}
}
public static void main(String[] args) {
/*
* 每实例化一个对象,调用本类方法都是独立的,不能实现数据共享
*/
new MyTickThread().start();
new MyTickThread().start();
new MyTickThread().start();
}
}
举例:利用Runnable实现买票的例子
public class MyTickRunnable implements Runnable{
private int tick = 10;
@Override
public synchronized void run() {
while(this.tick > 0){
this.tick--;
System.out.println("剩余票的数量:"+this.tick);
}
}
/**
* 因为target是Runnable类型,每次访问的是同一个类中的方法,实现数据共享
*/
public static void main(String[] args) {
Runnable target = new MyTickRunnable();
new Thread(target).start();
new Thread(target).start();
new Thread(target).start();
}
}