多线程基础知识

进程和线程的区别

  • 进程:正在运行的程序。计算机在执行硬盘中的程序时会为程序创建相应的进程,会以进程为单位进行资源分配。
    线程:每个进程实际是执行一系列的线程
  • 进程之间的资源是独立的,线程之间的资源在同一个进程内是共享的
    系统为每个进程分配独立的寻址空间,而线程没有独立的寻址空间,多线程共享同一个进程的寻址空间

线程的基本概念

  • 线程都有自己的名字,其中执行main方法的线程名称叫:main线程
    在这里插入图片描述
  • 每个线程都有自己的优先级,分为1-10个优先级,所有线程默认优先级为5。优先级数字越大,越优先执行,获取到的CPU资源越多。

线程的四种实现方式

  1. 实现Runnable接口,覆写run方法
//创建线程
public class Ticket implements Runnable {
    private int num = 100;   
    @Override
    public void run() {
        System.out.println("实现Runnable接口:"+ --num);
    }
}

//启动线程
public class Demo{
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread thread = new Thread(ticket);
        thread.start();
    }
}
  1. 继承Thread类,覆写run方法
public class CustomThread extends Thread {
    @Override
    public void run() {
        System.out.println("继承Thread");
    }

    public static void main(String[] args) {
        //启动线程
        new CustomThread().start();
    }
}
  1. 实现Callable接口,覆写call方法【这种线程实现方式有返回值,并且可以抛出异常】
//通过泛型来指定该线程执行完毕后的返回值
public class CustomCall implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        return 123;
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CustomCall call = new CustomCall();
        //FutureTask是Future接口的实现类
        FutureTask<Integer> task = new FutureTask<Integer>(call);
        Thread thread = new Thread(task);
        thread.start();
        //接收返回结果
        System.out.println(task.get());
    }
}
  • Thread和Runnable的相同和不同

    • 相同

      Thread类实现了Runnable接口

      二者都覆写了run方法

    • 不同

      Runnable接口可以使多个线程共享一个资源

      Runnable接口的实现类更健壮,避免了java单继承的限制

线程安全

线程安全问题:多个线程同时操作共享资源会出现的问题

线程同步:多个线程在执行某部分代码时按照一定顺序执行

线程通信:多线程之间信息传递

线程安全性

  • 原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作

    Atomic包、CAS算法、synchronized、lock

  • 可见性:一个线程对主内存的修改可以及时被其他线程观察到

    synchronized、volatile

  • 有序性:保证指令的重排序不会影响到多线程并发执行的正确性——遵循happens-before原则

    java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性

    举例:i=i++;-----------temp=i; i=i+1; i=temp;

Volatile—内存可见性

  • 内存可见性问题:当多个线程进行操作共享数据时,彼此不可见。【高速缓存和寄存器】与【内存】中的数据一致性问题。

    高速缓冲:cpu和内存之间的存储器件 寄存器:cpu的一部分

举例:线程甲从堆内存中获取共享数据,在使用的过程中线程乙把共享数据给修改了,而线程甲却仍然还在使用之前读取的数据

  • Volatile修饰符的作用:当多个线程进行操作共享数据时,保证内存中的数据可见。

    使用volatile修饰符修饰的数据,每次都从内存中读取数据,且读取之前先跨越内存栅栏,使所有【写操作线程】将数据同步到内存中,读操作线程才获取数据

CAS算法—原子性

当多个线程同时修改共享数据时,有且仅有一个线程修改成功,其他线程再次读取主存中的共享数据,再次执行。

举例:当线程A读取主存中的共享数据i=0,当线程A要修改时会再次到内存中读取共享数据,将前后2次的数据相同均为0,则修改共享数据,如果不同则什么也不做。

使用原子性类型,如AtomicInteger、AtomicLong

Synchronized

  • Synchronized同步锁
    • 修饰类
    • 修饰普通方法
    • 修饰静态方法
    • 修饰代码块
  • Synchronized的作用:保证共享变量修改能够及时可见
  • Synchronized的缺点:加了同步锁,代码运行效率会下降
  • Synchronized的原理:每个对象都有一个监视器锁(monitor),没有获取到同步锁的线程无法继续执行下去,就以队列的方式进行等待,获取到同步锁的线程执行完后才会释放锁
  1. Synchronized 修饰代码块
//对象监视器(同步锁):没有获取到同步锁的线程无法继续执行下去,获取到同步锁的线程执行完后才会释放锁
Synchronized(对象监视器){
    //要操作的共享代码
}
  1. Synchronized 修饰方法:方法的返回值类型前加Synchronized
public synchronized void Sell() {
    //要操作的共享代码
}
  • 同步锁必须是多线程共享的对象,这样每个线程才都能获取到同一把锁

    同步普通方法的同步锁默认是:this

    同步静态方法的同步锁是:同步方法所在类的字节码对象【类名.class】

    同步普通代码块的同步锁可以是任意对象,需要根据代码逻辑的具体的实现我们自己去指定,锁对象可以通过身加static来实现多线程共享一把锁,一般我们可以使用使用this,但也有可能使用:当前类名.clsss

Lock

Lock是个接口,Lock底层是CAS算法(数据一致性)和Volatile(内存可见性)实现

获取锁的方法lock(),释放锁的方法unlock

ReentrantLock是Lock接口的实现类,使用时需要注意是否加static,实现多线程共享一把锁

public class ThreadDemo extends Thread {
    private static Integer num = 100;
    private static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();
                if (num > 0) {
                    System.out.println(Thread.currentThread().getName() + ":" + num--);
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

死锁

甲想进入A1屋里的B1屋,乙想进入B2屋里的A2屋,A1和A2屋共用一把锁,B1和B2屋共用一把锁

//锁A的单例
public class LockA {
    private static final LockA lockA = new LockA();
    private LockA() {
    }
    public static LockA getLockA() {
        return lockA;
    }
}
//锁B的单例
public class LockB {
    private static final LockB lockB = new LockB();
    private LockB() {
    }
    public static LockB getLockB() {
        return lockB;
    }
}

public class ThreadTask implements Runnable {
    private int i = 0;
    @Override
    public void run() {
        while (true) {
            if (i % 2 == 0) {
                synchronized (LockA.getLockA()) {
                    System.out.println("甲进入房间A");
                    synchronized (LockB.getLockB()) {
                        System.out.println("甲进入房间B");
                    }
                }
            } else {
                synchronized (LockB.getLockB()) {
                    System.out.println("乙进入房间B");
                    synchronized (LockA.getLockA()) {
                        System.out.println("乙进入房间A");
                    }
                }
            }
            i++;
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        new Thread(new ThreadTask()).start();
        new Thread(new ThreadTask()).start();
    }
}

Synchronized和Lock的区别

  • Synchronized是关键字,Lock是接口
  • Synchronized加锁和解锁由JVM管理,Lock加锁和解锁由java代码实现
  • Synchronized可以锁住类、普通方法、静态方法、代码块,Lock只能锁住代码块
  • Synchronized是悲观锁,Lock是乐观锁,Lock底层是CAS算法(数据一致性)和Volatile(内存可见性)实现

悲观锁和乐观锁的区别

悲观锁在获取到数据的时候,别人就不能修改,必须等自己修改完了释放了锁才能修改

乐观锁在获得到数据的时候,别人可以修改,自己修改时只需要判断数据获取数据之后是否被其他线程更新即可,如版本号

线程通信

Synchronize同步时,线程通信使用的Object类的wait、notify、notifyAll方法

Lock同步时,线程通信使用的是与Lock锁绑定的Condition对象的await、signal、signalAll方法

synchronized(同步锁对象){
    this.wait();//等待
	this.notify();//唤醒任意一个等待的线程
	this.notifyAll();//唤醒所有等待的线程
}

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
condition.await();
condition.signal();
condition.signalAll();
lock.unlock;

  • wait、notify、notifyAll是Object类的方法,不是Thread的

  • join、yield、sleep是Thread的方法,sleep是静态方法

  • 只有在同步控制方法或同步控制块里才能调用 wait() 、notify() 和 notifyAll()。

  • 执行了wait方法的线程,只有执行notify() 和 notifyAll()才能被唤醒

  • notify唤醒同一个监视器对象上的其他处于等待状态的任意一个线程

    notify唤醒同一个监视器对象上的其他所有等待状态的线程

程序运行原理

分时调度、抢占式调度

分时调度:每个线程运行时间固定

抢占式调度:优先级高的线程优先运行,优先级相同随机选择一个运行【JVM采用的抢占式调度】

创建线程的目的

为程序创建单独的执行路径,使多个程序同时运行

虚拟机会给每个线程开一个独立的虚拟机栈,栈内存是线程私有的,一个方法一个栈帧,后进来的方法不执行完,先进栈的方法就无法执行

获取当前线程的名称:Thread.currentThread().getName()

单例模式

单例模式分为懒汉模式和饿汉模式

饿汉模式

饿汉模式解决了线程安全问题,但在类加载时就会创建对象时,而不管对象是否要被使用,会占用内存

public class SingleInstance {
    private static final SingleInstance instance = new SingleInstance();
    private SingleInstance() {
    }
    public static SingleInstance getInstance() {
        return instance;
    }
}

懒汉模式

懒汉模式不加synchronized,线程不安全,因此需要加synchronized,保证线程安全

public class SingleInstance {
    private static SingleInstance instance;
    private SingleInstance() {
    }
    public static synchronized SingleInstance getInstance() {
        if (instance == null) {
            return new SingleInstance();
        }
        return instance;
    }
}

  • 双重验证:加了synchronized修饰符的懒汉模式效率不高,因为只有第一次调用getInstance方法时,才需要创建实例对象,因此当实例对象已经被创建时就需要进同步锁。
public class SingleInstance {
    private static SingleInstance instance;
    private SingleInstance() {
    }
    public static SingleInstance getInstance() {
        if (instance == null) {
            synchronized (SingleInstance.class) {
                if (instance == null) {
                    return new SingleInstance();
                }
            }
        }
        return instance;
    }
}

枚举模式
public enum Singleton {
    INSTANCE;
}

ThreadLocal

ThreadLocal底层是ThreadLocalMap类型的伪Map容器,以线程对象为key,以每个线程内需要存储的值为Value。如果一个线程有多个需要存储的值存入Map容器中,再将Map容器存入ThreadLocal类型的对象

同步和异步的区别

同步必须等方法执行完成后才能继续往下执行

异步则不需要等方法执行完成就可以继续往下执行

并发和并行的区别

  • 并发:多个任务交替执行 ,同一时间只有一个任务执行 【CPU上下文切换】
  • 并行:多个任务同时执行
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值