并发和并行
- 并发:同一时刻,多个指令同时进行
-
线程和进程
进程:是操作系统上的一个程序
- 线程:是进程的一个实体,是cup调度和分派的单位
线程实现的三种方式
继承Thread方法
```java public class ThreadDemo extends Thread{ @Override public void run() {
} }for (int i = 0; i < 10; i++) {
System.out.println("线程:"+i);
}
class MyThread{ public static void main(String[] args) { ThreadDemo demo1 = new ThreadDemo(); demo1.start(); } }
**为什么重写run方法?**<br />run方法是封装被线程执行的方法<br />**run()方法和start()方法的区别?**<br />run():封装线程执行的代码,直接调用,相当于普通方法的调用<br />start():启动线程;然后由JVM调用此线程的run()方法
<a name="RpbNQ"></a>
#### 实现Runnable接口
```java
//1.实现Runnable接口
public class RunnableDemo implements Runnable{
//2.重写run方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程:"+i);
}
}
}
class RunnableTest{
public static void main(String[] args) {
//3.创建Runnable的实现类
RunnableDemo runnableDemo = new RunnableDemo();
//4.创建Thread对象并把runnable的实现对象传递进去
//5.调用start方法
new Thread(runnableDemo).start();
}
}
实现Callable接口
//1.实现Callable接口
public class CallableDemo implements Callable<String> {
//2.重写call方法,并给出返回值
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("线程执行:"+i);
}
return "结束";
}
}
class CallableTest{
public static void main(String[] args) throws ExecutionException, InterruptedException {
//3.创建Callable实现类对象
CallableDemo demo = new CallableDemo();
//4.创建FutureTask对象并将Callable实现类对象传递进去
FutureTask<String> task = new FutureTask<>(demo);
//5.创建Thread线程对象,把FutureTask对象作为构造方法的参数
Thread thread = new Thread(task);
//6.开始线程
thread.start();
//可以获取到线程执行后的返回结果
String result = task.get();
System.out.println(result);
}
}
三种实现方式的对比
- 实现Runnable、Callable接口
- 好处: 扩展性强,实现该接口的同时还可以继承其他的类
- 缺点: 编程相对复杂,不能直接使用Thread类中的方法
- 继承Thread类
- 好处: 编程比较简单,可以直接使用Thread类中的方法
- 缺点: 可以扩展性较差,不能再继承其他的类
- Runnable 接口 run 方法无返回值;Callable 接口 call 方法有返回值,支持泛型
- Runnable 接口 run 方法只能抛出运行时异常,且无法捕获处理;Callable 接口 call 方 法允许抛出异常,可以获取异常信息
同步方法和静态同步方法
同步方法锁对象是this
静态同步方法锁对象是类名.classLock锁
JDK1.5之后推出了Lock锁,可以人为的去加锁及解锁
使用:创建RenntrantLock对象,在要加锁的代码前面使用lock方法加锁,在解锁地方使用unlock方法解锁生产者消费者
| 方法名 | 说明 | | —- | —- | | void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 | | void notify() | 唤醒正在等待对象监视器的单个线程 | | void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
线程的生命周期
- 新建状态,生成线程对象,此时并没有调用对象的start方法,线程处于创建状态
- 就绪状态,当线程调用了start方法后,线程进入了就绪状态,此时有执行资格没有执行权,需要抢到cpu资源才能到达运行状态
- 运行状态,此时抢到cpu资源,开始运行run方法中的代码
- 阻塞状态,阻塞状态是因为某些原因,放弃cpu使用权,暂时停止运行直到线程进入就绪状态,才会转到运行状态,阻塞分为三种
- 阻塞:无法获得锁,获得锁有阻塞结束进入就绪状态再次抢夺cpu使用
- 等待:wait(),让线程等待某项完成,使用notify()或notifyAll()再次唤醒线程结束阻塞状态
- 计时等待:sleep(),当sleep完成后,线程结束阻塞状态
- 死亡:线程结束run方法执行,或者某些异常原因退出run方法,线程结束
线程池
手动创建线程池
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
//核心线程数量
2,
//最大线程数
5,
//空闲存活时间
3,
//时间单位
TimeUnit.SECONDS,
//任务队列
new ArrayBlockingQueue<>(10),
//创建线程工厂
Executors.defaultThreadFactory(),
//任务拒绝策略
// AbortPolicy 丢弃任务并抛出 DiscardPolicy 丢弃任务,但是不抛出异常 不推荐
// DiscardOldestPolicy 抛弃队列中等待最久的任务 然后把当前任务加入队列中 CallerRunsPolicy 调用任务的run()方法绕过线程池直接执行
new ThreadPoolExecutor.AbortPolicy()
);
executor.submit(() -> {
System.out.println(Thread.currentThread().getName() + "线程执行");
});
}
线程池原理
线程池关闭
关闭线程池,可以通过 shutdown 和 shutdownNow 两个方法
原理:遍历线程池中的所有线程,然后依次中断
- shutdownNow 首先将线程池的状态设置为 STOP,然后尝试停止所有的正在执行和未执 行任务的线程,并返回等待执行任务的列表;
shutdown 只是将线程池的状态设置为 SHUTDOWN 状态,然后中断所有没有正在执 行任务的线程;
原子性
定义: 即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
在多线程中
1. 堆内存是唯一的,每一个线程都有自己的线程栈。
2. 每一个线程在使用堆里面变量的时候,都会先拷贝一份到变量的副本中。
3. 在线程中,每一次使用是从变量的副本中获取的。
Volatile关键字 : 强制线程每次在使用的时候,都会看一下共享区域最新的值多线程面试
wait()和 sleep()的区别?(必会)
来自不同类
- wait来自Object类
- sleep来自Thread类
- 关于锁的释放
- wait在等待过程中会释放锁
- sleep在等待过程中不会释放
- 适用范围
- wait必须在同步代码块中用
- sleep可以在任何地方
- 是否需要捕获异常