什么是线程?
可以这么说:操作系统可以同时执行多个任务,每个任务是进程,进程可以执行多个任务,每个任务就是线程。
线程的优势
1、进程之间不能共享内存,但线程之间共享内存非常容易。
2、系统创建进程是需要为该进程 重新分配系统资源,但是创建线程代价小得多。
3、java语言内置了多线程功能支持,而不是单纯的作为底层操作系统的调度方式,从而简化了java的多线程开发。
线程的创建
1、通过继承Thread类创建线程类
(a)定义Thread子类,并重写该类的run()方法。该run()方法的方法体就代表了线程需要完成的任务。因此把run()方法称为线程执行体。
(b)创建Thread子类的实例,即创建了线程对象。
(c)调用线程的start()方法来启动线程。
示例:
package czz;
public class FirstThread extends Thread {
private int i;
//重写run()方法,run()方法的方法体就是线程的执行体
@Override
public void run(){
for (;i<300;i++){
//当线程继承Thread类时,直接使用this获取当前线程,Thread对象的getName()方法返回当前线程的名字
//因此可以直接调用getName()方法返回当前现成的名字
System.out.println(getName() + " " + i );
}
}
public static void main(String[] args) {
for (int i=0;i<300;i++){
//调用Thread的currentThread()方法获取当前线程(本例为主线程)
System.out.println(Thread.currentThread().getName() + " " + i);
if (i==10){
//创建并启动第一个线程
Thread jdwa = new FirstThread();
//为线程设置名字
jdwa.setName("jdwa");
jdwa.start();
//创建并启动第一个线程
Thread czz = new FirstThread();
czz.setName("czz");
czz.start();
}
}
}
}
结果:
"D:...jdk1.8.0_05injava.exe" "-javaagent:D:...IntelliJ IDEA
... ...
FilesJavajdk1.8.0_05jrelib
t.jar;E:...outproductionMyTest" czz.FirstThread
main 0
... ...
main 80
czz 0
... ...
czz 126
main 81
... ...
main 174
jdwa 0
jdwa 1
main 175
... ...
main 299
czz 127
... ...
czz 210
jdwa 2
... ...
jdwa 22
czz 211
... ...
czz 299
jdwa 23
... ...
jdwa 299
Process finished with exit code 0
有上面的示例可以看出,两个线程的循环变量不连续表明他们没有共享数据,因为每次创建线程都需要创建一个Thread对象,因此:
使用Thread类的方法创建线程时,多个线程之间无法共享线程类的实例变量。
2、实现Runnable接口创建线程类
(a)定义Runnable接口的实现类,并重写改接口的run()方法,该run()方法的方法体同样是该线程的执行体。
(b)创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。代码如下所示:
//创建Runnable实现类的实例
SecondThreadst = new SecondThread();
//以Runable实现类的实例作为Thread的target来创建Thread的对象,即线程对象。
new Thread(st);
也可以在创建对象时为该Thread对象指定一个名字:
new Thread(st,"threadName");
(c)调用线程对象的start()方法来启动该线程
示例:
package czz;
public class SecondThread implements Runnable {
private int i;
@Override
public void run(){
for (;i<300;i++){
//当线程类实现Runnable接口时,获取当前线程需要Thread.currentThread()
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i=0;i<12;i++){
//调用Thread的currentThread()方法获取当前线程(本例为主线程)
System.out.println(Thread.currentThread().getName() + " " + i);
if (i==10){
SecondThread target = new SecondThread();
//通过New Thread(target,name)方法创建新线程
new Thread(target,"jdwa").start();
new Thread(target,"czz").start();
}
}
}
}
3、使用Callable和Future创建线程
java5提供了Callable接口,这个接口类似于Runnable接口的增强版。Callable接口提供了一个call()方法作为线程的执行体。call()方法比run()方法功能更强大。
(a)call()方法可以有返回值
(b)call()方法可以声明抛出异常
但是,Callable接口不是Runnable接口的子接口,所以其实现类的实例并不能作为Thread的target。而且call()方法作为线程执行体,如何获取返回值呢?
java5提供了一个Future接口来代表Callable接口call()方法的返回值,并为Future接口提供了一个实现类FutureTask,该实现类不仅实现了Future接口,还实现了Runnable接口,因此可以作为Thread的target。接口有几个公共的方法:
(a)boolean cancle(boolean mayInterruptIfRunning):试图取消Future里关联的Callable任务
(b)V get():call()方法返回值
(c)V get(long timeout,TimeUnit unit):指定时间内call()没有返回值就抛出Timeoutxception
(d)boolean isCanclled():任务正常完成前被取消则返回true
(e)boolean isDone():任务已完成则返回true
创建并启动有返回值线程的步骤:
(a)创建Callable接口的实现类,并实现call()方法。在创建Callable接口的实现类(可以使用lambda表达式)
(b)使用FutureTask类来包装Callable对象,该实现类封装了call()方法返回值
(c)使用FutureTask对象作为Thread的target创建并启动新线程
(d)调用FutureTask对象的get方法获取线程返回值
示例(非Lambda表达式版):
package czz;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThirdThread implements Callable<Integer> {
@Override
public Integer call(){
int i=0;
for (;i<100;i++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
}
public static void main(String[] args) {
ThirdThread callThread = new ThirdThread();
FutureTask<Integer> task = new FutureTask<>(callThread);
for (int i=0;i<500;i++){
System.out.println(Thread.currentThread().getName() + "循环变量i= " + i);
if (i==5){
new Thread(task,"有返回值的线程").start();
}
}
try{
System.out.println("子线程的返回值:"+task.get());
} catch (Exception e){
e.printStackTrace();
}
}
}
示例(Lambda表达式版):
package czz;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThirdThread1 {
public static void main(String[] args) {
FutureTask<Integer> task = new FutureTask<>(()->{
int i=0;
for (;i<100;i++){
System.out.println(Thread.currentThread().getName() + " " + i);
}
return i;
});
for (int i=0;i<500;i++){
System.out.println(Thread.currentThread().getName() + "循环变量i= " + i);
if (i==5){
new Thread(task,"Lambda有返回值的线程").start();
}
}
try{
System.out.println("Lambda子线程的返回值:"+task.get());
} catch (Exception e){
e.printStackTrace();
}
}
}
三种方式的比较
Runnable与Callable接口实现基本一致。这两种方式优缺点:
(a)线程类还可以继承其他类
(b)多个线程可以共享一个target,非常适合多个相同的线程处理同一份资源的情况。从而可以将CPU,代码,数据分开,形成清晰的模型,较好的体现了面向对象的编程思想。
(c)缺点是编程复杂一些,如果需要访问当前线程,需要访问Thread.currentThread()
采用Thread
(a)缺点是不能继承其他父类
(b)优点是编码简单,当前线程this即可。
学无止境。大家可以关注我的微信公众号,方便利用零碎时间互相交流。共勉!
路漫漫其修远兮,吾将上下而求索。。。