• java之多线程


    前言:本章主要记载java多线程知识的一些沉淀~

    一、线程的基础知识

    1、线程和进程的区别:

    进程:系统中一个执行的程序,可称为进程,进程中可以有多个执行的任务(多个顺利执行流)也就是一个线程

    一个程序中至少有一个进程,一个进程可包含多个线程,但至少要有一个线程

    2、进程的有哪些特性:

    独立性:每个进程都是独立存在的实体,拥有自己独立的资源,在没有经过进程本身运行的情况下,其他线程不可以直接访问进程的地址空间

    动态性:进程是一个在系统中获得的指令集合,进程加入了时间概念,拥有自己的生命周期和不同的状态,具有动态性

    并发性:多个进程可以在多个处理器上并发执行,且进程直接互不影响

    敲黑板:并发和并行的区别是什么呢?

    并行:同一时间发布多条指令, 不同的处理器上进行运作

    并发:同一时刻只发布一条指令 ,通过一些特定的指令,进行轮换执行,从而达到同时执行的效果

    并发和并行看似相同,但是确实俩个不同的概念

    3、面试经常会问到使用多线程有哪些优势,以及为什么要使用多线程?

    先说后者,单线程的功能是非常有限的,随着项目的功能延伸和需求量的增加,单线程很难满足所有的用户需求

    举个栗子:

    单线程好比餐厅只有一个服务员,他只能做完第一件事才能做第二件事

    多线程就像是餐厅有多个服务员,他们各干各的事情,互不打扰

    优势:1、进程直接不能共享内存,线程之间可以共享

       2、多线程来实现任务并发比进程的效率更高

    二、线程的创建和启动

     1、线程创建有3种方式

    NO.1:继承Thread类,重写该类的run()方法

    public class demo extends Thread{
        private int i;
        @Override
        public void run() {
      //run方法线程体 for(;i<100;i++){ System.out.println(getName()+i);//1、继承Thread类时,直接使用this就可以换取当前线程,Tread对象的getname是返回当前线程的名字,所以这里可以直接调用getname } } public static void main(String[] args) {
      //主线程体main for (int i = 0; i <100; i++) { System.out.println(Thread.currentThread().getName() + i + "个"); if (i == 20) { new demo().start();//2、因继承了Tread类,子类直接创建可代表线程对象 new demo().start(); } } } }

    //备注:3、继承Tread类来创建线程类时,多个线程之间无法共享线程中的实例变量也是代码中的i

    NO.2:实现Runnable接口,重写run()方法

    public class RunnableDemo implements Runnable  {
        private  int i;
    
        @Override
        public void run() {
            for (;i<100;i++){
                System.out.println(Thread.currentThread().getName()+"11"+i);//1、通过Runable接口获取当前线程,只能使用Tread.currentTread().getname()
            }
        }
        public static void main(String[] args) {
            for (int i=0;i<100;i++){
                System.out.println(Thread.currentThread().getName()+"22"+i);
                if(i==20){
                    RunnableDemo runnableDemo=new RunnableDemo();//2、创建Runable对象只能作为线程对象的target
                    new Thread(runnableDemo,"新线程1哈哈").start();
                    new Thread(runnableDemo,"新线程2哈哈").start();
                }
            }
        }
    }


    //备注:3、两个子线程的i变量是连续的,也就是说采用Runnable接口的方式,创建多线程是可以共享线程类的实例变量

    小结:

    继承Trread类

    1、具备多线程能力

    2、启动线程:子类对象.start()

    3、不推荐使用:避免单继承的局限性

    实现Runable接口

    1、具备多线程能力

    2、启动线程:传入目标对象+Thread对象.start()

    3、推荐:灵活方便同一个对象被多个线程使用

    //一份资源
    RunnableDemo runnableDemo=new RunnableDemo();
    //多个代理 
    new Thread(runnableDemo,"张三).start();
    new Thread(runnableDemo,"李四").start();
    new Thread(runnableDemo,"王二").start();

    NO.3:实现Callable接口,重写call()方法

    1、实现Callable接口,需要有返回类型

    2、重新call方法需要跑出异常

    3、创建目标对象

    4、创建执行服务

    5、提交执行

    6、获取结果

    7、关闭服务

    平常工作中用这种方式不是太多,这里只是了解一下~

    实现Callable接口与Runable不同的最重要的是后4步

    /**
     * 好处:
     *1、可以定义返回值
     *2、可以跑出异常 
     * */
    public class CallableDemo implements Callable<Boolean>{
    
        @Override
        public Boolean call() throws Exception {
            //线程体
            for(int i=0;i<100;i++){
                System.out.println("我在看代码");
            }
            return true;
        }
    
        public static void main(String[] args) throws ExecutionException,InterruptedException {
            CallableDemo callableDemo=new CallableDemo();
            //创建执行服务
            ExecutorService executorService= Executors.newFixedThreadPool(1);
            //提交执行
            Future<Boolean> future=executorService.submit(callableDemo);
            //获取结
            Boolean result=future.get();
            System.out.println("打印返回结果"+result);
            //关闭服务
            executorService.shutdownNow();
    
        }
    
    }
    

    2、线程的生命周期

    新建,就绪,运行,阻塞,死亡

     

     常见线程方法合集

     

     线程休眠sleep

    sleep可以模拟网络延时,倒计时等

    sleep时间达到后线程进入就绪状态

    sleep可以设置当前阻塞的毫秒数

    每一个对象都有一个锁,sleep不会释放锁

    package com.snowy.snowy.controller;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    //sleep相关
    
    public class TendownDemo {
        public static void main(String[] args) {
            Date date=new Date(System.currentTimeMillis());//获取系统当前时间
            while (true){
                try {
                    Thread.sleep(1000);
                    System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
                    date=new Date(System.currentTimeMillis());//更新当前时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
    //        try {
    //            tendown();
    //        } catch (InterruptedException e) {
    //            e.printStackTrace();
    //        }
        }
    
        public static void tendown() throws InterruptedException {
            //使用for循环来模拟倒计时
    //        for(int num=10;num<=10;num--){
    //            Thread.sleep(1000);
    //            System.out.println(num);
    //            if (num<=1){
    //                break;
    //            }
    //        }
            int i=10;
            while (true){
                Thread.sleep(1000);
                System.out.println(i--);
                if (i<=0){
                    break;
                }
            }
        }
    }
    

      线程停止

    注意:不推荐使用jdk提供的stop()和destroy()方法 ,已废弃

    可以使用一个标识变量当flag=false,则线程终止运行

    package com.snowy.snowy.controller;
    
    public class StopDemo implements Runnable {
    
        //设置一个标识
        private boolean flag=true;
    
        @Override
        public void run() {
            int i=0;
            //线程体使用该标识
            while(flag){
                System.out.println("运行起来了"+i++);
            }
        }
    
        //编写一个停止线程的方法
        public void  stop(){
            this.flag=false;
        }
    
        public static void main(String[] args) {
            StopDemo stopDemo=new StopDemo();
            new Thread(stopDemo).start();
            for(int i=0;i<1000;i++){
                System.out.println("main"+i);
                if (i==900){
                    stopDemo.stop();
                    System.out.println("线程需要停止了");
                }
            }
        }
    
    }
    

      线程礼让yield

    1、让当前正在执行的线程暂停,但不阻塞

    2、执行yield()当前线程为就就绪状态

    3、让cpu重新调度,具有随机性不一定礼让成功

    例如:A,B俩个线程,cup正在执行A线程,然后A线程执行了yield方法礼让B线程,这时候A线程的状态为就绪状态(B线程也是继续状态),cpu会重新调度,如果调用B就礼让成功,若还是让A执行就说明没有礼让成功

    public class YieldDemo implements Runnable{
    
        public static void main(String[] args) {
            YieldDemo yieldDemo=new YieldDemo();
            new Thread(yieldDemo,"A").start();
            new Thread(yieldDemo,"B").start();
        }
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"开始");
            Thread.yield();
            System.out.println(Thread.currentThread().getName()+"结束");
        }
    }
    

    join合并线程

    尽量少用,容易造成线程阻塞

    合并线程,等此线程执行结束后,在执行其他的线程,其他的线程阻塞

    例子:大家都在排队买东西,小明是老板熟人,直接插队,其他的人只能等小明买完东西才能轮着自己买

    public class JoinDemo implements Runnable{
        @Override
        public void run() {
    //        for(int i=0;i<1000;i++){
             //插队线程
                System.out.println(Thread.currentThread().getName()+"插队插队vip");
    //        }
        }
    
        public static void main(String[] args) throws InterruptedException {
            JoinDemo joinDemo=new JoinDemo();
            Thread thread=new Thread(joinDemo);
            thread.start();
    
            //主线程
            for(int i=0;i<1000;i++){
                if (i==200){
                    thread.join();
                }
                System.out.println("main"+i);
            }
    
        }
    }
    

     附加: 获取线程的状态:Thread.State state=thread.getState();

    线程的优先级Priority

     java提供了一个线程调度器来监控程序中启动后进入就绪的所有线程,线程调度器按照线程的优先级决定调度那个线程来执行

    线程的优先级范围1--10:

    Thread.max_priority=10

    Thread.min_priority=1

    Thread.norm_priority=5

    获取、更改优先级

    getPriority()、setPriority(int xx);

    注意:线程优先级高不一定先执行,看cpu调度,但是优先级高权重也就高,执行的机会会增加

     守护线程daemon

    setDaemon(true);//设置守护线程

    isDaemon()//判断该线程是否是守护线程

    线程分为守护线程和用户线程

    虚拟机必须确保用户线程执行完毕---mian()就是一个用户线程

    虚拟机不用等待守护线程执行完毕---GC垃圾回收线程,不用等待虚拟机执行完成就结束了

    守护线程的作用:后台记录操作日志、监控内存、垃圾回收等待

    当所有的前台线程死亡时,守护线程也会跟着死亡
     

    线程同步

    多个线程操作同一资源

    并发:同一对象被多个线程同时操作

    处理多线程问题时,多个线程访问同一对象,并且某些线程还想修改这个对象,这时候就需要用到线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一线程再使用,光有队列还是不够的还需要锁

    为了保证数据在方法中被访问时的正确性,在访问时加入了锁机制sychronized,队列+锁才能保证线程同步的安全性

    存在的问题:

    1、一个线程只有锁可以导致其他需要此锁的线程挂起

    2、在多线程的竞争下,加锁,释放锁会导致较多的上下文切换,和调度延时,引起性能问题

    3、若一个优先级高的线程等待一个优先级低的线程释放锁,可能会引起优先级倒置,引起性能问题

    同步方法:sychronized()

    sychronized方法控制“对象”的访问,每个对象对应一把锁,每个sychronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到方法返回才释放锁,后面的线程获取这个锁,才能继续执行

    例子:一堆人排队去面试,第一个先进去把门关上了相当于上锁,等他面试完了后,门开了相当于锁释放了,后面排队的人进去时在把门关上相当于上锁

    从而达到加锁-->修改--->释放锁的机制

    同步块:sychronized(obj){

        }

    obj称之为同步监视器

    obj可以是任何对象,但是推荐共享资源作为同步监听器

    同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身或者是class

    锁的对象是变化的量,需要增删改

    附加:在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,并发包

    CopyOnWriteArrayList集合是安全的

    死锁

    某一个同步块同事拥有“俩个以上对象的锁”时,就可能会发生死锁的问题

    大白话:多个线程互相抱着对方需要的资源,然后形成僵持

    例子:俩个女生化妆,需要镜子和梳子,A持有镜子,B持有梳子,他们都想要对方手里的东西,但是又不放自己手里的东西,这时候就会产生死锁

    产生死锁的条件:

    1、互斥条件:一个资源只能被一个进程使用(俩个女生都想拿镜子,俩个进程都想去访问同一个资源)

    2、请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放(A拿了镜子想去拿梳子,但是我不想把镜子给你,而且我还想拿到梳子)

    3、不剥夺条件:进程已获得的资源,在使用完之前,不能强行剥夺(A在用镜子,还没用完,B就想来拿镜子)

    4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系(A想要B的东西,B想要A的)

    以上4种可能产生死锁的条件,我们只要想办法破解其中一种或多种就可以避免死锁的发生

    Lock(锁)

    jdk5.0开始,java提供了线程同步机制通过显式定义同步锁对象来实现同步,同步锁使用Lock对象充当(sychronized是隐式,锁了谁不太好弄,也看不到他的开始和结束)

    (可重复锁)ReentranLock类实现了lock,比较常用的是ReentranLock,可以显示加锁,释放锁

    class A{
       
        private  final ReentrantLock lock=new ReentrantLock();
    
        @Override
        public void run() {
           
                    lock.lock();//加锁,建议使用try catch
                   
                        try {
                  //保证线程安全的代码
                        }
                     finally {
                      lock.unlock();
                //如果有异常代码,要将unlock()写入finally语句块
              }
                }
        }
    

      总结:lock比对sychronized

    1、lock是显式锁手动开启和关闭,sychronized是隐式锁出了作用域自己释放

    2、lock只有代码块,sychronized有代码块锁和方法锁

    3、使用lock锁,jvm花费较少的时间来调度线程,性能会更好一些,并且有扩展性因为提供更多的子类

    4、优先使用顺序:

    Lock-->同步代码块(已经进入方法体,分配了相应资源)-->同步方法(在方法体之外)

    线程协作通信(生产者消费模式)

    分析:这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者相互依赖,互为条件

    在消费者问题中光有sychronized是不够的

      --sychronized可阻止并发更新同一个共享资源,实现了同步

      --sychronized不能用来实现不同线程之间的消息传递(通信)

    java提供了以下几种方法解决线程之间的通信问题

    wait()------->表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁

    wait(long timeout)---->指定等待的毫秒数

    notify()---->唤醒一个处于等待状态的线程

    notifyall()---->唤醒同一个对象所有调用wait()方法的线程,优先级别高的线程优先调度

    注意:都是Object类的方法,都只能在同步方法或者代码块中使用,否则会跑出异常

     解决线程通信方式一:

    举例:小明(消费者)去KCF点餐,要了一份薯条,前台(数据缓存区),后厨(生产者)

    1、对于生产者,没有生产产品之前,要等值消费者等待,生产了产品后要通知消费者消费(小明点的薯条没有了,后厨告诉前台,前台告诉小明需要等待,等后厨做好 ,通知小明来拿)

    2、对于消费者,在消费之后,通知生产者已经结束消费,需要生产新的产品以供消费(小明拿走薯条后,告诉后厨我拿走了,后厨接着准备薯条以供其他消费者)

    并发协作模式生产者/消费者--->管程法

    生产者讲生产好的东西放入缓冲区,消费者从缓冲区拿东西(消费者不能直接使用生产者的数据)

    解决线程通信方式二:

    并发协作模式生产者/消费者--->信号灯法

    通过一个标志位来判断,如果为true等待,为false唤醒

    大白话:红灯停绿灯行~

    线程池

    背景:经常销毁和创建,使用量特别大的资源,比如并发情况下的线程,等性能要求影响比较大

    思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁,实现重复利用(类似于公共交通工具)

    好处:提高响应速度,降低资源消耗,便于线程管理

    //线程池
    public class PoolTest {
        public static void main(String[] args) {
            //创建线程池
            //参数为线程的大小
            ExecutorService service= Executors.newFixedThreadPool(10);
            //执行---可回顾callable,用execute没有返回值,submit有返回值
            service.execute(new MyTread());
            service.execute(new MyTread());
            service.execute(new MyTread());
            service.execute(new MyTread());
            //关闭
            service.shutdown();
        }
    }
    
    class MyTread implements Runnable{
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }

    回顾:3种方式实现多线程

    //回顾线程创建方式
    public class TreadAllTest {
        public static void main(String[] args) {
    
            new Tread1().start();//Thread
            new Thread(new Tread2()).start();//Runnable
            FutureTask<Integer> futureTask=new FutureTask<Integer>(new Tread3());//Callable
            new Thread(futureTask).start();
            try {
                Integer integer=futureTask.get();
                System.out.println("1111"+integer);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    class Tread1 extends Thread{
        @Override
        public void run() {
            System.out.println("Tread1");
        }
    }
    
    class Tread2 implements Runnable{
        @Override
        public void run() {
            System.out.println("Tread2");
        }
    }
    
    class Tread3 implements Callable<Integer> {
    
        @Override
        public Integer call() throws Exception {
            System.out.println("Tread3");
            return 20;
        }
    }
    

      

  • 相关阅读:
    在浏览器中输入URL后,执行的全部过程。会用到哪些协议?(一次完整的HTTP请求过程)
    线程的5种状态详解
    哈希表(Hash Table)原理及其实现 【转载】
    notify() 和 notifyAll() 有什么区别? wait()
    B树、B-树、B+树、B*树介绍,和B+树更适合做文件索引的原因 【转】
    pthread_join和pthread_detach的用法 【】转载】
    什么是进程?什么是线程
    进程间的五种通信方式介绍【转载】
    81. Search in Rotated Sorted Array II
    33. Search in Rotated Sorted Array
  • 原文地址:https://www.cnblogs.com/minet/p/12335813.html
Copyright © 2020-2023  润新知