什么是进程
进程是操作系统分配资源的基本单位,可视为正在运行的程序
什么是线程
线程是操作系统进行调度的基本单位,一个进程中可以存在多个线程,能够访问所属进程的共享资源
线程与进程的区别
- 一个程序至少有一个进程,一个进程至少有一个线程
- 进程间想要交换资源相对困难,而线程间可以共同访问共享资源
线程的基本方法
| 方法 | 描述 |
|---|---|
| run() | 线程执行的内容 |
| start() | 线程启动方法 |
| currentThread() | 返回当前正在执行线程对象的引用 |
| setName() | 设置线程名称 |
| getName() | 获取线程名称 |
| setPriority() | 设置线程优先级 |
| getPriority() | 获取线程优先级 |
| setDaemon() | 设置线程为守护线程 |
| isAlive() | 判断线程是否启动 |
| interrupte | 中断另一个线程的执行状态 |
| join() | 可以使一个线程强制运行,运行期间其他线程无法运行,必须等待此线程执行完毕 |
| Thread.sleep() | 将当前正在执行的线程休眠 |
| Thread.yield | 将当前正在执行的线程休眠,让其他线程执行 |
线程的状态
- 新建New 没有调用start方法
- 可运行Runnable 调用start方法
- 运行Running 线程获得cpu时间片
- 阻塞Blocked
- 等待阻塞:线程调用wait方法
- 同步阻塞:对象获取同步锁时,同步锁被占用
- 其他阻塞:线程调用sleep或者join方法
- 终止Terminated 执行体被执行完毕
创建线程
三种方式
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
通过继承Thread类
- 定义Thread类的子类,重写run()方法。run()方法被称为执行体。
- 创建Thread子类的实例,即创建线程对象
- 调用线程对象start()方法来启动线程
public class ExtendThreadDemo {
static class myThread extends Thread {
@Override
public void run() {
System.out.println("线程测试");
}
}
public static void main(String[] args) {
myThread mt = new myThread();
mt.start();
}
}
实现Runnable接口
- 定义Runnable接口的实现类,重写run()方法作为执行体
- 创建Runnble实现类的实例,作为参数创建Thread对象
- 调用线程对象的start()方法来启动线程
static class MyThread implements Runnable {
int sleepTime;
public MyThread(int sleepTime) {
this.sleepTime = sleepTime;
}
@Override
public void run() {
System.out.println("当前线程名称:"+Thread.currentThread().getName());
try {
Thread.sleep(sleepTime);
System.out.println(Thread.currentThread().getName()+"睡眠"+sleepTime/1000+"秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread(1000), "线程1");
Thread t2 = new Thread(new MyThread(5000), "线程2");
Thread t3 = new Thread(new MyThread(10000), "线程3");
t1.start();
t2.start();
t3.start();
}
实现Callable接口
jdk1.5以后提供了Callable和Future。通过它们可以在线程执行结束后,返回执行结果。
通过实现Callable接口创建线程的步骤
- 创建Callable接口实现类,重写call()方法作为执行体
- 创建Callable接口实现类的实例,使用Futuretask包装Callable接口实现类
- 将Futuretask对象作为Thread构造函数的参数,并启动线程
- 调用Futuretask对象的get方法来获取线程执行后的结果
public class ImplementCallableThread {
static class MyCallable implements Callable {
@Override
public Object call() throws Exception {
System.out.println("看看会返回什么");
return this;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask ft = new FutureTask(new MyCallable());
Thread t = new Thread(ft);
t.start();
System.out.println(ft.get());
}
}
FAQ
run与start方法的区别
- run方法作为线程的执行体
- start方法则会调用run方法
核心源码
/**
* Causes this thread to begin execution; the Java Virtual Machine
* calls the <code>run</code> method of this thread.
* <p>
* The result is that two threads are running concurrently: the
* current thread (which returns from the call to the
* <code>start</code> method) and the other thread (which executes its
* <code>run</code> method).
* <p>
* It is never legal to start a thread more than once.
* In particular, a thread may not be restarted once it has completed
* execution.
*
* @exception IllegalThreadStateException if the thread was already
* started.
* @see #run()
* @see #stop()
*/
public synchronized void start() {
······
group.add(this);
boolean started = false;
try {
start0();
started = true;
}
}
······
由注释可以知道,该方法native为方法start0调用run方法
线程休眠
Thread.sleep方法可以使得正在执行的线程进入休眠状态 Thread.sleep(int 休眠时间[毫秒])
代码
public class ImplementRunnableDemo {
static class MyThread implements Runnable {
int sleepTime;
public MyThread(int sleepTime) {
this.sleepTime = sleepTime;
}
@Override
public void run() {
System.out.println("当前线程名称:"+Thread.currentThread().getName());
try {
Thread.sleep(sleepTime);
System.out.println("睡眠"+sleepTime/1000+"秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new MyThread(1000), "线程1");
Thread t2 = new Thread(new MyThread(5000), "线程2");
Thread t3 = new Thread(new MyThread(10000), "线程3");
t1.start();
t2.start();
t3.start();
}
}
结果
当前线程名称:线程1
当前线程名称:线程3
当前线程名称:线程2
线程1睡眠1秒
线程2睡眠5秒
线程3睡眠10秒
可以看到虽然是线程3比线程2先运行,但是由于睡眠进入睡眠状态,线程2进入运行,线程2只睡眠5秒钟,所以先唤醒。
线程礼让
Thread.yield方法,调用时切换其他线程来执行。
代码
public class ThreadYield {
static class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 4; i++) {
System.out.println(Thread.currentThread().getName() + "输出" + i);
Thread.yield();
}
}
}
public static void main(String[] args) {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();
Thread t1 = new Thread(mt1,"线程1");
Thread t2 = new Thread(mt2,"线程2");
Thread t3 = new Thread(mt3,"线程3");
t1.start();
t2.start();
}
}
结果
线程1输出0
线程2输出0
线程1输出1
线程2输出1
线程1输出2
线程2输出2
线程1输出3
线程2输出3
线程1输出4
线程2输出4
终止线程
使用stop方法来终止线程。
Thread中的stop方法被废弃理由如下: 使用stop方法会直接停止线程,导致它持有的所有锁被释放,如果有写入操作,很有可能导致每次执行的结果不一样 当一个线程运行时,另一个线程可以条用interrupt方法中断其运行状态。
使用interrupt方法来终止线程
代码
public class ThreadInterruptDemo {
public static void main(String[] args) {
MyThread mt = new MyThread(); // 实例化Runnable子类对象
Thread t = new Thread(mt, "线程"); // 实例化Thread对象
t.start(); // 启动线程
t.interrupt(); // 中断线程执行
for(int i = -10000;i <= -1; i++) {
System.out.println(i);
}
}
static class MyThread implements Runnable {
@Override
public void run() {
System.out.println("1、进入run()方法");
for(int i = 0;i <= 10000; i++) {
System.out.println(i);
}
try {
System.out.println("开始休眠");
Thread.sleep(10000); // 线程休眠10秒
System.out.println("2、已经完成了休眠");
} catch (InterruptedException e) {
System.out.println("3、休眠被终止");
return; // 返回调用处
}
System.out.println("4、run()方法正常结束");
}
}
}
通过以上代码得到的结果 可以得到以下的结论:
- interrupt方法无法中断某些处理逻辑,如上出代码的循环逻辑
- interrupt方法可以中断休眠状态的线程
如何解决interrupt方法无法中断循环逻辑?
- 定义标志位,在run方法中使用标志位控制线程结束
- 使用interrupt方法和Thread.interrupted方法配合
方法1
/*使用标志位中断线程*/
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread(); // 实例化Runnable子类对象
Thread t = new Thread(mt, "线程"); // 实例化Thread对象
t.start(); // 启动线程
t.sleep(10);
mt.setFlag(true);
}
static class MyThread implements Runnable {
boolean flag = false;
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
System.out.println("1、进入run()方法");
int i = 0;
for (i = 0; i <= 10000; i++) {
if(flag) break;
System.out.println(i);
}
if(i != 10001) {
System.out.println("线程被中断");
}
System.out.println("4、run()方法正常结束");
}
}
结果
584
585
586
587
588
线程被中断
4、run()方法正常结束
方法2
//结合interrupt和interrupted中断线程
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread(); // 实例化Runnable子类对象
Thread t = new Thread(mt, "线程"); // 实例化Thread对象
t.start(); // 启动线程
t.sleep(10);
t.interrupt(); // 中断线程执行
}
static class MyThread implements Runnable {
@Override
public void run() {
System.out.println("1、进入run()方法");
for(int i = 0;i <= 10000; i++) {
if(Thread.interrupted()) {
break;
}
System.out.println(i);
}
try {
System.out.println("开始休眠");
if(Thread.interrupted()) {
throw new InterruptedException();
}
Thread.sleep(10000); // 线程休眠10秒
System.out.println("2、已经完成了休眠");
} catch (InterruptedException e) {
System.out.println("3、休眠被终止");
return; // 返回调用处
}
System.out.println("4、run()方法正常结束");
}
}
结果
682
683
684
开始休眠
2、已经完成了休眠
4、run()方法正常结束
线程类别
Java中有两类线程
- 用户线程(User Thread)
- 守护线程(Daemon Thread)
两者的关系: 只要用户线程还存在,所有的守护线程必须工作,只有当所有的用户线程都结束了,守护线程随着JVM一同结束工作
守护线程
Daemon守护线程的优先级别低,工作就是为其他线程运行提供便利服务,最典型的守护线程就是GC(垃圾回收器),
用户如何定义守护线程?
- 使用isDaemon方法来判断线程是否为守护线程
- 使用setDaemon方法来设置线程为守护线程
注意点:
- 用户设置的守护线程必须要在线程开始执行之前设置,不能把正在运行的常规线程设置成守护线程
- 在守护线程中新产生的线程也是守护线程
- 不能把所有工作都交给守护线程,如读写操作和计算逻辑,因为一旦用户线程结束,所有的守护线程也会跟着结束,可能会导致计算结果每次都不同
代码
public class ThreadDaemonDemo {
static class MyThread extends Thread {
@Override
public void run() {
while (true) {
System.out.println("我是守护线程");
}
}
}
static class MyThreadB extends Thread {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if(i == 50) {
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i);
}
}
}
public static void msikaoain(String[] args) {
MyThread t = new MyThread();
MyThreadB b = new MyThreadB();
t.setDaemon(true);
t.start();
b.start();
}
}
从结果可以看出,当用户线程执行完毕后,守护线程停止
FAQ
sleep、yield、join方法有什么区别?
yield
- 当前线程变为就绪状态
- 只有与当前线程相同或者更高优先级的线程才会得到执行机会
sleep
- 当前线程状态变为等待睡眠状态
- 等待时间过后恢复为就绪状态
- 调用方法后,所有优先级的线程都有机会进入运行状态
join
- 线程进入阻塞状态
- 必须等待调用的线程执行完毕才能继续执行
为什么sleep和yield方法是静态的?
它们针对的是正在运行的线程,对于其他线程是没有意义的,设为静态方法可以避免程序员在其他状态调用它们
Java线程是否按照线程优先级严格执行?
即使设置了优先级也无法保证线程按照优先级严格执行。因为线程优先级依赖与操作系统的支持。
线程间通信
当多个线程需要合作完成一个需求时,并且有先后顺序,那么就需要对线程之间进行协调。
主要用到的方法wait/notify/notifyAll
wait方法
让当前正在运行的线程释放它所有的锁,并进入到阻塞状态,需要等待notify/notifyAll来唤醒。如果没有释放锁,那么其他线程就无法进入到对象的同步方法或者同步控制块中,就无法执行notify或者notifyAll来唤醒阻塞状态的线程,造成死锁
notity方法
唤醒一个阻塞状态的线程,让它拿到对象锁,具体唤醒哪一个线程由JVM控制
notifyAll方法
唤醒所有阻塞状态的线程
注意事项
- 上面三个方法都是在Object类中的方法
- 上面三个方法都只能在synchronized方法或者synchronized代码块中使用
为何不将它们定义在Thread中?
同步线程中,只有同一个锁上被等待线程,可以被同一个锁上的notify唤醒,无法对不同锁中的线程进行唤醒
代码
public class ThreadWaitNotifyDemo {
private static final int QUEUE_SIZE = 10;
private static final PriorityQueue<Integer> queue = new PriorityQueue<>(QUEUE_SIZE);
public static void main(String[] args) {
new Producer("生产者A").start();
new Producer("生产者B").start();
new Consumer("消费者").start();
}
static class Consumer extends Thread {
Consumer(String name) {
super(name);
}
@Override
public void run() {
while (true) {
//对象锁,同处理同一个对象的对象才能进入
synchronized (queue) {
//如果列表空了,消费者则不能消费,进入阻塞状态,同时唤醒消费者
while (queue.size() == 0) {
try {
System.out.println("队列空,等待数据");
queue.wait();
queue.notify();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notifyAll();
}
}
//取走一件商品后,便进入阻塞,让下一位消费者或者生产者工作
queue.poll(); // 每次移走队首元素
queue.notify();
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 从队列取走一个元素,队列当前有:" + queue.size() + "个元素");
}
}
}
}
static class Producer extends Thread {
Producer(String name) {
super(name);
}
@Override
public void run() {
while (true) {
synchronized (queue) {
while (queue.size() == QUEUE_SIZE) {
try {
System.out.println("队列满,等待有空余空间");
queue.wait();
queue.notify();
} catch (InterruptedException e) {
e.printStackTrace();
queue.notifyAll();
}
}
queue.offer(1); // 每次插入一个元素
queue.notify();
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 向队列取中插入一个元素,队列当前有:" + queue.size() + "个元素");
}
}
}
}
}
管道
用于线程之间的数据交流
四个具体实现
- PipedOutputStream
- PipedInputStream
- PipedReader
- PipedWriter
实现步骤
- 连接输入输出管道
- 输入端输入
- 输出端输出
代码
public static void main(String[] args) throws Exception {
PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
// 将输出流和输入流进行连接,否则在使用时会抛出IOException
out.connect(in);
Thread printThread = new Thread(new Print(in), "PrintThread");
Thread writeThread = new Thread(new Write(out), "WriteThread");
writeThread.start();
printThread.start();
}
static class Print implements Runnable {
private PipedReader in;
Print(PipedReader in) {
this.in = in;
}
public void run() {
int receive = 0;
try {
StringBuilder sb = new StringBuilder("");
while (true) {
receive = in.read();
if ((char) receive == '-') {
break;
}
sb.append((char) receive);
}
System.out.println(sb.toString());
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
static class Write implements Runnable {
private PipedWriter out;
public Write(PipedWriter out) {
this.out = out;
}
@Override
public void run() {
int receive = 0;
try {
StringBuilder sb = new StringBuilder("");
while (true) {
receive = System.in.read();
sb.append((char) receive);
if ((char) receive == '-') {
break;
}
}
out.write(sb.toString());
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}