Java多线程
线程简介
进程Process与线程Thread
- 进程是执行程序的一次执行过程,它时一个动态的概念。进程是系统资源分配的单位。
- 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所有就有同时执行的错觉。
核心概念
- 线程是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程
- main()称为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行有调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
- 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制
- 线程会带来额外的开销,如cpu调度时间,并发控制开销
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
线程创建
三种创建方式
Thread Class
使用该方法的步骤为:
-
自定义线程类继承Thread类
-
重写run()方法,编写线程执行体
public class StartThread1 extends Thread { //线程入口点 @override public void run() { //线程体 写需要运行的逻辑 } }
-
创建线程对象,调用start()方法启动线程
public static void main(String[] args) { //创建线程对象 StartThread1 t = new StartThread1(); //启动线程 t.start(); }
Runnable接口
使用该方法的步骤为:
-
自定义类实现Runnable接口
-
实现run()方法,编写线程执行体
public class StartThread2 implements Runnable { @override public void run() { //线程体 写需要运行的逻辑 } }
-
创建线程对象,调用start()方法启动线程
public static void main(String[] args) { //创建实现类对象 StartThread2 t = new StartThread2(); //创建代理类对象 Thread thread = new Thread(t); //启动线程 thread.start(); }
推荐使用Runnable接口,因为Java单继承的局限性,灵活方便,方便同一个对象被多个线程使用
静态代理模式
重点
- 真实对象和代理对象都要实现同一个接口
- 代理对象要代理真实角色
优点
- 代理对象可以做很多真实对象做不了的事
- 真实对象专注做自己的事情
Lambda表达式
函数式接口
-
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
public interface Runnable { public abstract void run(); }
-
对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
new Thread (()->{ //这是逻辑代码 }).start();
线程状态
停止线程 stop
- 不推荐使用JDK提供的stop() 、destroy()方法【已废弃】
- 推荐让线程自己停止下来
- 建议使用一个标志位进行终止变量
线程休眠 sleep
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep存在异常InterruptedException
- sleep时间到达后线程进入就绪状态
- sleep可以模拟网络延时、倒计时等
- 每一个对象都有一个锁,sleep不会释放锁
线程礼让 yield
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,礼让不一定成功
Join
- Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
Thread的六种状态
1 NEW
新建状态
Thread state for a thread which has not yet started.
线程还没有调用start的时候
2 RUNNABLE
可运行状态
Thread state for a runnable thread. A thread in the runnable
state is executing in the Java virtual machine but it may
be waiting for other resources from the operating system
such as processor.
调用start之后,jvm启动了这个任务,但是可能被其他资源占用线程变成WAITING
3 BLOCKED
阻塞状态
Thread state for a thread blocked waiting for a monitor lock.
A thread in the blocked state is waiting for a monitor lock
to enter a synchronized block method or
reenter a synchronized block method after calling
{@link Object#wait() Object.wait}.
线程被锁的时候,线程等待进去一个synchronized块方法或者可重入锁的时候
4 WAITING
等待状态
Thread state for a waiting thread.
A thread is in the waiting state due to calling one of the
following methods:
{@link Object#wait() Object.wait} with no timeout< li>
{@link #join() Thread.join} with no timeout< li>
{@link LockSupport#park() LockSupport.park}< li>
< ul>
A thread in the waiting state is waiting for another thread to
perform a particular action.
For example, a thread that has called Object.wait()
on an object is waiting for another thread to call
Object.notify() or Object.notifyAll() on
that object A thread that has called Thread.join()线程调用object.wait thread.join LockSupport.park 的时候变成 WAITING
5 TIMED_WAITING,
时间等待状态
Thread state for a waiting thread with a specified waiting time.
A thread is in the timed waiting state due to calling one of
the following methods with a specified positive waiting time:
- {@link #sleep Thread.sleep}< li>
- {@link Object#wait(long) Object.wait} with timeout< li>
- {@link #join(long) Thread.join} with timeout< li>
- {@link LockSupport#parkNanos LockSupport.parkNanos}< li>
- {@link LockSupport#parkUntil LockSupport.parkUntil}< li>
< ul>
sleep 或者wait,join带时间参数等方法时
6 TERMINATED
终止状态
Thread state for a terminated thread.
The thread has completed execution.
线程执行完成 或者被中断的时候变成TERMINATED
TERMINATED;
线程优先级
-
线程的优先级用数字表示,范围师1~10
-
使用以下方式改变或获取优先级
getPriority(); //获取 setPriority(int x); //改变
-
优先级的设定建议在start()调度前
-
优先级低只是意味着获得调度的概率低,并不是优先级低的就不会被调用了,需要看CPU的调度。
守护(daemon)线程
-
线程分为用户线程和守护线程
-
虚拟机必须确保用户线程执行完毕
-
虚拟机不要等待守护线程执行完毕
-
守护线程如:后台记录操作日志,监控内存,垃圾回收等。
线程同步
当不同线程要访问同一资源时,需要加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。
存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。
死锁
死锁产生的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源。
避免方法
上面四个死锁必要条件,破坏其中的任意一个或多个条件就可以避免死锁的发生。
Lock(锁)
class A {
private final ReenTrantLock lock = new ReentrantLock();
public void method() {
try{
//加锁
lock.lock();
//执行其他逻辑
}finally {
//解锁
lock.unlock();
}
}
}
synchronized与Lock的对比
- Lock时显式锁(手动开启和关闭锁),synchronized式隐式锁,出了作用域自动释放
- Lock只有代码块锁,synchronized有代码块锁和方法锁
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
- 优先使用顺序:Lock > 同步代码块(已经进入了方法体,分配了相应资源) > 同步方法(在方法体之外)
线程通信
通信方法
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
生产者和消费者
- 管程法
- 信号灯法