一:基本概念
(1)程序:
是为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
(2)进程:
(3)线程:
线程是进程的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程,一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
(4)简而言之:
(5)并发:
指两个或多个事件在同一个时间段内发生。(交替执行)(一个CPU交替执行多个任务)。
(6)并行:
指两个或多个事件在同一时刻发生。(同时发生)(多个CPU同时执行多个任务)。
(6)拓展:
一个Java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然,如果发生异常,会影响主线程。
(7)多线程的优点:
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率。
改善程序结构。将既长又复杂的进城分为多个线程,独立运行,利于理解和修改。
(8)何时需要多线程:
程序需要同时执行两个或多个任务。
- 程序需要实现一些需要等待的任务时,如用户输入、文件读写、网络操作、搜索等。
-
二:线程的创建和使用(重点)
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现。
(1)Thread类的特性:
java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类。
- 每个线程都是通过某个特定的Thread对象的run()方法来完成操作的经常把run()方法的主体称为线程体。
- 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()。
(2)Thread类中常用的方法:
static Thread currentThread();//静态方法。返回当前正在执行的线程对象的引用。可以先获取到当前正在执行的线程,再使用线程中的方法getName()获取线程的名称。
public static void sleep(long millis);
//使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。毫秒数结束之后,线程继续执行。使用方式:Thread.sleep(毫秒数)。
void start();//启动线程,并执行对象的run()方法。
void run();//线程被调度时执行的操作
String getName();//返回线程的名称
void setName();//设置线程的名称
void yield();//释放当前CPU的执行权(当然,有可能在下一刻再次抢到执行权)
void join();//在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完毕,线程a才结束阻塞状态。
void stop();//强制线程生命周期结束,不推荐使用。(此方法已经过时)
sleep(long millistime);//让当前线程“睡眠”指定的毫秒数。在指定的毫秒时间内,当前线程处于阻塞状态。
boolean isAlive();//判断当前线程是否存活。
代码案例:
class MyThread extends Thread{
//run();设置线程任务
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
try {
//sleep();让当前线程“睡眠”指定的毫秒数
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//currentThread();返回当前线程.
//getName();返回线程的名称.
System.out.println(Thread.currentThread().getName()+"---"+i);
}
if(i % 20 == 0){
//释放当前CPU的执行权(当然,有可能在下一刻再次抢到执行权)
yield();
}
}
}
//使用构造器给线程命名
public MyThread(String name){
super(name);
}
}
//Thread类中常用方法测试
public class ThreadMethodTest {
public static void main(String[] args) {
MyThread mt = new MyThread("线程1");
//setName();设置线程的名称
//mt.setName("线程1");
//start();启动线程,并执行对象的run()方法。
mt.start();
//给主线程命名
Thread.currentThread().setName("主线程");
for (int i = 0; i < 100; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
if (i == 20){
try {
//join();在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完毕,线程a才结束阻塞状态。
mt.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//boolean isAlive();判断当前线程是否存活。
System.out.println(mt.isAlive());
}
}
(3)创建多线程的两种方法:
A:第一种方法实现步骤:
创建一个Thread类的子类。
- 在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做的事)。
- 创建Thread类的子类的对象。
- 通过此对象调用Thread类中的start方法,开启新的线程,执行run方法。
- start()的两个作用(1.启动当前线程。2.调用当前线程的run()方法。)
案例代码:
//遍历100所有偶数案例
public class ThreadText {
//1.创建一个Thread类的子类。
static class MyThread extends Thread{
//2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做的事)。
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
public static void main(String[] args) {
//3.创建Thread类的子类的对象。
MyThread thread = new MyThread();
//4.通过此对象调用Thread类中的start方法,开启新的线程,执行run方法。
thread.start();
}
}
多线程创建过程中两个问题的说明:
- 不能直接调用run()方法来启动线程,如果这么做,那么它只是调用了run()方法,却没有开启新的线程,run()方法中的任务依旧是在main()主线程中执行的。
-
B:第二种方法:(实现Runnable接口)
java.lang.Runable
Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义为一个称为run的无参数方法。
java.lang.Thread构造方法:
Thread(Runnable target):分配新的Thread对象。
Thread(Runnable target,String name):分配新的Thread对象。
实现步骤: 创建一个Runnable接口的实现类。
- 在实现类中重写Runnable接口的run方法,设置线程任务
- 创建一个Runnable接口的实现类对象
- 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
- 调用Thread类中的strat方法,开启新的线程执行run方法。
代码案例:
//1.创建一个Runnable接口的实现类。
class MyRunnable implements Runnable{
//2.在实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
public class RunnableTest {
public static void main(String[] args) {
//3.创建一个Runnable接口的实现类对象
MyRunnable mr = new MyRunnable();
//4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t = new Thread(mr);
//5.调用Thread类中的strat方法,开启新的线程执行run方法。
t.start();
}
}
两种创建多线程方式的比较:
开发中,优先选择:实现Runnable接口的方式。
原因:
- 实现的方式没有类的单继承的局限性
- 实现的方式更适合来处理多个线程有共享数据的情况
联系:Thread类也实现了Runnable接口
相同点:两种方式都需要重写run();将线程要执行的逻辑声明在run()中。
拓展:匿名内部类方式实现线程的创建
匿名:没有名字
内部类:写在其他类内部的类
匿名内部类的作用:简化代码
- 把子类继承父类、重写父类的方法、创建子类对象合成一步完成。
- 把实现类接口,重写接口中的方法,创建实现类对象合成一步完成。
- 匿名内部类的最终产物:子类/实现类对象,而这个类没有名字。
代码案例
new Thread(){
@Override
public void run() {
//设置线程任务(开启线程要做的事)
}
}.start();
三:线程的优先级问题
(1)线程调度:
分时调度:
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度:
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用的为抢占式调度。