目录
1. 线程
1.1 线程的提出
1.2 线程的概念
1.3 线程和进程的关系
1.4 Java 的线程和 *** 作系统线程
2. Java中创建线程
2.1 继承Thread类 重写run()方法
2.2 实现Runnable接口 重写run()方法
2.3 继承Thread类 重写run()方法,使用匿名内部类
2.4 实现Runnable接口 重写run()方法,使用匿名内部类
2.5 start和run的区别
3. Thread类及常用方法
3.1 Thread的常见构造方法
3.2 Thread类的几个常见属性
3.3 等待一个线程
3.4 休眠当前线程
3.5 获取当前线程的引用
4. 线程的状态
1. 线程 1.1 线程的提出
在进程的学习中,我们了解到引入进程就是为了“并发编程”,虽然进程能解决并发的问题,但是我们认为还不是不够理想。
因为创建进程/销毁进程/调度进程,开销有点大:
- 创建进程需要分配资源;
- 销毁进程需要释放资源;
频繁的创建和销毁进程就会开销较大。
于是就有了“线程”(Thread)的 概念,线程在有些系统上也叫做"轻量级进程"。
1.2 线程的概念一个线程就是一个 “执行流”.,每个线程之间都可以按照顺讯执行自己的代码.,多个线程之间 “同时” 执行着多份代码
1.3 线程和进程的关系
- 创建线程比创建进程更高效
- 销毁线程比销毁进程更高效
- 调度线程比调度进程更高效
- 进程和线程是包含关系。每个进程至少有一个线程存在,即主线程。
- 进程和进程之间不共享内存空间.。同一个进程的线程之间共享同一个内存空间。每个进程拥有独立的内存空间(虚拟地址空间),同一进程多个线程共用这个内存空间(虚拟地址空间)
- 进程是系统分配资源的最小单位,线程是系统调度的最小单位。
在Java中使用 Thread 类
这个类的对象来表示 *** 作系统中的线程
2. Java中创建线程 2.1 继承Thread类 重写run()方法
- 在 *** 作系统中 ,
PCB
是用来描述线程的.- 在Java代码中 ,
Thread类
是用来描述线程的
(1)创建Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建了Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
代码示例:
class MyThread extends Thread{
@Override
public void run() {
System.out.println("线程名:" + currentThread().getName());
}
}
public class ThreadDemo {
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
}
}
2.2 实现Runnable接口 重写run()方法
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程。
线程的执行流程很简单,当执行代码start()时,就会执行对象中重写的void run()方法,该方法执行完成后,线程就消亡了。
代码示例:
public class demo2 {
public static void main(String[] args) {
MyThread02 target=new MyThread02();
Thread t1=new Thread(target);
Thread t2=new Thread(target);
t1.start();
t2.start();
}
}
class MyThread02 implements Runnable{
@Override
public void run() {
System.out.println("线程名:"+Thread.currentThread().getName());
}
}
2.3 继承Thread类 重写run()方法,使用匿名内部类
代码示例:
public class demo3 {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
System.out.println("hello thread !");
}
};
t.start();
}
}
2.4 实现Runnable接口 重写run()方法,使用匿名内部类
代码示例:
public class demo4 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello thread !!!");
}
});
t.start();
}
}
2.5 start和run的区别
代码示例:
class MyThread extends Thread{
@Override
public void run() {
System.out.println(" thread !!! ");
}
}
public class demo5 {
public static void main(String[] args) {
Thread t = new MyThread();
t.start(); //1
//t.run(); // 2
}
}
上面代码中start和run的区别,程序的执行上结果是一样的,但实际上:
- 使用
t.start()
会创建一个新的 PCB ,新的 PCB 链接在链表上,然后执行myThread.run()
方法 - 使用
t.run()
会直接调用myThread.run()
代码示例:
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("线程命名");
Thread t4 = new Thread(new MyRunnable(), "线程命名");
3.2 Thread类的几个常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
- ID是唯一的标识,不同的线程不会重复
- 名称就是线程的名字
- 状态就是线程的状态,存在的意义就是辅助进行线程调度
- 优先级高的更容易被调度
- 创建的线程,默认不是后台线程,注意:JVM会在一个进程的所有非后台线程结束后,才会结束
- 是否存活,就是内核中的PCB是不是销毁了,也就是说系统中的线程是不是销毁了.简单理解,为run方法是否运行结束了
线程和线程之间,调度顺序是完全不确定的…如果想要线程的顺序可控,线程等待就是一种方法。
方法 | 说明 |
---|---|
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 等待线程结束,时间单位更精确 |
当执行到 t.join()
时,这里的线程会阻塞等待。
假定一种情况,A吃饭时没带钱包,就请B帮他把钱包送过来:
代码示例:
public class Main {
private static class B extends Thread {
@Override
public void run() {
// 模拟 B 要做很久的工作
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
println("B 说:我把钱包送来了");
}
}
private static void println(String msg) {
Date date = new Date();
DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(format.format(date) + ": " + msg);
}
public static void main(String[] args) throws InterruptedException {
B b = new B();
b.start();
println("A 自己先去吃饭");
// 有 join 和没有 join 的区别
b.join();
println("A 说:B 给我把钱送来了,结账走人");
}
}
不加b.join时,可以看到A的 *** 作时顺序执行的,并没有等待B的 *** 作完成。
加了b.join时,就可以保证A一定是等到B *** 作完成后才继续执行自己的 *** 作,达到了顺序可控。
3.4 休眠当前线程public static void sleep(long millis) // 休眠当前线程millis毫秒
从线程状态的角度来看,调用sleep(xx),就是让当前线程从“运行态”-->“阻塞态”。此处的阻塞是指线程必须要等待某个条件:即要求时间过去xx之后。当这段时间过去后(条件满足),线程才会从“阻塞态”-->“就绪态”,此时如果线程被调度器选中,就会接着之前的指令开始执行,表现为sleep之后的语句执行。
整体的外部表现为让线程休眠一段时间。
另外一种让线程休眠的方法为:
TimeUnit.SECONDS.sleep();
3.5 获取当前线程的引用
方法 | 说明 |
---|---|
public static Thread currentThread(); | 返回当前线程对象的引用 |
Thread.currentThread();
该方法会返回当前线程对象的引用,在哪个线程中调用的该方法,就返回哪个对象。
public class Main {
static class MyThread extends Thread {
@Override
public void run() {
printCurrentThreadAttributes();
}
}
private static void printCurrentThreadAttributes() {
// 返回当前(这个方法是在哪个线程中被调用的)线程的引用
Thread t = Thread.currentThread();
System.out.println(t.getId());
System.out.println(t.getName());
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.setName("t1");
t1.start();
MyThread t2 = new MyThread();
t2.setName("t2");
t2.start();
MyThread t3 = new MyThread();
t3.setName("t3");
t3.start();
printCurrentThreadAttributes();
}
}
所以固定的方法被不同的线程调用,返回的就是不同的对象。
4. 线程的状态- NEW: Thread 对象创建了,但是内核没有创建出PCB。
- RUNNABLE: 当前的PCB创建出来了,这个PCB就绪了.这个线程可能在CPU上运行,也可能在就绪队列中排队。
- BLOCKED: 线程中尝试进行加锁,结果发现锁已经被其他线程占用了,此时PCB也会处于阻塞状态.这个等待会在其他线程释放锁之后被唤醒。
- WAITING: PCB处于阻塞状态(死等)
- TIMED_WAITING: 表示PCB在阻塞队列中等待,这个等待是有结束时间的等待。
- TERMINATED: 表示当前PCB已经结束了,但是Thread对象还在,此时调用获取状态,得到的就是这个状态。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)