栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Java多线程学习

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Java多线程学习

Java多线程学习
  • 线程的调度
  • 进程与线程
  • 线程的生命周期
  • 线程的创建
    • 继承Thread类
    • 实现Runable接口
    • Thread与Runable联系
    • Callable与Future
    • 常用方法介绍
  • 线程安全与效率
    • Java多线程的内存模型
    • 可能产生的问题
    • 如何确保线程安全
      • volatile关键字
      • CAS操作
      • synchronized关键字
      • Lock接口
      • 各种概念锁

线程的调度
  1. 抢占式(重点)
    在抢占模式下,操作系统负责分配CPU时间给各个进程,一旦当前的进程使用完分配给自己的CPU时间,操作系统将决定下一个占用CPU时间的是哪一个线程。
  2. 协作式
    协作式线程调度器在将cpu控制权交给其他线程钱,会等待正在运行的线程自己去暂停,然后才可以交给另外一个线程。一些早期或者特殊用途的虚拟机 可能会使用这种方式。
进程与线程

进程的运行需要较多的资源,因此,操作系统能够同时运行的进程数量是有限的。进程间的切换与通信也存在较大的开销。为了能够并行的执行更多的任务,提升系统的效率,才引入了线程,线程间的切换开销比进程间的切换开销小得多。因此线程是运行的基本单元(CPU调度的基本单位)、进程是资源分配的基本单元。
线程是进程的一部分,一个进程拥有1~~N个线程,线程共享所在进程的资源。
线程分为守护线程和用户线程。

  • 守护线程:做一些辅助操作,程序不会等待守护线程结束而结束。
    thread.setDaemon(true)将一个线程设置为守护线程必须在thread.start()之前设置。
    在Daemon线程中产生的新线程也是Daemon的。
    不是所有的应用都可以分配给Daemon线程来进行服务的,比如读写操作或者计算逻辑。
  • 用户现场:用户线程结束,程序也就结束了,即使守护线程没有终止也会随之停止。
线程的生命周期

  • 新建:线程被new出来的状态。
  • 就绪:使用start开启线程,此时线程不一定会执行,处于竞争资源的时候。
  • 运行:获取到了运行资源,执行代码。
  • 阻塞:不执行代码,暂时放弃CPU的使用权,有可能会释放锁,进行等待,直到满足某个条件时回到就绪状态。
  • 死亡:线程当中代码执行完毕或者执行过程中出现异常,或被强行中断(不建议)。
线程的创建 继承Thread类

通过继承Thread类,覆盖其run()方法即可编写一个线程。

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

new MyThread().start();让线程处于就绪状态。

实现Runable接口

Java单继承的特性并不满足开发需求所以可以通过实现Runable接口来创建线程。

public class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

new Thread(new Mythread()).start();让线程处于就绪状态。

Thread与Runable联系

在Runable接口源码中其实只定义了一个抽象的run方法。
Runable源码。

@FunctionalInterface
public interface Runnable {
    
    public abstract void run();
}

Thread实现了Runable并对其功能进行了扩展,扩展的功能主要还是通过JNI调取的由C++/C写的代码。
Thread类部分源码。

public
class Thread implements Runnable {
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    public synchronized void start() {
        
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                
            }
        }
    }
    ······
    ······
    ······
    ······
    ······
    ······
}
Callable与Future

Java5添加了Callable接口和Future接口,使得线程可以产生返回值,并且可以抛出异常。

public class MyThread implements Callable {
    @Override
    public Integer call() throws Exception {
        sleep(3000);
        return 10;
    }
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        Futuref=executor.submit(new MyThread());
        try {
            System.out.println(f.get()); //执行到此处会被阻塞,直到获取到返回值。
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        executor.shutdown();
    }
}
常用方法介绍
方法作用
t.start();启动线程t
t.checkAccess();检查当前线程是否有权限访问线程t
t.interrupt();尝试通知线程t中断
Thread.currentThread().isInterrupted()检查当前线程是否被要求中断
t.setPriority(int)设置线程的优先级,1~10依次增大
t.isDaemon();判断线程t是否为守护线程
t.setDaemon(boolean)在start前设置线程t是否为守护线程,默认false
t.isAlive();判断线程t是否存活
t.join(long);等待线程t终止在执行 ,参数为超时时间
t.setName(String);设置线程名称
yield();让当前线程让出CPU,转为就绪状态,重新争夺资源,当只有大于等于t优先级的线程才能被执行。
Thread.currentThread();得到当前线程对象的引用
Thread.sleep(long)让当前线程转为阻塞状态,参数表示阻塞时间,后转为就绪状态,不会释放锁
wait(long);Object当中的方法,和sleep类似,但是会释放锁,所以只能在synchronized作用域中使用
t.notify();wait()的对应方法,可以唤醒线程t,同样在synchronized作用域中使用
线程安全与效率 Java多线程的内存模型


我们声明的对象的成员变量、静态变量等非局部变量都位于主内存中,而每个线程又都有自己的工作内存,工作内存是CPU中寄存器与高速缓存的抽象描述,并不是真正的一块内存,一般情况下,当主线程获得CPU使用权后,开始执行前会将主内存当中的变量load到工作内存,处理完成在save会主内存。

可能产生的问题

线程安全主要考虑的三个方面

  • 可见性:一个线程对某个变量修改时,其他线程可以立即检测到。
  • 有序性:禁止指令重排。
  • 原子性:线程在执行某一连续操作时要么都执行,要么都不执行。

线程同步的机制(四个)

  • 临界区:在同一时刻只允许一个线程执行的代码块称为临界区,临界区通常用锁机制来实现。
  • 互斥量:互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。
  • 信号量:信号量允许有限数量的线程在同一时刻访问同一资源。
  • 事件:用来通知线程有一些事件已发生,从而启动后继任务的开始。

可能产生的问题

  • 饥饿:某个线程长时间得不到所需要的资源,而总是经常性的处于就绪状态。
  • 死锁:两个或两个以上线程在资源竞争中互不相让,形成资源等待或长时间处于阻塞状态。
如何确保线程安全 volatile关键字

作用:禁止指令重排和保证其可见性,但是不能解决原子性。

原理: 被volatile关键字修饰的变量会存在一个“lock:”的前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能,它的作用是将本处理器的缓存写入了内存,该写入动作也会引起别的处理器或者别的内核无效化(Invalidate,MESI协议的I状态)其缓存,这种操作相当于对工作内存与主内存中的变量做了一次“store和write”操作。所以通过这样一个操作,可让前面volatile变量的修改对其他处理器立即可见。lock指令的更底层实现:如果支持缓存行会加缓存锁(MESI);如果不支持缓存锁,会加总线锁。

CAS操作

更新某个变量前,检查变量的当前值是否符合期望值,如果相符就使用新值替换当前值,否则自旋尝试重新获取,直到成功。

可能存在的问题:

  • ABA问题:如果一个值原来是A,接着变成了B,之后又变成了A,执行CAS时会发现值没有预期当中的变化不执行。
  • 循环开销:当冲突严重时,自旋的开销严重增加CPU负担。
  • 只能保证一个共享变量的原子性。
synchronized关键字

是Java的关键字,被用于标记一个方法或代码块,通过给对象上锁的方式,将自己的作用域变为一个临界区,由编译器负责加锁与解锁的操作,是自动进行的。

synchronized悲观锁、偏向锁—>轻量级锁—>重量级锁(逐渐升级)、非公平锁、可重入锁、独占锁

作用范围:

  • 修饰代码块:
        synchronized(data){
            ······
            ······       
        }

作用范围是{}括起来的代码块,作用对象是()内的对象,表示获得该对象的锁。

  • 修饰非静态方法:
    public synchronized void set(){
        ······
        ······
    }

被修饰的方法称为同步方法,其作用范围是整个方法,作用的对象是拥有这个方法的对象,执行此方法时会先获取调用此方法的对象的锁。
注意:在定义接口方法时不能使用synchronized来修饰。构造方法也不能用synchronized来修饰,但是构造方法体内可以使用synchronized来修饰。

  • 修饰静态方法:
    public synchronized static void set(){
        ······
        ······
    }

其作用范围是整个静态方法,作用对象是该静态方法所在的类(静态方法属于类不属于对象),锁定的是该类本身。

  • 修饰一个类:
        synchronized(MyThread.class){
            ······
            ······
        }

作用范围是{}括起来的部分,和修饰静态方法一样,获取的是类本身的锁。

Lock接口

与synchronized类似,对需要的对象添加锁,与synchronized不同,需要unlock主动去解锁,最好放在finally块当中。

具体实现类:

实现类作用
ReentrantLock()可重入锁
ReentrantReadWriteLock()读写锁
readLock()读锁(在ReentrantReadWriteLock类当中获取)
writeLock()写锁(在ReentrantReadWriteLock类当中获取)
各种概念锁
解释
乐观锁总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据(CAS操作)。
悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁
自旋锁线程循环地去获取锁
非自旋锁/互斥锁普通锁。获取不到锁,线程就进入阻塞状态。
适应性自旋锁自旋一定次数或时间获取不到锁变为非自旋锁
偏向锁无实际竞争,且将来只有第一个申请锁的线程会使用锁。
轻量级锁无实际竞争,多个线程交替使用锁;允许短时间的锁竞争。可以优化为自旋形式
重量级锁有实际竞争,且锁竞争时间长。一般为互斥形式
公平锁指在分配锁前检查是否有线程在排队等待获取该锁,优先将锁分配给排队时间最长的线程(先来先服务)
非公平锁指在分配锁时不考虑线程排队等待的情况,直接尝试获取锁,在获取不到锁时再排到队尾等待(谁抢到是谁的)
可重入锁/递归锁即若当前线程执行某个方法已经获取了该锁,那么在该方法或其调用的方法中尝试再次获取锁时,可以获取到
不可重入锁即若当前线程执行某个方法已经获取了该锁,那么在该方法或其调用的方法中尝试再次获取锁时,就会获取不到被阻塞。
排他锁/写锁/独占锁只能被一个线程读取和修改,其他线程获取时会被阻塞
共享锁/读锁允许多个线程读取,但不能修改
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/389721.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号