线程(Thread)是一个程序内部的一条执行流程。程序中如果只有一条执行流程,那这个程序就是单线程程序。多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU调度执行)。例如电脑下载软件时两个软件可以同时下载。
创建线程
方式一、继承Thread类
定义一个子类继承Thread类,使其成为线程类,然后重写父类中的run=方法,当调用子线程执行时执行的就是run方法中的内容。
示例
package CreateDemo;
public class ThreadDemo1 {
//main方法本身是由一条主线程负责推进执行的,此时还具有t1线程的存在,实现了多线程
public static void main(String[] args) {
//目标:认识多线程,掌握创建线程的方式一:继承Thread类
//4、创建一个线程对象,代表线程
MyThread t1 = new MyThread();
//5、调用start方法启动线程
t1.start();
for (int i = 0; i < 5; i++)
{
System.out.println("主线程输出:"+i);
}
}
}
//1、定义一个子类继承Thread类,成为一个线程类
class MyThread extends Thread {
//2、重写run方法
@Override
public void run() {
//3、在run方法中编写线程要执行的代码
for (int i = 0; i < 5; i++)
{
System.out.println("子线程输出:"+i);
}
}
}
//优点:编码简单
//缺点:线程类已经继承了Thread类,不能继承其他类,不利于功能的扩展
当需要执行多线程时,先创建一个线程类对象,然后调用其的start()方法就可以实现主线程和子线程共同执行。此时主线程和子线程是同步执行的,比如子线程输出一个1接着主线程也输出一个1,但也有某个线程因为执行速度快一次性跑完的情况,可以多跑跑代码测试。
这个创建方式编码简单,但因为类只能继承一个父类,因此不能再继承其他类,不利于功能的扩展。
方式二:实现Runnable接口创建
创建一个类实现Runnable接口,重写Runnable接口中的run方法。当需要多线程执行时,先用Runnable的实现类创建一个线程任务对象,然后用Thread包装线程任务对象使其成为线程,再调用start方法执行Runnable的实现类中的run方法。
package CreateDemo;
public class ThreadDemo2 {
public static void main(String[] args) {
//掌握多线程的创建方式二:实现Runnable接口创建
// 3、创建线程任务类的一个对象代表一个线程任务
Runnable r = new MyRunnable();
//4、创建一个线程对象,代表线程
Thread t1 = new Thread(r);
//5、调用start方法启动线程
t1.start();
for (int i = 0; i < 5; i++)
{
System.out.println("主线程输出:"+i);
}
}
}
//1、定义一个线程任务类实现Runnable接口
class MyRunnable implements Runnable{
//2、重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 5; i++)
{
System.out.println("子线程输出:"+i);
}
}
}
//优点:线程任务类只是实现接口,可以继续继承其他类,实现其他接口,扩展性强
//缺点:需要多一个Runnable对象
当然还有另一种实现Runnable接口的方案,无需创建新的类,直接用匿名内部类的方法实现Runnable接口中的run方法,再包装,执行。也可以直接Thread包装Runnable接口的匿名内部类。
package CreateDemo;
public class ThreadDemo2_2 {
public static void main(String[] args) {
//目标:掌握多线程的创建方式二:使用Runnable接口的匿名内部类创建
Runnable r = new Runnable(){
@Override
public void run() {
for (int i = 0; i < 5; i++)
{
System.out.println("子线程输出:"+i);
}
}
};
//直接在创建Thread对象时,传入Runnable接口的匿名内部类对象
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++)
{
System.out.println("子线程输出2:"+i);
}
}
}).start();
Thread t1 = new Thread(r);
//5、调用start方法启动线程
t1.start();
for (int i = 0; i < 5; i++)
{
System.out.println("主线程输出:"+i);
}
}
}
因为Runnable是一个函数式接口,以上代码还可以通过Lambda进行代码简化。
这样实现多线程可以继续继承其他类,功能延展性比前一个方法好,但要多创建一个Runnable对象。
方式三:实现Callable接口
前两种方案虽然实现了同时进行多个线程,但却不能在合适的时间获取线程的结果。例如我需要一个线程获取1-100之间的数量和,但我却不能判断需要在什么时候获取这个线程的结果,因为几个线程是交替进行的,无法确定哪个位置会跑完哪个线程。此时,我们就需要通过实现Callable接口来创建线程任务对象,Callable接口有独特的优势,它可以获取线程执行完毕后的结果。
步骤:定义一个Callable接口的实现类,创建该实现类的对象,然后再把Callable的实现类对象包装成FutureTask对象(线程任务对象)
package CreateDemo;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadDemo3 {
public static void main(String[] args) {
//掌握多线程的创建方式3:实现Callable接口,方式3的优势,可以获取线程执行完毕后的结果
//3、创建一个Callable接口的实现类对象
Callable<String> c1 = new MyCallable(100);
//4、把Callable对象封装成一个FutureTask对象(线程任务对象),
/**
* 未来任务对象的作用
* 本身是一个Runnable的线程任务对象,可以交给Thread线程对象处理
* 获取线程执行结果后的结果
*/
FutureTask<String> ft = new FutureTask<>(c1); //public FutureTask(Callable<V> callable)
//5、创建一个线程对象,代表线程
Thread t1 = new Thread(ft);
//6、调用start方法启动线程
t1.start();
Callable<String> c2 = new MyCallable(50);
FutureTask<String> ft2 = new FutureTask<>(c2);
Thread t2 = new Thread(ft2);
t2.start();
//获取线程执行结果的结果
try {
//如果发现第一个线程没有执行完毕会让出cpu,等第一个线程执行完毕后才会向下执行
System.out.println(ft.get());
}catch (Exception e) {
e.printStackTrace();
}try {
//如果发现第二个线程没有执行完毕会让出cpu,等第二个线程执行完毕后才会向下执行
System.out.println(ft2.get());
}catch (Exception e){
e.printStackTrace();
}
}
}
//定义一个Callable接口的实现类
class MyCallable implements Callable<String> { //<>内的类型就是call方法的返回值类型
private int n;
public MyCallable(int n) {
this.n = n;
}
//实现call方法,定义线程执行体
@Override
public String call() throws Exception {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += i;
}
return "子线程计算1-"+n+"的和为:"+sum;
}
}
FutureTask对象本身是一个Runnable的线程任务对象,可以交给Thread线程对象处理,当需要获取线程结果时,就调用FutureTask对象的get方法以获得值。
线程对象的常用方法
1、命名
线程对象的默认名为Thread -索引
对线程对象命名有两种方案,一种是直接用线程对象的setName方法,即变量名.setName(“名字”);
还有一种是在子类中写一个有参构造器接收名字,在子类的有参构造器中将名字用super方法传给父类.
2、获取线程对象名
对象名.getName()
package ThreadApiDemo;
public class ThreadApiDemo1 {
public static void main(String[] args) {
Runnable r = new MyThread();
//包装线程任务对象时可以直接用有参构造器命名,Thread有这种构造器
Thread t = new Thread(r,"3号线程");
//目标:搞清楚线程的常用方法
MyThread t1 = new MyThread("1号线程");//有参构造器
//设置名字
// t1.setName("一号线程");
t1.start();
System.out.println("线程名称:"+t1.getName()); //默认线程名称:Thread-索引
MyThread t2 = new MyThread("2号线程");
// t2.setName("二号线程");
t2.start();
System.out.println("线程名称:"+t2.getName());
for (int i = 0; i < 5; i++)
{
System.out.println("主线程输出:"+i);
}
//哪个线程调用了currentThread方法,这个线程对象就拿到哪个线程
Thread m = Thread.currentThread();//主线程
m.setName("主线程");
System.out.println("主线程名称:"+m.getName());//主线程名字(main)
}
}
//1、定义一个子类继承Thread类,成为一个线程类
class MyThread extends Thread {
//2、重写run方法
@Override
public void run() {
//3、在run方法中编写线程要执行的代码
for (int i = 0; i < 5; i++)
{
System.out.println(Thread.currentThread().getName()+"输出:"+i);
}
}
public MyThread(String name)
{
super(name);
}
public MyThread()
{
super();
}
}
3、Sleep方法(线程休眠),可以让线程停止一段你设定的时间然后再继续执行
对象名.sleep(时间) (时间单位是毫秒)
package ThreadApiDemo;
public class ThreadApiDemo2 {
public static void main(String[] args) {
//目标:搞清楚Thread类的Sleep方法(线程休眠)
for (int i = 0; i < 10; i++) {
System.out.println(i);
try {
//让当前执行的线程进入休眠状态,时间到了以后才继续执行
Thread.sleep(1000); //1000ms = 1s
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4、join方法(线程插队)
让这个线程先执行完再执行其他线程
对象名.join()
package ThreadApiDemo;
public class ThreadApiDemo3 {
public static void main(String[] args) {
//目标:搞清楚join方法:线程插队:让调用这个方法的线程先执行完
MyThread2 t1 = new MyThread2();
t1.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "输出:" + i);
if (i==0)
{
try {
t1.join(); //让t1线程先执行完,然后再执行主线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class MyThread2 extends Thread {
//2、重写run方法
@Override
public void run() {
//3、在run方法中编写线程要执行的代码
for (int i = 0; i < 5; i++)
{
System.out.println(Thread.currentThread().getName()+"输出:"+i);
}
}
public MyThread2(String name)
{
super(name);
}
public MyThread2()
{
super();
}
}