一、概念
每一个程序运行时,内部可能包含多个顺序执行流,每个顺序执行流就是一个线程。
1.线程与进程
进程是运行中的程序,是具有独立功能的,进程是系统进行资源分配的和调度的一个独立单位。
进程的三个特点:
独立性:进程是系统中独立存在的实体,拥有自己独立的资源,每一个进程都有自己的私有地址空间。
动态性:进程与程序的区别是,程序是一个静态的指令集合,而进程是一个在系统中动态活动的指令集合,有生存周期与不同的状态。
并发性:可以在处理器上并发执行,多个进程间不会相互影响。
线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程,线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不拥有系统资源,它与此父进程的其他线程共享该线程所拥有的全部资源
二、线程的创建和启动
1.继承Thread类
步骤:a.定义Thread子类,并重写该类的run()方法,该方法就是线程需要完成的任务;
b.创建Thread的实例,就创建了线程对象;
c.调用线程的start()方法就启动了线程。
package Thread;
//继承Thread类创建线程
public class FirstThread extends Thread {
//重写run()方法;使用getName()可以直接返回线程的名字
private int i;
public void run() {
for(i=0;i<100;i++) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) throws InterruptedException {
for(int i = 0;i<100;i++) {
//使用currentThread()方法调用当前线程
System.out.println(currentThread().getName()+" "+i);
if(i==50) {
Thread.sleep(1000);
FirstThread th = new FirstThread();
th.start();
FirstThread th1 = new FirstThread();
th1.start();
}
}
}
}
运行结果如下(部分):
Thread-0 98
Thread-0 99该线程共有三个线程:一个main主线程,两个创建的线程;
currentThread()方法是Thread的静态方法,用于返回当前正在执行的线程对象;getName()返回线程的名字;
注意:继承Thread线程类的方法类创建线程时,多线程间时无法共享实例变量的,如上:i为Thred1的实例变量,不是局部变量。
2.实现Runnable接口创建线程
步骤:a.定义Runnable接口的实现类,重写run()方法,此方法也是线程的执行体(是由Thread将其包装为线程执行体的)
b.创建Runnable接口的实例,以此实例作为Thread的target来创建对象,此对象才是真正的线程对象
package Thread;
//事项Runnable接口创建线程
public class RunnableThread implements Runnable {
//重写其run()方法
private int i;
@Override
public void run() {
for(;i<100;i++) {
//由于实现Runnable接口,获取当前对象只能用ThreadRunnable().getName();
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==10) {
//通过new Thread(target,name)创建线程;
RunnableThread rt = new RunnableThread();
new Thread(rt,"线程1").start();
new Thread(rt,"线程2").start();
}
}
}
}
运行结果(部分):
线程1 11
线程1 12
main 18
线程1 13
main 19
main 20
使用Thread与Runnable创建线程的区别:
继承Thread类的子类可代表线程的对象,而实现Runnable接口只能作为线程对象的target;
Runnable接口创建的线程可以共享实例变量,Thread不可以。
3.实现Callable接口与Future创建线程
步骤:a.创建Callable接口,实现call()方法,以call()方法作为线程的执行体,该call()方法具有返回值;
b.使用FutureTask类来包装Callable对象,并且封装了其call方法的返回值,
c.使用FutureTask对象作为Thread对象的target创建并启动线程,
d.调用FutureTask对象的get() 方法获得子线程的执行结束后的返回值。
package Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableThread {
public static void main(String[] args) {
//创建Callable对象
CallableThread ca = new CallableThread();
//使用Lambda表达式创建Callable对象;
//使用FutureTask包装Callable对象
FutureTask task = new FutureTask((Callable)()->{
int i=0;
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"的循环变量I的值"+i);
}
return i;
});
for(int i =0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"的循环变量i的值"+i);
if(i==20) {
new Thread(task,"有返回值的线程").start();
}
}
try {
System.out.println("子线程的返回值:"+task.get());
}catch(Exception e) {
e.printStackTrace();
}
}
}
运行结果(部分):
main的循环变量i的值49
子线程的返回值:100
总结:使用lambda表达式创建Callable对象,就无需先创建Callable实现类,再创建对象了,call()方法允许抛出异常和带返回值。
三种方式创建现成的比较:
继承Thread类与实现Runable、Callable接口都可以创建多线程,使用Runable与Callable接口创建基本方式相同,只是用Callable接口实现有返回值,并且抛出异常。
实现接口与继承Thread的优缺点:实现接口还可以继承其他类;使用接口多个线程可以共享一个target对象,使用与多个线程处理一份资源的情况;缺点是要访问当前
线程就要用Thread.currentThread()方法。
使用Thread的优缺点:编写简单,方法当前线程简单直接用this即可。缺点是不能继承其他类