这一部分的内容主要是为了明年的笔试面试做准备,将一些基础知识整理归纳,如果有不正确的地方欢迎指正
Java多线程
实现线程的两种主要方式
- 继承Thread类
- 实现Runnable接口
两者都是通过重写run方法完成相应的业务操作,需要注意的是线程的调用需要使用start方法,不能直接调用run方法,直接调用run方法时和调用普通的对象方法没有区别
public class Test {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
MyThread thread1 = new MyThread("thread1");
thread1.start();
MyThread thread2 = new MyThread("thread2");
thread2.run();
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
}
}
上面的代码结果会发现线程2的id和主线程id相同,因为直接调用run方法时,是主线程调用了普通对象thread2的实例方法,在该run方法运行结束之前,其他的线程无法并发执行
此外使用继承Thread类的方式创建线程对象,多个线程对象之间是没有办法进行实例变量的共享的。而实现Runnable接口的方式创建对象作为Thread类对象的target这种方式,可以让多个线程对象共享成员变量,因为Java不能实现多继承,所以实现Runnable接口来创建多线程对象是一个不错的选择
使用Callable和hi是Future方式创建线程这边不讨论
线程的生命周期
线程的生命周期主要有新建,就绪,运行,阻塞,死亡这几个状态
- 新建:当一个线程对象被创建出来的时候就处于新建状态
- 就绪:当一个线程对象调用start方法的时候就处于就绪状态,但是并不一定会立即执行,需要等待cpu分配时间片,就绪表示的是线程可以执行了
- 如果一个就绪态的线程对象获得了CPU,开始或继续执行run方法,就处于运行态了,
在下面的这些状况下,线程会放弃CPU,进入阻塞状态:
- 线程调用sleep方法让出CPU
- 调用了阻塞式IO方法返回之前线程会被阻塞
- 当线程试图获取锁,但是锁被其他线程持有的时候,线程是处于阻塞状态的(等待锁)
- 等待通知或suspend挂起线程(等待通知)
相应的就有从阻塞态进入就绪态的情况,注意这边是就绪态,并不是运行态。因为当线程从阻塞态恢复过来之后,能不能执行还是要看是有没有被分配到CPU时间片。
- 死亡:当线程执行体执行完毕,线程正常结束就进入死亡态,此外当线程抛出一个未捕获的Exception或者Error也会进入死亡态,直接调用线程的stop也会进入死亡状态,对于死亡的线程就不能调用start方法了
join线程
join方法,就是让一个线程等待另一个线程执行完毕,比如在main方法中调用了一个线程thread1的join方法,那么也就是说主线程在此时会进入阻塞状态,等待thread1执行完成
public static void main(String[] args){
MyThread thread1 = new MyThread()
thread1.start()
thread1.join()
}
public class JoinTest {
public static void main(String[] args) throws InterruptedException{
JoinThread t = new JoinThread("t");
t.start();
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
if(i==10){
t.join();
}
}
}
}
class JoinThread extends Thread{
public JoinThread(String name){
super(name);
}
public void run(){
for(int i=0;i<20;i++){
System.out.println(this.getName()+":"+i);
}
}
}
main:0
t:0
main:1
t:1
main:2
t:2
t:3
t:4
t:5
t:6
t:7
t:8
main:3
main:4
main:5
main:6
main:7
t:9
t:10
t:11
t:12
t:13
main:8
main:9
main:10
t:14
t:15
t:16
t:17
t:18
t:19
main:11
main:12
main:13
main:14
main:15
main:16
main:17
main:18
main:19
可以看到当主线程的i==10时调用t的join方法,主线程进入阻塞状态,直到线程t执行完成之后主线程才继续执行
后台线程
后台线程也叫守护线程,最常见的后台线程就是Java的GC线程,后台线程在所有的前台线程死亡时也会死亡
使用setDaemon方法可以将线程设置为守护线程,此外线程的创建有继承性,后台线程创建的子线程默认也是后台线程,前台线程创建的子线程默认也是前台线程,需要注意的是后台线程的设置必须在调用start方法之前
yield
线程让步,yield方法的作用是让当前的线程从运行状态转到就绪状态,和sleep方法有一点相似,但是sleep方法是将当前的线程状态变为阻塞,使用yield方法后线程进入就绪态,有可能又被分配到CPU时间片后执行,实际上只有与yield线程优先级相同或更高优先级的线程能够分配到CPU时间片
public class YieldTest {
public static void main(String[] args){
MyThread t1 = new MyThread("t1");
MyThread t2 = new MyThread("t2");
// t1.setPriority(Thread.MAX_PRIORITY);
// t2.setPriority(Thread.MIN_PRIORITY);
t2.start();
t1.start();
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name){
super(name);
}
@Override
public void run() {
super.run();
for(int i=0;i<5;i++){
System.out.println(this.getName()+":"+i);
if(i == 2)
this.yield();
}
}
}
t2:0
t2:1
t2:2
t1:0
t1:1
t1:2
t2:3
t2:4
t1:3
t1:4
去掉注释后的执行结果
t1:0
t1:1
t1:2
t1:3
t2:0
t1:4
t2:1
t2:2
t2:3
t2:4
可以看到当不设置优先级的时候,两个线程是同一个优先级,当t2中i==2的时候t2变为让出CPU变成就绪态,而当设置了优先级的时候,t1中i==2的时候并没有让t2执行,仍然是t1获得了CPU时间片
多线程的同步和通信以及线程池的应用将在下面的文章中进行描述