Java多线程

线程简介

进程Process与线程Thread

  • 进程是执行程序的一次执行过程,它时一个动态的概念。进程是系统资源分配的单位
  • 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位

注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所有就有同时执行的错觉。

核心概念

  • 线程是独立的执行路径
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程
  • main()称为主线程,为系统的入口,用于执行整个程序
  • 在一个进程中,如果开辟了多个线程,线程的运行有调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
  • 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

线程创建

三种创建方式

线程创建方式

Thread Class

使用该方法的步骤为:

  1. 自定义线程类继承Thread类

  2. 重写run()方法,编写线程执行体

    public class StartThread1 extends Thread {
        //线程入口点
        @override
        public void run() {
            //线程体  写需要运行的逻辑
        }
    }
    
  3. 创建线程对象,调用start()方法启动线程

    public static void main(String[] args) {
    	//创建线程对象
    	StartThread1 t = new StartThread1();
        //启动线程
        t.start();
    }
    

Runnable接口

使用该方法的步骤为:

  1. 自定义类实现Runnable接口

  2. 实现run()方法,编写线程执行体

    public class StartThread2 implements Runnable {
        @override
        public void run() {
            //线程体  写需要运行的逻辑
        }
    }
    
  3. 创建线程对象,调用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,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可。

存在以下问题:

  1. 一个线程持有锁会导致其他所有需要此锁的线程挂起
  2. 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题
  3. 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

死锁

死锁产生的四个必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  3. 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源。

避免方法

上面四个死锁必要条件,破坏其中的任意一个或多个条件就可以避免死锁的发生。

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()方法的线程,优先级别高的线程优先调度

生产者和消费者

  • 管程法
  • 信号灯法

线程池

posted @ 2021-02-15 23:14  bGpi  阅读(29)  评论(0编辑  收藏  举报