多线程基础
基本概念
同步、异步
- 同步 线程B要等待线程A的执行结果之后才能执行。要顺序执行
- 异步 相对同步来说,彼此独立,在在等待某事件的过程中可以继续做自己的事。
这个更直观 小人相当于一个线程
image
并行/并发
并发和并行是异步的两种实现形式,并行是真正的异步
-
并发
- 单核cpu情况,同一时刻只能分配给一个线程使用。其他等待。是逻辑层面的同时工作
image
-
并行
-
多核cpu情况下,每个cpu都可以分配一个线程使用,同一时刻执行多个并发任务。叫并行
image
-
线程状态
线程状态共5种:创建、就绪、运行、==阻塞(阻塞、等待、锁定)==、终止。
创建线程
- 继承Thread类
public class A extends Thread {
//继承Thread类 实现run接口
@Override
public void run() {
System.out.println("123");
}
public static void main(String[] args) {
A a = new A();//创建线程,线程进入创建状态
a.start();//开启线程,线程进入runnable
}
}
- 实现Runable接口
public class A implements Runnable {
//继承Thread类 实现run接口
@Override
public void run() {
System.out.println("123");
}
public static void main(String[] args) {
//创建一个runable实现类的对象
A a = new A();
//不是启动线程,只是方法调用
a.run();
}
}
- 实现Callable接口
public class A implements Callable<Integer> {
//继承Callable类 实现call接口 call具有返回值
@Override
public Integer call() throws Exception {
return 123;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建一个callable实现类的对象
A a = new A();
//用FutureTask封装
FutureTask<Integer> future = new FutureTask<Integer>(a);
//创建一个线程
Thread thread = new Thread(future);
thread.start();
//线程启动后可以获取返回值
Integer integer = future.get();
System.out.println(integer);
}
}
实现Runnable接口比继承Thread类优势:
-
线程池只能放入实现Runable的
-
避免单继承的限制
-
代码被多个线程共享,代码和数据独立,例如
- 继承Thread
int ticket=10;
MyThread t1=new MyThread();
t1.start();
MyThread t2=new MyThread();
t2.start();
//上面的代码 就会卖出20张票 没有数据共享
- 实现接口
int ticket=10;
MyThread t=new MyThread();
t.run();
t.run();
//这样多个线程的时候,就能实现数据共享
线程其他状态
-
Runnable(就绪)
线程调用start()方法,状态为可执行,等待获取cpu的使用权
-
Running(运行状态)
获得cpu的使用权,执行代码。
-
Blocked(阻塞)
-
等待阻塞
执行wait()方法,jvm把该线程放入==等待池==
-
同步阻塞
获取同步锁时,锁被其他线程占用,jvm把该线程放入==锁池==
-
其他阻塞
执行sleep()或join()或发出i/o请求时,jvm把线程设置为阻塞状态
-
常用函数说明
-
sleep()
- 在指定的毫秒数内让线程休眠,进入阻塞状态。
-
join()
- 主线程需要等待调用join的子线程执行完毕。
-
yield() 退让
- 暂停当前正在执行的线程对象,并执行其他线程,该线程进入runnable状态
- 目的:是为了让相同优先级的线程之间轮转执行。
- 实际情况下,并不一定有效果,因为让步之后,该线程还是有可能被再次选中
| sleep | yield |
|---|---|
| 在指定时间内肯定不会被执行 | 随时都可以获取锁 |
| 允许较低优先级的线程获得运行机会 | 只能同级 |
- interrupt() 中断
- 并不能中断线程!
- 只是给线程发出一个中断信号。
- wait()休眠
- 线程在获取对象锁之后,释放对象锁,所以必须是再synchronized{}中
- notify() 唤醒
- 线程对对象锁的唤醒操作。也要和synchronized{}搭配使用
- notify并不能马上获取对象锁,而是再synchronized语句块结束,自动释放锁之后,jvm在所有的wait()的线程中随机选取。
- 有个经典的题,就是三个线程。分别打印10次A、B、C,要求顺序执行
public class MyThreadPrinter implements Runnable {
private String name;
private Object prev;
private Object self;
/**
*
* @param name 需要打印的东西
* @param prev 上一个对象
* @param self 自己
*/
private MyThreadPrinter(String name, Object prev, Object self) {
this.name = name;
this.prev = prev;
this.self = self;
}
@Override
public void run() {
int count = 10;
while (count > 0) {
//需要获取上一个对象的锁和本身的锁
synchronized (prev) {
synchronized (self) {
//重点在这
//打印本身的信息
System.out.print(name);
count--;
// 打印完 name了 可以唤醒了 因为上一个线程把本身给wait了
self.notify();
}
try {
// 释放锁 等待notify才可以重新获取锁
//比如a执行完 需要执行b 需要 b a锁 就把c给wait
prev.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws Exception {
Object a = new Object();
Object b = new Object();
Object c = new Object();
MyThreadPrinter pa = new MyThreadPrinter("A", c, a);
//pa执行完 c就wait了 只有a b可以获取锁
MyThreadPrinter pb = new MyThreadPrinter("B", a, b);
MyThreadPrinter pc = new MyThreadPrinter("C", b, c);
new Thread(pa).start();
Thread.sleep(100); //确保按顺序A、B、C执行
new Thread(pb).start();
Thread.sleep(100);
new Thread(pc).start();
Thread.sleep(100);
}
}
- wait / sleep
| wait | sleep |
|---|---|
| 释放锁 | 没有释放锁 |
| 只能在同步快sync中使用 | 任何地方 |








网友评论