美文网首页干货分享
深度剖析——Java多线程

深度剖析——Java多线程

作者: 小白菜aaa | 来源:发表于2020-09-29 20:44 被阅读0次

    线程简介

    任务、进程、线程、多线程

    一个进程可以有多个线程,如视频中同时听声音、看视频、看弹幕等

    Process与Thread

    程序s是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念

    进程则是执行程序的一次执行过程,是一个动态的概念。是系统组员分配的单位

    一个进程包含了若干个线程。一个进程至少有一个线程,不然没有存在的意义。线程是cpu调度和执行的单位。

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

    核心概念:

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

    线程创建
    Thread、Runnable、Callable

    Thread class 继承Tread类
    Runnable接口 实现Runnable接口
    Callable接口 实现Callable接口

    Thread类:
    自定义线程继承thread类
    重写run()方法,编写线程执行体
    创建线程对象,调用start()方法启动线程`package Thread;

    /**
     * 继承thread类
     */
    //创建线程方式  :继承Thread类,重写run()方法。调用
    public class TestThread1 extends Thread {
        @Override
        public void run() {
            //run方法线程体
            for (int i=0;i<20;i++){
                System.out.println("我在看代码---"+i);
            }
    
        }
    
        public static void main(String[] args) {
            //创建一个线程对象
            TestThread1 ts1 =new TestThread1();
            ts1.start();
    
            //main线程,主线程
            for (int i=0;i<20;i++){
                System.out.println("我在学习多线程---"+i);
            }
        }
        //
    }
    `
    
    

    总结:
    线程开启不一定立即执行,由cpu调度执行

    案例:
    在网络上下载图片

    package Thread;
    
    import org.apache.commons.io.FileUtils;
    
    import java.io.File;
    import java.io.IOException;
    import java.net.URL;
    
    /**
     * 练习Thread,实现多线程同步下载图片
     */
    public class TestThread2 extends Thread{
        private  String url;//网络图片地址
        private  String name;//保存文件名
    
        public TestThread2(String url,String name){
            this.url =url;
            this.name =name;
        }
        //下载图片得执行体
        @Override
        public void run() {
            WebDownloader webDownloader = new WebDownloader();
            webDownloader.downloader(url,name);
    
            System.out.println("下载得文件名字为:"+name);
        }
    
        public static void main(String[] args) {
            TestThread2 t1 = new TestThread2("https://pic.baike.soso.com/ugc/baikepic2/2161/20200620034717-762358680_jpeg_300_208_15324.jpg/300","1.jpg");
            TestThread2 t2 = new TestThread2("https://pic.baike.soso.com/ugc/baikepic2/2161/20200620034717-762358680_jpeg_300_208_15324.jpg/300","2.jpg");
            TestThread2 t3 = new TestThread2("https://pic.baike.soso.com/ugc/baikepic2/2161/20200620034717-762358680_jpeg_300_208_15324.jpg/300","3.jpg");
    
            //
            t1.start();
            t2.start();
            t3.start();
        }
    
    }
    //下载器
    class WebDownloader{
        //下载方法
    
        public void downloader(String url,String name)  {
           try {
               FileUtils.copyURLToFile(new URL(url),new File(name));
           }catch (IOException e){
               e.printStackTrace();
               System.out.println("IO异常,downloader方法出现问题");
           }
        }
    }
    
    

    实现Runnable

    定义MyRunnable类实现Runnable接口
    实现run()方法,编写线程执行体
    创建线程对象,调用start()方法启动线程

    package Thread;
    
    /**
     * 实现runnable接口
     */
    //创建线程方式:实现runnable接口
    public class TestThread3 implements Runnable{
        @Override
        public void run() {
            //run方法线程体
            for (int i=0;i<20;i++){
                System.out.println("我在看代码---"+i);
            }
    
        }
    
        public static void main(String[] args) {
            //创建一个runnable线程对象
            TestThread3 testThread3 =new TestThread3();
    
            //创建线程对象,通过线程对象来开启我们的线程,代理
           // Thread thread = new TestThread3(testThread3);
          //  thread.start();
          new Thread(testThread3).start();
            //main线程,主线程
            for (int i=0;i<20;i++){
                System.out.println("我在学习多线程---"+i);
            }
        }
    }
    
    

    简单的龟兔赛跑:

    package Thread;
    
    /**
     * 模拟龟兔赛跑
     */
    public class Race implements Runnable {
        //胜利者
        private static String winner;
    
        @Override
        public void run() {
            for (int i = 0; i <= 100; i++) {
                //判断是否结束
                boolean flag =gameover(i);
    
                //模拟兔子休息
                if (Thread.currentThread().getName()=="兔子" && i%10==0){
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
    
                if (flag){
                    break;
                }
    
                System.out.println(Thread.currentThread().getName() + "-->跑了" + i + "步");
            }
        }
    
        //判断是否完成比赛
        private boolean gameover(int steps){
            //判断是否由胜利者
            if (winner !=null){
                return true;
            }
            {
    
                if (steps >= 100) {
                    winner = Thread.currentThread().getName();
                    System.out.println("Winner is" + winner);
                    return true;
                }
            }
    
            return  false;
        }
    
        public static void main(String[] args) {
            Race race =new Race();
    
            new Thread(race,"兔子").start();
            new Thread(race,"乌龟").start();
        }
    
    }
    
    

    实现Callable接口(了解)

    1. 实现Callable接口,需要返回值类型
    2. 重写call()方法,需要抛出异常
    3. 创建目标对象
    4. 创建执行服务:ExcutorService ser =Excutors.newFixedThreadPool(1);
    5. 提交执行:Future reasult = ser.submit(11);
    6. 获取结果: boolean r1 =result1.get();
    7. 关闭服务: ser.shutdownNow();

    callable好处:
    1.可以定义返回值
    2.可以抛出异常

    静态代理

    package Thread;
    
    /**
     * 静态代理
     */
    public class StaticProxy {
    
        public static void main(String[] args) {
            WeddingCompany weddingCompany = new WeddingCompany(new You());
            weddingCompany.HappyMarry();
        }
    }
    
    interface Marry {
        void HappyMarry();
    
    }
    
    class You implements Marry{
    
        @Override
        public void HappyMarry() {
            System.out.println("结婚了,超开心");
        }
    }
    
    //代理角色
    class WeddingCompany implements Marry{
    
        private Marry target;
    
        public WeddingCompany(Marry target) {
            this.target = target;
        }
    
        @Override
        public void HappyMarry() {
            before();
            this.target.HappyMarry();
            after();
        }
    
        private void after() {
            System.out.println("结婚之后,收尾款");
    
        }
    
        private void before() {
            System.out.println("结婚之前,布置现场");
        }
    }
    
    

    好处:
    1.代理对象可以做很多真是对象做不到的事情
    2.真实对象专注做自己的事情

    Lamda表达式

    • 希腊字母表中排序第十一字母,英语名称为Lambda
    • 避免匿名内部类定义过多
    • 实质属于函数式编程的概念

    函数式接口的定义:
    任何接口,如果只包括唯一一个抽象方法,他就是
    函数式接口

    对于函数式接口,我们可以通过lambda表达式来创建该接口的对象
    
    

    lambda->

    线程的状态

    五大状态:
    1.创建状态
    2.就绪状态
    3.运行状态
    4.阻塞状态
    5.死亡状态

    线程方法:
    setPriority(int newPriority) 更改线程的优先级
    static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠
    void join() 等待该线程终止
    static void yield() 暂停当前正在执行的线程对象,并执行其他线程
    void interrupt() 终端线程,别用这个方式
    boolean isAlive() 测试线程是否处于活动状态

    停止线程:
    不推荐使用jdk提供的stop()、destroy()方法【已经废弃】
    推荐线程自己停止下来
    建议使用一个标志位进行终止变量,当flag=false,则终止线程运行

    public class TestStop implements Runnable{
        private boolean flag =true;
        @Override
        public void run(){
        while(flag){
            System.out.println("run....Thread");
        }
    }
    public void stop(){
        this.flag =false;
    }
    
    }
    
    

    自己写的外部停止线程方法

    package State;
    
    /**
     * 测试stop
     */
    //建议线程正常停止---》利用次数,不建议死循环
        //建议使用标志位---》设置一个标志位
        //不要使用stop或者destroy等过时或者jdk不建议使用的方法
    public class TestState1 implements  Runnable{
        private  boolean flag=true;
    
        @Override
        public void run() {
            int i=0;
            while(flag){
                System.out.println("run....Thread"+i++);
            }
    
        }
    
        public  void stop(){
            this.flag =false;
        }
    
        public static void main(String[] args) {
            TestState1 testState1 =new TestState1();
    
            new Thread(testState1).start();
    
            for (int i=0;i<1000;i++){
                System.out.println("main"+i);
                if (i==900){
                    testState1.stop();
                    System.out.println("该停止了");
                }
            }
        }
    }
    
    

    线程休眠

    • sleep(时间)指定当前线程阻塞的毫秒数
    • 1000毫秒=1s
    • sleep存在异常InterruptedException
    • sleep时间达到后线程进入就绪状态
    • sleep可以模拟网络延迟,倒计时等
    • 每一个对象都有一个锁,sleep不会释放锁

    倒计时

    package State;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * 模拟网络延迟
     */
    public class TestSleep1 {
    
        public static void main(String[] args){
            //打印当前系统时间
            Date startTime =new Date(System.currentTimeMillis());//获取系统当前时间
    
            while(true){
                try {
                    Thread.sleep(1000);
                    System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                    startTime =new Date(System.currentTimeMillis());//更新当前时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            }
        }
    
        public static void tenDown(){
            int num=10;
            while(true){
                try {
                    Thread.sleep(1000);
                    System.out.println(num--);
                    if (num<=0){
                        break;
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    

    线程礼让
    礼让线程,让当前正在执行的线程暂停,但不阻塞
    将线程从运行状态转为就绪状态
    让cpu重新调度,礼让不一定成功!看cpu心情

    package State;
    
    public class TestYield {
        public static void main(String[] args) {
            MyYield myYield = new MyYield();
            new Thread(myYield,"a").start();
            new Thread(myYield,"b").start();
        }
    }
    
    class MyYield implements Runnable{
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName()+"线程开始执行");
            Thread.yield();//礼让
            System.out.println(Thread.currentThread().getName()+"线程停止执行");
        }
    }
    
    

    Join
    join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
    可以想为插队

    package State;
    
    /**
     * 插队
     */
    public class TestJoin implements Runnable {
    
        @Override
        public void run() {
            for (int i=0;i<1000;i++){
                System.out.println("线程VIP来了"+i);
    
            }
    
        }
    
        public static void main(String[] args) throws InterruptedException {
        TestJoin testJoin = new TestJoin();
        Thread thread =new Thread(testJoin);
        thread.start();
    
        //主线程
            for (int i=0;i<500;i++){
                if (i == 200){
                    thread.join();//插队
                }
    
                System.out.println("main"+i);
            }
        }
    
    }
    
    

    线程状态
    Thread.State
    new
    runnable
    blocked
    waitting
    timed waiting
    terminated

    线程优先级
    java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个
    线程的优先级用数字表示,范围由1·10
    Thread.min_priority =1;
    Thread.max_priority=10;
    Thread.norm_priority =5;

    使用一下方式改变或获取优先级
    getPriority() setPriority(int xxx)

    守护线程
    线程分为用户线程和守护线程
    虚拟机必须保护用户线程执行完毕
    虚拟机不用等待守护线程执行完毕
    如,后台记录操作日志,监控内存,垃圾回收等

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

    处理多线程问题的时候,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们需要线程同步,线程同步就是一个等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程在使用

    线程同步
    由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问的时候加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
    一个线程持有锁会导致其他所有需要此锁的线程挂起
    在多线程竞争下,枷锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能问题

    由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我恶魔你只需要针对方法提出一套机制,这套机制就是synchronized关键字,包括两种用法:synchronized方法和synchronized快
    同步方法:public synchronized void method(int args){}

    synchronized方法控制对“对象”的访问,每隔对象对应一把锁,每隔synchronize方法都必须获取调用该方法的对象的锁才可以执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获取这个锁,继续职系那个
        缺陷:若将一个大的方法声明为synchronized将会影响效率
    
    

    同步块:synchronized(obj){}

    obj称为同步监视器
    obj可以是任何对象,但是推荐使用共享资源作为同步监视器
    同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class

        同步监视器的执行过程
            1.第一个线程访问,锁定同步监视器
            2.第二个线程访问,返现同步监视器被锁定,无法访问
            3.第一个线程访问完毕,解锁同步监视器
            4.第二个线程访问,发现同步监视器没有锁,然后锁定并访问
    
    

    死锁
    多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的清形,某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”问题

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

    Lock锁
    从jdk1.5开始,java提供了更强大的线程同步机制–通过显式定义同步锁对象来实现同步。同步锁使用lock对象充当
    java.util.concurrent.locks.lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得lock对象
    ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的ReentrantLock,可以显式加锁、释放锁

    classA {
    private final ReentrantLock lock = new ReenTrantLock();
    public void m(){
    lock.lock();
    try{
    //保证线程安全的代码
    }
    finally{
    lock.unlock();
    //如果同步代码有异常,要将unlock()写入finally语句快
    }
    }
    }

    Synchronized与Lock的对比
    Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,除了作用域自动释放
    Lock只有代码块锁,synchronized有代码块锁和方法锁
    使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性
    优先使用顺序:
    Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

    常用的锁是ReentrantLock

    线程协作
    生产者消费者模式

    synchronized可阻止并发更新同一个共享资源,实现了同步
    synchronized不能用来实现不同线程之间的消息传递(通信)

    java提供了几个方法解决线程之间通信问题
    wait():表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁
    wait(long timeout):指定等待的毫秒数
    notify():唤醒一个处于等待状态的线程
    notifyAll():唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程有限调度

    注意:均是object类方法,都只能在同步方法或者同步代码块中使用,否则就会抛出异常IllegalMonitirStateException

    并发协作模型"生产者/消费者模式"—》管程法
    生产者:负责生产数据的模块
    消费者:负责处理数据的模块
    缓冲区:消费者不能直接使用生产者的数据,它们之间有个缓冲区

    生产者将生产号的数据放入缓冲区,消费者从缓冲区拿出数据

    使用线程池
    背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
    思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完之后放回池中。可以避免频繁创建销毁、实现重复利用。类型生活中的公共交通工具
    好处:
    提高响应速度(减少了创建新线程的时间)
    降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    便于线程管理
    corePoolSize:核心池的大小
    MaximumPoolSize:最大线程数
    KeepAliveTime:线程没有任务时最多保持多长时间后会终止

    使用线程池
    jdk5提供了线程池相关api:ExecutorService和Executors
    ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    void execute(Runnable Command):执行任务/命令,没有返回值,一般用来执行Runnable
    Future submit(Callabletask):执行任务,有返回值,一般又来执行Callable
    void shutdown():关闭连接池

    Executors:工具类
    线程池的工厂类,用于创建并返回不同类型的线程池

    package Thread;
    
    import java.util.concurrent.Executor;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 线程池的测试
     */
    public class TestPool {
        public static void main(String[] args) {
    
            //创建线程池
            ExecutorService service = Executors.newFixedThreadPool(10);
    
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
            service.execute(new MyThread());
    
            //关闭
            service.shutdown();
    
        }
    }
    
    class MyThread implements  Runnable{
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
    

    结尾

    本文到这里就结束了,感谢看到最后的朋友,都看到最后了,点个赞再走啊,如有不对之处还请多多指正。

    相关文章

      网友评论

        本文标题:深度剖析——Java多线程

        本文链接:https://www.haomeiwen.com/subject/ixqyuktx.html