一、什么是线程?
1)介绍
线程是调度CPU资源的最小单位,线程模型分为KLT模型与ULT模型,JVM使用的KLT模
型,Java线程与OS线程保持1:1的映射关系,也就是说有一个java线程也会在操作系统里有一个对应的线程。
2)Java线程与OS线程保持1:1的映射关系
我们本地执行代码测试一下是否是1:1的关系。初始系统运行的线程是1771个。我们创建一个有300个线程的测试,看结果是什么
public static void main(String[] args) {
for (int i = 0; i < 300; i++) {
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
我们可以发现从2018变到了2351,忽略线程略微的变动,能看到300的一个变动就可,因为系统还运行这其他进程的各种线程。
二、三种创建线程的方式
1)extends Thread
① demo实例
public class CreateThreadTest1 extends Thread{
@Override
public void run() {
System.out.println("通过继承Thread,线程号:" + currentThread().getName());
}
}
public static void main(String[] args) {
Thread thread = new CreateThreadTest1();
thread.start();
}
② 过程描述
2)implements Runnable
① demo实例
public class CreateThreadTest implements Runnable{
@Override
public void run() {
System.out.println("通过实现Runnable,线程号:" + Thread.currentThread().getName());
}
}
public static void main(String[] args) {
Thread thread = new Thread(new CreateThreadTest());
thread.start();
}
② 过程描述
3)implements Callable
① demo实例
public class CreateThreadTest2 implements Callable {
@Override
public Object call() throws Exception {
System.out.println("通过实现Callable,有返回值;线程号:" + Thread.currentThread().getName());
return 10;
}
}
public static void main(String[] args) {
FutureTask futureTask=new FutureTask(new CreateThreadTest2());
Thread thread = new Thread(futureTask);
thread.start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
② 过程描述
- 1.实现Callable类 ,重写call()方法 ,该call()方法将作为线程执行体,并且有返回值。
- 2.创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
- 3.使用FutureTask对象作为Thread对象的target创建并启动新线程。
- 4.调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
③ 两种实现方式
- 1.用FutureTask封装
- 2.线程池调用
public static void main(String[] args) {
//随意创建一个线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future future = executorService.submit(new CreateThreadTest2());
try {
//获取值
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//关闭线程池
executorService.shutdown();
}
三、三种创建线程的优缺点
1)采用继承Thread类方式:
(1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
(2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。
2)采用实现Runnable接口方式:
(1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。
3)Runnable和Callable的区别:
(1)Callable规定的方法是call(),Runnable规定的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
(3)call方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
四、生命状态
1)状态
- NEW,新建
- RUNNABLE,运行
- BLOCKED,阻塞
- WAITING,等待
- TIMED_WAITING,超时等待
- TERMINATED,
2)状态切换图解
3)源码位置
在Thread类里,有一个内部类state,来标明状态
public enum State {
}
五、常用的一些状态转换方法调用
1)由NEW到RUNNABLE的过程
RUNNABLE可以分为两个阶段:就绪状态和运行状态
① 调用thread.run()到就绪状态
②调用thread.start()运行状态
③调用thread.yield()方法由运行状态到就绪状态
public static native void yield();
1、这是一个静态方法,一旦执行,它会使当前线程让出CPU。但要注意,让出CPU并不表示当前线程不执行,当前线程在让出CPU后,还会进行CPU资源的争夺,但是否能够再次别分配到就不一定了。
因此,对于Thread.yield()的调用就好像是说:“我已经完成了一些最重要的工作了,我可以休息一下,可以给其他线程一些工作机会”。
2、因为还会再次抢夺,所以就好比我机会让出去了,其他线程珍不珍惜可就不关我的事了。
3、yield只会让步优先级同级或者高的线程,这就是社会规则。
// 设置优先级:MIN_PRIORITY最低优先级1;NORM_PRIORITY普通优先级5;MAX_PRIORITY最高优先级10
threadA.setPriority(Thread.MIN_PRIORITY);
threadB.setPriority(Thread.MAX_PRIORITY);
4、只会让出CPU资源,但是若持有锁,则不会让出。所以说再AQS中常见,因为本身就是一种乐观锁。
2)由RUNNABLE到WATING状态
① LockSupport.park()
LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语。LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数:
public native void unpark(Thread jthread);
public native void park(boolean isAbsolute, long time);