一:基本概念
1.程序,进程,线程的理解
- 程序(program)是为完成指定任务,用某种语言编写的一组指令集合.指一段静态的代码.
- 进程(process)是正在运行的一个程序.是一个动态的过程.
- 线程(thread)是一个程序内部的一条执行路径.
2.单核CPU,多核CPU的理解
- 单核CPU,实质是一种假的多线程,在一个时间单元内,也只能执行一个线程的任务.
- 多核CPU是多线程,每个核单独执行一个线程的任务.
- 一个java应用程序java.exe,至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程.
3.并行与并发
- 并行:多个CPU同时执行多个任务.(多个人同时做不同的事)
- 并发:一个CPU(采用时间片)同时执行多个任务.(多个人做同一件事)
4.多线程优点
- 提高应用程序的响应.
- 提高CPU的利用率
- 改善程序结构
5.何时需要多线程
- 程序需要同时执行两个或多个任务.
- 程序需要实现一些需要等待的任务.
- 需要一些后台运行的程序时.
二:线程的创建和使用
JVM允许程序运行多个线程,通过java.lang.Thread类体现.JDK1.5之前有两种方式创建线程: 继承Thread类 和 实现Runnable接口.
1.继承Thread类
-
Thread类特性
- 每个线程都是通过某个特定Thread对象的run()方法完成操作的,常把run()方法的主体称为线程体.
- 通过Thread对象的start()方法启动线程,不是直接调用run()方法
-
Thread类构造器
- Thread(): 创建Thread对象
- Thread(String threadname): 创建线程并指定线程实例名
- Thread(Runnable target): 创出线程的目标对象,它实现了Runnable接口的run()
- Thread(Runnable target, String name): 创建新的Thread对象
-
流程
- 继承Thread
- 重写Thread中的run()
- 创建线程对象
- 调用对象的start(): 启动线程,调用run()
-
示例
//1. 创建一个继承于Thread类的子类 class MyThread extends Thread { public MyThread(){} public MyThread(String name){ super(name); } //2. 重写Thread类的run() @Override public void run() { for (int i = 0; i < 100; i++) { if (i % 2 == 0) { /*public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); }*/ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest { public static void main(String[] args) { //3. 创建Thread类的子类的对象 // MyThread t1 = new MyThread(); MyThread t1 = new MyThread("分线程"); //设置线程名称 // t1.setName("线程一"); //4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run() t1.start(); //问题一:我们不能通过直接调用run()的方式启动线程。 // t1.run(); //问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。 /*if (threadStatus != 0) throw new IllegalThreadStateException();*/ // t1.start(); // java.lang.IllegalThreadStateException //我们需要重新创建一个线程的对象 MyThread t2 = new MyThread(); t2.start(); Thread.currentThread().setName("主线程"); //如下操作仍然是在main线程中执行的。 for (int i = 0; i < 100; i++) { if (i % 2 == 0) { System.out.println(Thread.currentThread().getName() + ":" + i); } } } }
-
注意点
- 对象调用run()方法,没有启动多线程模式,
- run()方法由JVM掉用,什么时候,执行过程都由CPU调度决定.
- 启动多线程,必须调用start()方法.
- 一个线程对象不能重复调用start()方法,否则发生 “线程状态不符” 异常
2.实现Runnable接口
-
流程
- 实现Runnable接口.
- 重写Runnable接口中的run()方法.
- 通过Thread(Runnable target)构造器创建线程对象.
- 调用start()方法: 开启线程,调用Runnable子类接口的run()方法.
-
示例
//1. 创建一个实现了Runnable接口的类 class MyThread2 implements Runnable{ //2. 实现类去实现Runnable中的抽象方法:run() @Override public void run() { for (int i = 0; i <= 100 ; i++) { if (i % 2 == 0){ System.out.println(Thread.currentThread().getName()+":"+i); } } } } public class ThreadTest2 { public static void main(String[] args) { //3. 创建实现类的对象 MyThread2 r1 = new MyThread2(); //4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象 Thread t1 = new Thread(r1); t1.setName("线程1"); //5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run() // -->调用了Runnable类型的target的run() t1.start(); //再启动一个线程,遍历100以内的偶数 Thread t2 = new Thread(r1); t2.setName("线程2"); t2.start(); } }
3.两种方式的联系与区别
public class Thread extends Object implements Runnable
- 联系: 都需要重写run()方法,将线程要执行的逻辑都声明在run()中
- 实现Runnable接口方式的好处
- 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类的对象,适合处理多个线程有共享数据的情况
三:Thread类的相关方法
- void start(): 启动当前线程,并执行当前线程的run()方法
- run(): 被CPU调度时执行的操作,将创建的线程要执行的操作声明在此方法中
- String getName(): 返回线程的名称
- void setName(String name): 设置线程的名称
- static Thread currentThread(): 返回执行当前代码的线程. 在Thread子类中指this, 通常用于主线程和Runnable实现类.
- static void yield(): 释放当前cpu的执行权
- join(): 在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
- static void sleep(long millitime): 让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态
- boolean isAlive(): 判断当前线程是否存活
四:线程的优先级及分类
- 调用策略: 时间片(单元时间内做任务切换) 或 抢占式(高优先级的线程抢占CPU)
- 调度方法: 同优先级线程组成先进先出队列(先到先服务),使用时间片策略; 对高优先级,使用抢占式策略
- 线程优先级
- MAX_PRIORITY: 10; MIN_PRIORITY: 1; NORM_PRIORITY: 5;(默认优先级)
- getPriority(): 返回线程等级值; setPriority(int newPriority): 设置线程等级值
- 说明: 线程创建时继承父线程的优先级; 低优先级只是获得调度的概率低,并不一定在高优先级线程之后才被调用
- 线程分类
- 分为守护线程 和 用户线程.
- 守护线程是用来服务用户线程的,在start()方法前调用thread.setDaemon(true)可以把一个用户线程变为一个守护线程.
- java垃圾回收就是一个守护线程.若JVM中都是守护线程,当前JVM将退出.
示例:
class HelloThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
// try {
// sleep(10);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
}
// if(i % 20 == 0){
// yield();
// }
}
}
public HelloThread(String name){
super(name);
}
}
public class ThreadMethodTest {
public static void main(String[] args) {
HelloThread h1 = new HelloThread("Thread:1");
// h1.setName("线程一");
//设置分线程的优先级
h1.setPriority(Thread.MAX_PRIORITY);
h1.start();
//给主线程命名
Thread.currentThread().setName("主线程");
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
}
// if(i == 20){
// try {
// h1.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
}
// System.out.println(h1.isAlive());
}
}
五:示例
/**
* 示例:创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
*
* @author zlg
* @create 2019-10-25 1:56
*/
public class ThreadDemo {
public static void main(String[] args) {
// MyThread1 m1 = new MyThread1();
// MyThread3 m2 = new MyThread3();
// m1.start();
// m3.start();
// 方式二:采用匿名内部类
//创建Thread类的匿名子类的方式:遍历偶数
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
//创建Thread类的匿名子类的方式:遍历奇数
new Thread(){
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}.start();
}
}
//方式一:采用两个类分别执行不同任务
// 遍历100以内偶数
class MyThread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
// 遍历100以内奇数
class MyThread3 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}