**一、线程概述**
进程:一个在内存中运行的应用程序。
线程:进程上的一个执行单元,一个进程可以有多个线程。
多进程:在操作系统中同时运行多个程序。
多线程:在同一应用程序中有多个执行单元同时执行
线程的生命周期:—个线程从创建到执行完的整个过程
多线程能解决什么问题:多线程能并发执行程序,提高程序的运行效率。
jvm就是一个进程:
守护线程(垃圾回收),主线程(main函数)
二、如何创建线程对象:
第一种方法:继承Thread类:创建一个线程的子类去继承线程类,因为线程类没有实现类,无法实现功能。同时子类需要重写run()
方法。想要线程跑起来,代码必须写在run()
方法中。
开启线程用start()方法。
在一个线程中开启另外一个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同。不过主线程先启动占用了cpu资源,因此主线程总是优于子线程。如下面的main线程与thread线程
public class ThreadDemo01 {
public static void main(String[] args) {
//创建线程对象,新建状态
ThreadImpl thread = new ThreadImpl();//新建状态
//开启线程,会让线程进入就绪状态
//就绪状态:拥有争夺CPU时间片的权利
thread.start();
//把遍历操作放一份在主线程
for (int i = 0;i<100;i++){
System.out.println("主线程"+i);
}
}
}
/**
* 子类继承Thread后run()必须重写
*/
class ThreadImpl extends Thread{
@Override
public void run() {//执行到run方法 多线程处于运行状态
for (int i = 0;i<100;i++){
//获取当前线程的线程名称
System.out.println(currentThread().getName()+"--"+i);
}
}
}
package com.jy.Thread;
public class ThreadDemo02 {
public static void main(String[] args) {
CreateThread createThread1 = new CreateThread();
//设置线程名称
createThread1.setName("分支线程-1");
CreateThread createThread2 = new CreateThread();
createThread2.setName("分支线程-2");
CreateThread createThread3 = new CreateThread();
createThread3.setName("分支线程-3");
createThread1.start();
createThread2.start();
createThread3.start();
}
}
class CreateThread extends Thread{
@Override
public void run() {
for (int i = 0;i<100;i++){
//返回的是当前线程的对象
Thread thread = Thread.currentThread();
//获取当前线程的线程名称
System.out.println(thread.getName());
}
}
}
ps:使用睡眠可以使进程进入阻塞状态,阻塞状态会释放cpu时间片
如:Thread.sleep(100);
多线程中主线程与子线程执行的顺序:https://www.yuque.com/wenbusheng-5qk2j/is5soq/qb50a7/edit
第二种方法:实现Runnable接口
/**
* 创建线程的第二种方式,实现Runnable接口
* 实现接口Runnable的方式来创建线程对象,但要注意所创建的对象new CreateThread01()本身不是线程对象
* 创建线程对象:Thread thread1 = new Thread(new CreateThread01());
*/
public class ThreadDemo03 {
public static void main(String[] args) {
//创建线程对象
Thread thread1 = new Thread(new CreateThread01());
Thread thread2 = new Thread(new CreateThread01());
Thread thread3 = new Thread(new CreateThread01());
thread1.start();
thread2.start();
thread3.start();
}
}
//虽然实现了Runnable接口,但并不是线程类,只是一个实现类,因为Runnable不是线程接口
class CreateThread01 implements Runnable{
@Override
public void run() {
}
}
第三种方法:通过匿名内部类重写runnable里的run方法的方式创建线程对象
/**
* 通过匿名内部类的方式创建线程对象
*/
public class ThreadDemo04 {
public static void main(String[] args) {
//创建线程对象
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
}
});
thread.start();
}
}
第四种方法 :使用Callable结合Future实现多线程编程。(很重要)
可以获得返回值,前两种没有返回值。
实现方式: **FutureTask futureTask = new FutureTask(new Callable())
FutureTask是继承与Future的**
缺点:在获取线程返回值之前,可以能造成主线程阻塞
package com.jy.Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadDemo08 {
public static void main(String[] args) {
//创建一个任务类对象futureTask 不是线程对象
FutureTask futureTask = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
int num = 0;
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
num+=i;
}
return num;
}
});
//创建线程对象
Thread thread = new Thread(futureTask);
thread.setName("t1");
thread.start();
//获取线程的返回值
try {
Object o = futureTask.get();
System.out.println("t1线程的返回值结果为"+o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("主线程结束");
}
}
上述代码中,在获得返回值num后,才会输出“主线程结束”,即主线程被阻塞。
三、线程的生命周期
1、新建状态:线程对象被创建
2、就绪状态:线程对象调用start方法后就会处于就绪状态,拥有争夺cpu时间片的权利
3、运行状态:当某一个线程争夺到cpu使用权后,就会执行run方法,执行run方法时就处于运行状态
4、阻塞状态:当某一个线程处于运行态时,发生了睡眠sleep或者控制台打印等需要等待的操作,这时线程就会进入阻塞状态,释放cpu使用权
5、死亡状态:当run方法执行结束后,即死亡状态。
**四、线程在jvm内存上的分布
**
1、每一个线程对应一个栈(如主线程main占用一个栈)
2、栈的资源是不共享的
3、堆的资源是共享的,因为new出的对象都在堆中开辟空间,堆只有一个。
**五、线程调度模型
**
抢占式调度模型,优先级高的线程抢到cpu时间片的概率更高。Java就是抢占式。
均分式调度模型,平均分配时间片。
Java中线程优先级默认为5.所有线程争夺CPU的概率相同。
getPriority()方法,获得线程优先级。
**六、yield() 让位方法
**
暂停当前正在执行的线程对象,并执行其他对象
yield()方法的执行会让当前线程从运行状态回到就绪状态。
使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。
但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。