当前位置: 首页>后端>正文

java基础-线程

线程

定义线程的方式

  • 实现Runnable接口。

  • 继承Thread类。

  • 继承Callable接口(有返回值)。


public class FirstThread extends Thread{

    public void run() {

        System.out.println("线程体");

    }

}

new FirstThread().start();  // 启动线程


public class Thread1 implements Runnable{

// 共享  Runnable可以继承其他类、同时多个线程共享同一个对象。

private int age = 0;

@Override

public void run() {

        age++;

        System.out.println("Runable ------------"+age);  

  }

}

Thread1 thread = new Thread1();

new Thread(thread).start();

new Thread(thread).start();


 public class RtnThread implements Callable<Integer> {

    @Override

    public Integer call() throws Exception {

        int i = 0;

        for (; i < 100; i++) {

            System.out.println("循环变量I"+i);

        }

        return i;

    }

}

public class CallableTest {

    public static void main(String[] args){

        // 创建Callable对象

        RtnThread thread = new RtnThread();

        //使用Future来包装Callable对象

        FutureTask<Integer> task = new FutureTask<>(thread);

        for (int i = 0; i<100 ;i++){

            System.out.println(Thread.currentThread().getName()+"---------"+i);

            if(i==20){

                new Thread(task,"有返回值的线程").start();

            }

        }

        try{

            System.out.println("子线程返回值:"+task.get());

        } catch (InterruptedException e) {

            e.printStackTrace();

        } catch (ExecutionException e) {

            e.printStackTrace();

        }

    }

}

生命周期

  • 新建-就绪-运行-阻塞-死亡

    • 新建:new 关键字创建一个线程后。

    • 就绪:当线程对象调用start()方法之后。

    • 运行:处于就绪状态的线程获取到CPU,开始执行run方法中的线程体。

    • 阻塞:线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态(阻塞后-就绪)。

    • 死亡:线程执行完成。(异常或stop)

java基础-线程,第1张
线程状态.png

常用方法


demoThread.isAlive()//新建、死亡 false  其余状态 true

// 死亡状态的线程 不能start()

demoThread.join() // 被join的demoThread线程执行完成、才会执行主线程。

// 后台线程  (jvm中的GC)   当整个后台只剩下后台线程时候,后台线程将死亡

demoThread.setDaemon(true) //设置为后台线程

demoThread.isDaemon() // 判断是否为后台进程

Thread.currentThread() //获取驱动该任务的Thread对象的引用

线程休眠和让步

  • 休眠

//老版本

Thread.sleep(100);

//se5/6

TimeUnit.MILLISECONDS.sleep(100);

  • 让步

Thread.yield(); // 只是让线程重新转入就绪状态。

优先级

  • 优先级高的执行频率高

  • 设置优先级setPriority(),获取优先级getPriority(),

    • 范围1~10 建议用常量 不同操作系统支持不同。
  • 优先级尽量在run方法的开头设定。

线程同步方式

  • volatile 修饰

    • 保证了可见性性,当一个线程修改了某个变量时,其他所有线程都知道该变量被修改了。

    • 并不是线程安全的。

    • 这是因为当线程在获取到 race 变量的值,然后对其进行自增这中间,有可能其他线程对 race 变量做了自增操作,然后写回了主内存。而当前线程再将数据写回主内存时,就发生了数据覆盖。因此,就发生了数据不一致的问题。

  • synchronized 代码块和方法


// 代码块形式  对资源account进行加锁

public void run() {

        synchronized (account){

            if(account.getAccount()>(a-p*100)){

                while ((p*100)<a){

                    account.setAccount(account.getAccount()-100);

                    p++;

                }

                System.out.println("总共取钱"+p*100+"元"+",剩余金额:"+account.getAccount());

            }else {

                System.out.println("金额不足  取钱失败!");

            }

        }

    }

    // 方法加锁  static

public synchronized static void  pay(Account account,double p,double a){

        if(account.getAccount()>(a-p*100)){

            while ((p*100)<a){

                account.setAccount(account.getAccount()-100);

                p++;

            }

            System.out.println("总共取钱"+p*100+"元"+",剩余金额:"+account.getAccount());

        }else {

            System.out.println("金额不足  取钱失败!");

        }

}

  • ReentrantLock

public class Account {

    ...

    //需要声明这个锁

    private Lock lock = new ReentrantLock();

    ...

    public  void drawMoney(double money) {

        // 1、拿到是谁来取钱

        String name = Thread.currentThread().getName();

        lock.lock();

        try {

          ...

        }catch (Exception e){

            e.printStackTrace();

        }finally {

            lock.unlock();

        }

    }
  • synchronized和ReentrantLock区别
    描述 synchronized ReentrantLock
    用法 普通方法、静态方法、代码块 代码块
    获取锁方式 自动加锁和释放锁 手动加锁和释放锁
    类型 非公平锁 非公平锁(默认)、公平锁
    响应中断 不能响应中断(存在死锁问题) 可以响应中断(避免死锁问题)
    底层实现 jvm层面实现的 AQS程序级别的api实现的

  • 死锁

    • 产生的条件:互斥、请求保持、不剥夺条件、循环等待条件。
  • 乐观锁:认为访问共享资源不会出问题,无需加锁无需等待,只是在提交时候去验证对应的资源是否被其他线程修改,如果已被修改,则放弃本次操作。

  • 悲观锁:认为访问访问共享资源一定会出问题,每次访问共享资源前都要加锁,这样其他线程访问共享资源就会阻塞直到锁被上一个持有者释放。

    • 互斥锁:属于悲观锁,加锁失败后会释放cpu资源。
    • 自旋锁:属于悲观锁,加锁失败后线程一直等待,直到拿到锁为止。
  • 什么时候释放同步锁

    • 同步代码块执行完成

    • 同步代码块break、return、异常

    • 程序执行了同步监视器的wait()方法。

  • 不是释放同步锁

    • 执行sleep和yield方法。

    • 该线程的suspend()挂起。

线程组

  • 对一批线程分类管理。

  • 创建一个线程未显示指定线程组,默认:子线程和创建他的父线程一个组。

  • 某线程加入线程组,该线程就一直属于该线程组,直至线程死亡。


// 构造方法 创建线程加入线程组

Thread(ThreadGroup group,Runnable target)

Thread(ThreadGroup group,Runnable target,String name)

Thread(ThreadGroup group,String name)

// 构造方法 创建线程组  创建完成不允许修改名称

ThreadGroup(String name)

ThreadGroup(ThreadGroup parent,String name)  

int activeCount() //返回活动中的线程数目

interrupt() //中断此线程中的所有线程。

isDaemon() // 判断线程组是否为后台线程

setDaemon(boolean daemon) // 设置该线程组为后台线程组、(线程并不为后台线程)

setMaxPriority(int pri) //设置线程组的优先级

线程池Executor


例:ExecutorService e=Executors.newCachedThreadPool();

方法 概述
ExecutorService newCachedThreadPool(首选) 创建和所需数量相同的线程。
ExecutorService newFixedThreadPool(I) 创建固定数量线程的线程池。
ExecutorService newSingleThreadExecutor() 就像线程数量为1的FixedThreadPool,提交多任务、任务会排队,上一个任务结束下一个任务才会开始。所有任务使用相同的线程。按提交顺序。
ScheduledExecutorService newScheduledThreadPool(int) 创建具体指定线程数的线程池,它可以指定延迟后执行线程任务。
ScheduledExecutorService newSingleThreadScheduledExecutor() 创建具有1个线程的线程池,它可以在指定延迟后执行线程任务
  • ExecutorService 方法
方法 概述
Future<?> submit(Runnable task) 将一个Runnable对象指定给线程池。线程池空闲时执行Runnable对象代表的任务。Future代表返回值,如果没有则返回null。
<T> Future<T> submit(Runnable task,T result) result显示指定线程之执行结束的返回值。
<T> Future<T> submit(Callable<t> task) 将一个Callable对象提交给线程池
<T> void<T> execute(Runnable<t> command) Executor方法、只接受Runnable
  • ScheduledExecutorService 方法
方法 概述
ScheduledFuture<V> schedule(Callable<V> callable,long delay,TimeUnit unit) 指定callable任务再delay延迟后执行。
ScheduledFuture<?> schedule(Runnable command,long delay,TimeUnit) 指定command任务将delay延迟后执行。
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long init,long period,TimeUnit unit) 指定command任务将再init延迟后执行,init后开始执行,依次再init+period*n处重复执行。
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,TimeUnit unit) 创建并执行一个在给定初始延迟首次启动的定期操作,随后,每次一次执行终止和下一次执行开始之间都存在一定延迟。如果任一次执行出现异常,就会取消后续执行。
  • 使用线程池执行任务的步骤

    • 调用Executors类的静态方法创建一个ExecutorService对象。

    • 创建Runnable实现类或者Callable实现类的实例,作为线程执行任务。

    • 调用ExecutorService对象的submit方法来提交Runnable实例或者Callable实例。

    • 当不想提交任何任务时,调用shutdown方法来关闭线程池。


        ExecutorService pool= Executors.newFixedThreadPool(6);

        pool.submit(new TestThread());

        pool.submit(new TestThread());

        pool.shutdownNow();

线程池5种状态

  • RUNNING : 可以接受新任务和执行已添加的任务。
  • SHUTDOWN : 不接收新任务,但处理已添加的任务。
  • STOP : 不接收新任务,不处理已添加的任务,并且中断正在执行的任务。
  • TIDYING : 所有任务已中止,记录的任务数量为0,执行terminated()。
  • TERMINARED:当terminated()执行完成,线程池彻底中止,变为TERMINARED状态。


    java基础-线程,第2张
    线程池状态.jpg

为什么不建议使用Executors创建线程池

  • Executors各个方法都存在弊端
    • newFixedThreadPool和newSingleThreadExecutor 使用队列LinkedBlockingQueue(默认使用integer.MAX_VALUE,可以理解无界阻塞队列),导致其无限增大,撑爆内存(OOM)。
    • newCachedThreadPool和newScheduledThreadPool 主要的问题是线程最大个数为integer.MAX_VALUE,可能会创建非常多数量的线程从而撑爆内存(OOM)。
    使用ThreadPoolExecutor创建
    new ThreadPoolExecutor(核心线程池大小,线程最大数量,线程活动保持时间,线程保持时间单位,任务队列,线程工厂,饱和策略)
    
    • corePoolSize 线程池核心线程大小
      • 线程池中会维护一个最小的线程数量,即使这些线程处理空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。这里的最小线程数量即corePoolSize。
    • maximumPoolSize 线程池最大线程数量
      • 一个任务被提交到线程池以后,首先会找有没有空闲存活线程,如果有则直接将任务交给这个空闲线程来执行,如果没有则会缓存到工作队列(后面会介绍)中,如果工作队列满了,才会创建一个新线程,然后从工作队列的头部取出一个任务交由新线程来处理,而将刚提交的任务放入工作队列尾部。线程池不会无限制的去创建新线程,它会有一个最大线程数量的限制,这个数量即由maximunPoolSize指定。
    • keepAliveTime 空闲线程存活时间
      • 一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定。
    • unit keepAliveTime的计量单位
    • workQueue 工作队列
      • 新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk中提供了四种工作队列:ArrayBlockingQueue 有界队列、LinkedBlockingQuene 无界队列、SynchronousQuene 直接提交、PriorityBlockingQueue 优先级队列。
    • threadFactory
      • 用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程做些更有意义的事情,比如 设定线程名、设置daemon和优先级等等。
    • handler 拒绝策略
      • 当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,这时如果有新任务提交进来,该如何处理呢。这里的拒绝策略,就是解决这个问题的,jdk中提供了4中拒绝策略:
        • CallerRunsPolicy 只用调用者所在线程来运行任务
        • AbortPolicy 直接抛出异常
        • DiscardPolicy 不处理,丢弃掉
        • DiscardOldestPolicy 丢弃队列里最近的一个任务,并执行当前任务.

线程相关类

  • 包装线程不安全集合(Collections提供的静态方法)
方法 描述
<T> Collection<T> synchronizedCollection(Collection<T> c) 返回指定collection对应线程安全的collection
static <T> List<T> synchronizedList(List<T> list) 返回指定List对应线程安全的List对象
static <K,V>Map<K,V> synchronizedMap(Map<K,V> m) 返回指定Map对象对应的线程安全的Map对象
static <T>Set<T> synchronizedSet(Set<T> s) 返回指定Set对应的线程安全的Set
static <K,V> SortedMap<K.V> synchronizedSortedMap(SortedMap<K,V> m) 返回指定SortedMap对象所对应的线程安全的SortedMap对象。
static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s) 返回指定SortedSet对象对应的线程安全的SortedSet对象
  • 线程安全集合类

ConcurrentHashMap、ConcurrentLinkedQueue。


https://www.xamrdz.com/backend/34c1940410.html

相关文章: