1、JDK版本的选择
选择JDK8、JDK11进行讲解的原因:Oracle长期支持
2、进程和线程的区别
进程和线程的由来
3、进程与线程的区别
进程是资源分配的最小单位,线程是cpu调度的最小单位.
- 所有与进程相关的资源,都被记录在PCB(进程控制块)中。
- 进程是抢占处理机的调度单位;线程属于某个进程,共享其资源。
- 线程只由堆栈寄存器、程序计数器和TCB组成。
进程是资源分配的基本单位,所有与进程有关的资源都记录在进程控制块PCB中,以表示进程拥有这些资源或者正在使用它们,进程也是抢占处理机的调度单位,它拥有完整的虚拟内存地址空间,当进程发生调度时,不同的进程拥有不同的地址空间,而同一进程内的不同线程共享同一地址空间。与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其它线程共享进程的资源。
总结:
- 线程不能看做独立应用,而进程可看做独立应用。
- 进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径。
- 线程没有独立的地址空间,多进程的程序比多线程程序健壮。
- 进程的切换比线程的切换开销大。
4、Java进程和线程的关系
- Java对操作系统提供的功能进行封装,包括进程和线程。
- 运行一个程序会产生一个进程,进程包含至少一个线程。
- 每个进程对应一个 JVM 实例,多个线程共享 JVM 里的堆。
- Java采用单线程编程模型,程序会自动创建主线程。
- 主线程可以创建子线程,原则上要后于子线程完成执行
获取当前线程名称:
System.out.println("Current Thread: "+Thread.currentThread().getName());
5、Thread中的start和run方法的区别
- 调用 start()方法会创建一个新的子线程并启动
- run( )方法只是 Thread的一个普通方法的调用
创建一个新线程
public class Test {
private static void attack() {
System.out.println("Current Thread is :" + Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread t = new Thread() {
// 新线程run()需要执行的内容
public void run() {
attack();
}
};
System.out.println("main Thread :" + Thread.currentThread().getName());
// 调用start方法,开启新线程
t.start();
}
}
结果:
main Thread :main
Fight
Current Thread is :Thread-0
5、Thread和Runnable的关系
Thread是一个类,实现了Runnable接口
我们可以打开其源码,发现Thread类实现了Runnable接口,进入Runnable接口发现其中只有一个抽象的run方法,也就说明了这个接口并不具备多线程的特性,是依赖Thread中的start的方法去创建一个子线程,再在子线程中调用一个thread实现好了的run方法去执行相应的业务逻辑.才能让类具备多线程的特性。
Thread
public class Thread implements Runnable {
public synchronized void start() {
}
}
Runnable
public interface Runnable{
public abstract void run( );
}
Thread和 Runnable是什么关系
- Thread是一个类,实现Runnable接口,里面含有start( )方法。
- Runnable是一个接口,里面只有run( )方法。
- Thread是实现了 Runnable接口的类,使得run支持多线程。
- 因类的单一继承原则,推荐多使用 Runnable接口。
也就是线程需要执行,需要run( )方法,也就是新建线程的执行逻辑。还需要start( )方法,调用系统创建多线程的方法。也就是 start( )方法让系统创建一个新的线程执行run( )方法中逻辑。
extends Thread:内部有继承的run( )方法和start( )方法。可以自己执行。
implement Runnable:内部只有run( )方法。需要调用Thread中的start ( )才能执行。
6、创建多线程的方式
继承Thread方式(extends Thread)
自定义线程类:
public class MyThread extends Thread {
/**
* 利用继承中的特点 * 将线程名称传递 进行设置
*/
public MyThread(String name) {
super(name);
}
/**
* 重写run方法 * 定义线程要执行的代码
*/
public void run() {
for (int i = 0; i < 20; i++) {
//getName()方法 来自父亲
System.out.println(getName() + i);
}
}
}
测试类:
public class Demo {
public static void main(String[] args) {
System.out.println("这里是main线程");
MyThread mt = new MyThread("小强");
mt.start();//开启了一个新的线程
for (int i = 0; i < 20; i++) {
System.out.println("旺财:"+i);
}
实现Runnable方式(implements Runnable)
定义实现类:
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
定义测试类:
public class Demo {
public static void main(String[] args) {
//创建自定义类对象 线程任务对象
MyRunnable mr = new MyRunnable();
//创建线程对象
Thread t = new Thread(mr, "小强");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("旺财 " + i);
}
7、如何给run( )方法传参
- 构造函数传参
- 成员变量传参
- 回调函数传参
8、如何实现处理线程的返回值
实现的方式
- 主线程等待法
- 使用Thread类的join()阻塞当前线程等待子线程处理完毕
- 通过Callable接口:通过FutureTask或者线程池实现
8,1主线程等待
当主线程没有获取到值时,主线程等待子线程完成赋值。、
优点:实现简单,
缺点:
- 自己需要实现循环等待逻辑,等待变量越多,代码异常臃肿。
- 循环多久是不确定的。
子线程:
public class CycleWait implements Runnable {
private String name;
@Override
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
name = "we have date now";
}
}
主线程:
// 主线程
public static void main(String[] args) throws Exception {
CycleWait cw = new CycleWait();
Thread t = new Thread(cw);
t.start();
// 当辅助线程中name没有值时,主线程等待
while (cw.name == null) {
Thread.sleep(100);
}
System.out.println(cw.name);
}
8.2 使用Thread类的join()阻塞当前线程等待子线程处理完毕
- 使用Thread里面的join( )方法,替代主线程等待法代码。
- 优点:更精准控制,代码更简单。
- 缺点:力不不够细,不能指定等待某个子线程完成再执行
替换为:t.join( )等待该线程终止。
// 主线程
public static void main(String[] args) throws Exception {
CycleWait cw = new CycleWait();
Thread t = new Thread(cw);
t.start();
当辅助线程中name没有值时,主线程等待
t.join();
System.out.println(cw.name);
}
8.3 通过Callable接口:通过FutureTask或者线程池实现
子线程:
import java.util.concurrent.Callable;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
String value = "test";
System.out.println("Ready to work");
Thread.sleep(5000);
System.out.println("task done");
return value;
}
}
使用FutureTask方式:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class FutureTaskDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task = new FutureTask<String>(new MyCallable());
//启动线程
new Thread(task).start();
// 线程等待
if (!task.isDone()){
System.out.println("wait");
}
System.out.println("task return="+task.get());
}
}
执行结果:
使用线程池方式:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1、创建线程池
ExecutorService pool = Executors.newCachedThreadPool();
// 2、获取返回的结果
Future<String> result = pool.submit(new MyCallable());
// 3、线程等待
if (!result.isDone()){
System.out.println("wait ..");
}
try {
// 4、获取返回值
System.out.println(result.get());
} catch (Exception e) {
e.printStackTrace();
}finally {
// 5、关闭线程池
pool.shutdown();
}
}
}
9、线程状态
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
TimedWaiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep. |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
无限等待(Waiting)
没有设置 Timeout参数的 object.wait( )方法。
没有设置 Timeout参数的 Threadjoin( )方法。
LockSupport.park( )方法。
限期等待(Timed Waiting)
Thread.sleep( )方法。
设置了 Timeout参数的 Object.wait( )方法。
设置了 Timeout参数的 Thread.join( )方法。
LockSupport.parkNanps( )方法。
LockSupport parkUntil( )方法。
10、Sleep和Wait的区别
基本的差别
- sleep是 Thread类的方法,wat是Object类中定义的方法
- sleep( )方法可以在任何地方使用
- wait( )方法只能在 synchronized方法或 synchronized块中使用
最主要的本质区别
- Thread.sleep只会让出CPU,不会导致锁行为的改变。
- Object.wait不仅让出CPU,还会释放已经占有的同步资源锁。
public class WaitSleepDemo {
public static void main(String[] args) {
//创建lock锁对象
final Object lock = new Object();
//启动线程A
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread A is waiting to get lock");
synchronized (lock){
try {
System.out.println("thread A get lock");
Thread.sleep(20);
System.out.println("thread A do wait method");
lock.wait(1000);
System.out.println("thread A is done");
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();
//线程等待10s让线程A,执行到wait( );
Thread.sleep(10);
//启动线程B
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread B is waiting to get lock");
synchronized (lock){
try {
System.out.println("thread B get lock");
System.out.println("thread B is sleeping 10 ms");
Thread.sleep(10);
System.out.println("thread B is done");
} catch (InterruptedException e){
e.printStackTrace();
}
}
}
}).start();
}
}
执行结果:
thread A is waiting to get lock
thread A get lock
thread B is waiting to get lock
thread A do wait method
thread B get lock
thread B is sleeping 10 ms
thread B is done
thread A is done
11、notify和norifyAll的区别?
两个概念:
- 锁池(EntryList)
- 等待池(WaitSet)
锁池:
假设线程A已经拥有了某个对象(不是类)的锁,而其它线程B、C想要调用这个对象的某个 synchronized方法(或者块),由于B、C线程在进入对象的 synchronized方法(或者块)之前必须先获得该对象锁的拥有权,而恰巧该对象的锁目前正被线程A所占用,此时B、C线程就会被阻塞,进入一个地方去等待锁的释放,这个地方便是该对象的锁池
等待池(Object.wait( )):
假设线程A调用了某个对象的wat()方法,线程A就会释放该对象的锁,同时线程A就进入到了该对象的等待池中,进入到等待池中的线程不会去竞争该对象的锁。
notify和norifyAll的区别?
- notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会。
- notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会。
12、yield
public static native void yield() 暂停当前正在执行的线程对象,并执行其他线程。
概念
当调用 Thread.yield( )函数时,会给线程调度器一个当前线程愿意让出CPU使用的暗示,但是线程调度器可能会忽略这个暗示。
public class YieldDemo {
public static void main(String[] args) {
Runnable yieldTask = new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
if (i == 5) {
Thread.yield();
}
}
}
};
Thread t1 = new Thread(yieldTask, "A");
Thread t2 = new Thread(yieldTask, "B");
t1.start();
t2.start();
}
}
执行结果:
请求让出CPU执行的,不一定会被采纳,可能会让你继续执行(B1后还是B线程 的B2),也可能让出(B5后换成A线程的A1)。
B1
B2
B3
B4
B5
A1
B6
A2
A3
A4
A5
A6
A7
A8
A9
A10
13、如何中断线程
已经被抛弃的方法
- 线程A通过调用stop( )方法停止线程B。
- 通过调用 suspend( )和 resume( )方法
目前使用的方法
调用 interrupt0,通知线程应该中断了
- ①如果线程处于被阻塞状态,那么线程将立即退出被阻塞状态,并抛出一个 InterruptedException异常。
- ②如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。
需要被调用的线程配合中断。
- ①在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
- ②如果线程处于正常活动状态,那么会将该线程的中断标志设置为true。被设置中断标志的线程将继续正常运行,不受影响。
代码:
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
Runnable interruptTask = new Runnable() {
@Override
public void run() {
int i = 0;
try {
//在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程
while (!Thread.currentThread().isInterrupted()) {
Thread.sleep(100); // 休眠100ms
i++;
System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + ") loop " + i);
}
} catch (InterruptedException e) {
//在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程。)
System.out.println(Thread.currentThread().getName() + " (" + Thread.currentThread().getState() + ") catch InterruptedException.");
}
}
};
// 启动“线程t1”
Thread t1 = new Thread(interruptTask, "t1");
System.out.println(t1.getName() + " (" + t1.getState() + ") is new.");
t1.start();
System.out.println(t1.getName() + " (" + t1.getState() + ") is started.");
// 主线程休眠300ms,然后主线程给t1发“中断”指令。
Thread.sleep(300);
t1.interrupt();
System.out.println(t1.getName() + " (" + t1.getState() + ") is interrupted.");
// 主线程休眠300ms,然后查看t1的状态。
Thread.sleep(300);
System.out.println(t1.getName() + " (" + t1.getState() + ") is interrupted now.");
}
}
执行结果:
t1 (NEW) is new. //线程被新创建。
t1 (RUNNABLE) is started. 线程执行,(调用start( )方法)
t1 (RUNNABLE) loop09 1 //线程正在执行
t1 (RUNNABLE) loop09 2 //线程正在执行
t1 (RUNNABLE) catch InterruptedException. //线程正在阻塞,调用抛出异常
t1 (TIMED_WAITING) is interrupted. //继续执行后面的逻辑
t1 (TERMINATED) is interrupted now. //线程已经终止