一、进程与线程[了解]
什么是进程?
一个进程就是一个运行的程序(app)
例如: idea,内网通,qq,微信
进程是电脑
资源分配
的最小单位进程中至少包含一个线程
进程中的资源,线程会共享,比如堆中的对象,静态域中的数据
什么是线程?
线程是进程中的一个执行路径
线程是
资源调度
的最小单位在内存栈中的数据,是属于线程的
为什么需要多线程?
因为代码运行是有
顺序
的,执行完前面才能执行后面但是很多时候,需要功能同时执行
即顺序执行的代码,功能太简单,有局限性
多线程,可以实现代码并发执行,即同时执行
提升资源利用率,提升效率
Java代码是多线程的吗?
是,至少有两个
代码运行的main线程
垃圾回收线程
二、创建线程[重点]
main方法是一个线程,我们需要额外单独再开一条独立线程.方式
继承Thread类
实现Runnable接口
实现Callable接口
线程池
2.1 继承Thread类
Thread类,在java中代表一个线程,想要实现多线程,方式1
继承Thread
重写
run
方法,写的是线程的任务创建线程对象
开启线程,调用的是
start
需求: main线程输出1-100,再开一个线程输出101-200,两个线程同时执行
// 线程类
public class MyThread1 extends Thread{
/**
* 重写run方法
* 该方法内,就是新线程的执行任务代码
*/
@Override
public void run() {
for (int i = 101; i < 201; i++) {
System.out.println("新线程--->" + i );
}
}
}
//测试
public class TestThread {
public static void main(String[] args) {
// 创建线程对象
MyThread1 t1 = new MyThread1( );
// 开线程,注意!!! 此处开线程是start方法,不是run()
// 调用start方法,开启新线程,自动调用run方法
t1.start();
//===============================
// main线程
for (int i = 1; i < 101; i++) {
System.out.println("主线程--->" + i );
}
}
}
执行效果,出现main线程和新线程在争抢执行....
匿名内部类实现继承Thread
new Thread(){ // 相对于是在创建子类对象
@Override
public void run() {
for (int i = 201; i < 301; i++) {
System.out.println("线程3--->" + i );
}
}
}.start();
3.2 实现Runnable接口
子类实现Runnable接口
重写run方法
创建子类对象
将子类对象当做参数,传递给Thread类的构造方法,创建出thread对象
使用thread对象开启start
// 子实现类
public class MyThread2 implements Runnable{
@Override
public void run() {
for (int i = 1; i < 101; i++) {
System.out.println("R1 线程执行 -->" + i );
}
}
}
// 测试
public class TestThread2 {
public static void main(String[] args) {
// 创建子类型对象
MyThread2 m2 = new MyThread2( );
// 将子类对象,当参数传递给Thread类构造
Thread t2 = new Thread(m2);
// 开启线程
t2.start();
// =================================
// 主线程
for (int i = 1; i < 101; i++) {
System.out.println("main线程执行 -->" + i );
}
}
}
特别注意,第4步 第5步
匿名内部类,实现RUnnable接口完成多线程
// 使用匿名内部类完成开启多线程
new Thread(new Runnable( ) { // 创建子类对象
@Override
public void run() {
for (int i = 1; i < 101; i++) {
System.out.println("R2 线程执行 -->" + i);
}
}
}).start();
3.3 二者区别
从使用方便程度来说,继承Thread类实现多线程更直接方便点,因为创建对象,直接调用start即可
但是呢,继承Thread类有局限性,类只能单继承,即一个类如果已经继承过别的,此时无法再继承Thread,只能实现Runnable接口开启线程
所以,建议还是使用实现Runnable接口完成多线程
3.4 Callable接口
目前,实现多线程无论是继承Thread,还是实现Runnable接口,都必须重写run方法,但是这个run方法
无返回值
,且无异常抛出,只能捕获
基于以上问题,jdk提供了可以有返回值和抛出异常的开启线程的方案
Callable + FutureTask
// 实现Callable接口
public class MyThread3 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("--- Callable线程开始工作 ---" );
int sum = 0;
for (int i = 1; i < 101; i++) {
sum += i;
}
System.out.println("--- Callable线程结束工作 ---" );
return sum;
}
}
// 测试,将对象交给FutureTask
public class TestThread3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建Callable子实现类对象
MyThread3 c = new MyThread3( );
// 将对象交给FutureTask的构造
FutureTask<Integer> task = new FutureTask<>(c);
// 将task对象,当参数传递给Thread对象.创建Thread
Thread t = new Thread(task);
// 开启线程
t.start();
// 一定注意!!!要线程结束,获得返回值
Integer i = task.get( );
System.out.println("线程返回结果:" + i );
}
}
四、线程的API
已经掌握了开启线程的方式,下面学习线程对象的一些方法,这些方法可以改变线程的运行状态,例如线程名字,运行,停止,加入,阻塞,优先级和守护
4.1 关于线程名字[常用]
线程在创建时,默认都会给其一个指定名字,thread-0,-1,-2
getName()
也可以通过 方法设置名字
setName(String n)
也可以在创建对象时,通过构造方法设置名字
Thread(String name)
public static void main(String[] args) {
// 自定义线程
Thread t1 = new Thread("按摩线程"){
@Override
public void run() {
Thread thread = Thread.currentThread( );
System.out.println(thread.getName()+"自定义线程" );
}
};
// 设置线程名
// t1.setName("洗浴线程");
// 获得线程名
String name = t1.getName( );
System.out.println("name = " + name);
t1.start();
}
4.2 获得线程对象[重要]
JDK提供了一个方法,可以获得当前正在运行的线程对象
Thread Thread.currentThread();
public static void main(String[] args) {
// 自定义线程
Thread t1 = new Thread( ){
@Override
public void run() {
Thread thread = Thread.currentThread( );
System.out.println(thread.getName()+"自定义线程" );
}
};
t1.start();
// 获得主线程对象
Thread main = Thread.currentThread( );
System.out.println("主线程名字 = " + main.getName() );
}
4.3 线程优先级[了解]
线程是有优先级,最高优先级是10,最低优先级是1,默认初创线程优先级是5
getPriority() 获得优先级
setPriority() 设置优先级
public class Demo2 {
public static void main(String[] args) {
Thread t1 = new Thread("T1") {
@Override
public void run() {
for (int i = 1; i < 101; i++) {
System.out.println(getName()+"执行-->" +i );
}
}
};
t1.setPriority(1);
Thread t2 = new Thread("T2") {
@Override
public void run() {
for (int i = 1; i < 101; i++) {
System.out.println(getName()+"执行-->" +i );
}
}
};
t2.setPriority(1);
Thread t3 = new Thread("T3") {
@Override
public void run() {
for (int i = 1; i < 101; i++) {
System.out.println(getName()+"执行-->" +i );
}
}
};
t3.setPriority(10);
t1.start();
t2.start();
t3.start();
}
}
注意: 经过测试,虽然设置优先级,但是效果并不明显,不会保证绝对的顺序,只能是大概率优先..
且main方法只要在最上面先执行,优先级最高!
想要保证绝对的顺序或者交替执行等效果需要后续其他api...
4.4 守护线程
守护线程就是守护别的线程,随着守护的线程消失
需要在开启线程前,设置某一线程为守护线程,其他的线程就是被守护的 ; 即被守护的线程停止了,守护线程会随之停止
例如: qq聊天窗口和主面板,聊天窗口就是守护线程,主面板就是被守护.即聊天窗口可以随意关闭,但是主面板如果推出聊天窗口立即关闭
setDaemon(boolean n); 标记为true即为守护线程
public static void main(String[] args) {
Thread dw = new Thread("大王") {
@Override
public void run() {
System.out.println("大王打仗");
}
};
Thread xb = new Thread("小兵") {
@Override
public void run() {
for (int i = 1; i < 10000; i++) {
System.out.println("小兵" + i + "在攻击...");
}
}
};
// 开启之前,设置小兵线程为守护线程
xb.setDaemon(true);
/**
* 大王线程结束
* 小兵线程发现后,也会结束不再执行完
*/
dw.start();
xb.start();
}
4.5 礼让线程
public class Demo4 {
public static void main(String[] args) {
Thread t1 = new Thread("汽车") {
@Override
public void run() {
for (int i = 1; i < 1001; i++) {
if (i == 100){
Thread.yield();// 让出线程
}
System.out.println("汽车" + i + "执行...");
}
}
};
Thread t2 = new Thread("行人") {
@Override
public void run() {
for (int i = 1; i < 1001; i++) {
System.out.println("行人" + i + "执行...");
}
}
};
t1.start();
t2.start();
}
}
4.6 join(插队)
void join() 等待该线程终止。 (无限期等待) void join(long millis) 等待该线程终止的时间最长为 millis 毫秒。 (有限期等待)
public class Demo5 {
public static void main(String[] args) {
Thread t2 = new Thread("行人") {
@Override
public void run() {
for (int i = 1; i < 1001; i++) {
System.out.println("行人" + i + "执行...");
}
}
};
Thread t1 = new Thread("汽车") {
@Override
public void run() {
for (int i = 1; i < 1001; i++) {
if (i == 100){
try {
//t2.join();// 让线程2加入,直到线程2执行完毕,线程1才会继续
t2.join(1);// 让线程2加入,但是线程2只运行1毫秒,1毫秒后,线程1 2继续争抢
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("汽车" + i + "执行...");
}
}
};
t1.start();
t2.start();
}
}
4.7 sleep(线程休眠)[重要]
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),
public static void main(String[] args) throws InterruptedException {
Thread t2 = new Thread("行人") {
@Override
public void run() {
for (int i = 1; i < 1001; i++) {
System.out.println("行人" + i + "执行...");
}
}
};
Thread t1 = new Thread("汽车") {
@Override
public void run() {
for (int i = 1; i < 1001; i++) {
if (i == 100){
try {
/**
* 让当前线程休眠
* 让出系统资源,别的线程执行
* 等当前线程休眠结束,继续执行
*/
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("汽车" + i + "执行...");
}
}
};
// t1.start();
// t2.start();
for (int i = 10; i > 0; i--) {
System.out.println("倒计时: "+i+ "秒");
Thread.sleep(999);
}
System.out.println("发射!" );
}
4.8 线程停止stop
public static void main(String[] args) {
new Thread( ){
@Override
public void run() {
for (int i = 1; i < 1001; i++) {
if (i == 100) {
// this.interrupt();// 给线程一个中断的信号
this.stop();// 立即让线程停止
}
System.out.println("线程执行 --> " +i );
}
}
}.start();
}
五、线程的状态[面试]
线程的几种状态:
创建/新建/初始
new 完线程对象
就绪
调用start
等待/阻塞
join()或者sleep()
运行
执行run()方法
死亡/销毁/终止
run方法执行完,或者调用stop()或者interrupt()中断等
清楚状态之间的转换
线程对象 new完是没有开启线程
new 完属于初始状态(
New
)调用start了开启线程,如果抢到资源执行了run方法,属于运行状态(
Running
)如果没抢到,属于就绪(
Ready
)状态线程正常执行完就进入死亡(
Terminated
)状态(结束)
如果过程中有sleep,那么当前线程进入等待状态,但是是有期限的等待(
Timed Waiting
),到时进入就绪状态,准备争抢!!如果别的线程使用了join(),那么当前线程会进入无限期等待(
Waiting
)在加锁的情况下,一个线程获得锁执行代码,另外一个线程就进入阻塞状态(
Blocked
)