多线程的基本概念

目录

一、进程、线程和纤程

二、多线程的实现方法

三、start和run方法的区别

四、线程的状态

五、sleep、wait、yield、join        

六、多线程的三大特性


一、进程、线程和纤程

        进程:比如我们下载QQ之后电脑上会有一个QQ.exe程序,双击打开它登录进去,这个时候就是一个进程。即操作系统分配内存资源的最小单位
        线程:一个程序里面不同的执行路径叫做一个线行。即CPU最小执行单元(内核空间)。其中线程与线程之间是不可见的。
        纤程(协程):虚拟pc管理的属于用户空间的线程即轻量级线程。后期应该会单独总结。      

二、多线程的实现方法

        其实多线程的实现方法也可以看做启动线程的几种方式:

1.继承Thread类,重写run方法。

2.实现runnable接口,重新run方法。

        其中runnable还存在3种变种方式(不是很常用):

        2.1.实现callable接口,重新call方法,存在返回值。

        2.2.实现futrue接口,存在cacle方法(试图取消一个线程的执行,可能失败)

        2.3.继承futureTask(future的实现):高并发情况下可以确保任务只执行一次

3. 通过线程池实现多线程

        最主要的方式就是Threah和Runnabe这俩种方式,在实际工作中Runnabel其实比Thread更加常用,下面对Threah和Runnabel进行比较:

1、java中“单继承,多实现”的特性,Runnable比Thread更加灵活。

2、Runnable更加符合面向对象,将线程单独进行了对象的封装。

3、Runnable降低了线程对象和线程任务的耦合性。

4、其实Runnable生成的是任务,无法直接使用start方法进行启动,必须new Thread(任务).start()。

public class HowToCreateThread {
    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("Hello MyThread!");
        }
    }

    static class MyRun implements Runnable {
        @Override
        public void run() {
            System.out.println("Hello MyRun!");
        }
    }

     /**
     * @Author zhangchunming
     * @Description //TODO
     *  启动线程的几种方式:
     *      1.Thread 2.Runnable 3.Executors线程池 4.lambda表达式也可以算一种
     **/

    public static void main(String[] args) {
        new MyThread().start();
        new Thread(new MyRun()).start();
        new Thread(()->{
            System.out.println("Hello Lambda!");
        }).start();
        
        //设置线程池的数量
        private static int threadPoolNum = 8;
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for(int i = 0; i < threadPoolNum; i ++){
            //创建线程对象
            MyRun thread = new MyRun ();

            //Thread.sleep(1000);//使线程休眠1秒
            //执行线程
            executorService.execute(thread);
        }
    }

}

三、start和run方法的区别

        用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的 start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。

        run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。

        总结:其实就相当于start方法创建了子线程,run方法还在当前线程。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。

        注意:不可以反复调用同一个线程的start()方法。调用第一次之后,threadStatus的值改变(不等于0),此时在调用会抛错illegalThreadStateException。

package com.zcm;

import java.util.concurrent.TimeUnit;

public class WhatIsThread {
    private static class T1 extends Thread {
        @Override
        public void run() {
           for(int i=0; i<10; i++) {
               try {
                   //设置睡眠时间
                   TimeUnit.MICROSECONDS.sleep(1);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               System.out.println("T1");
           }
        }
    }

    //使用俩种方法执行结果不相同
    public static void main(String[] args) {
        //new T1().run();
        new T1().start();
        for(int i=0; i<10; i++) {
            try {
                TimeUnit.MICROSECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main");
        }

    }
}

四、线程的状态

        线程的状态主要分为:新建(new),就绪(runable:调用start方法),运行(running:cpu开始调度),阻塞(blocked:分三种),消亡(Dead)

        其中阻塞状态有三种:

1.同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

2.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

        下图为线程各种状态之间的转换关系图:

五、sleep、wait、yield、join        

        sleep 方法是属于 Thread 类中的,sleep 过程中线程不会释放锁,只会阻塞线程,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态,可中断,sleep 给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会。

        wait 方法是属于 Object 类中的,wait 过程中线程会释放对象锁,只有当其他线程调用 notify 才能唤醒此线程。wait 使用时必须先获取对象锁,即必须在 synchronized 修饰的代码块中使用,那么相应的 notify 方法同样必须在 synchronized 修饰的代码块中使用,如果没有在synchronized 修饰的代码块中使用时运行时会抛出IllegalMonitorStateException的异常。

        yield(没有使用过,了解就行)和 sleep 一样都是 Thread 类的方法,都是暂停当前正在执行的线程对象,不会释放资源锁,和 sleep 不同的是 yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。还有一点和 sleep 不同的是 yield 方法只能使同优先级或更高优先级的线程有执行的机会 


        调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景。例如:主线程创建并启动了子线程,如果子线程中药进行大量耗时运算计算某个数据值,而主线程要取得这个数据值才能运行,这时就要用到 join 方法了。

package com.zcm;

public class Sleep_Yield_Join {
    public static void main(String[] args) {
//        testSleep();
//        testYield();
        testJoin();
    }

    static void testSleep() {
        new Thread(()->{
            for(int i=0; i<100; i++) {
                System.out.println("A" + i);
                try {
                    Thread.sleep(500);
                    //TimeUnit.Milliseconds.sleep(500)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    static void testYield() {
        new Thread(()->{
            for(int i=0; i<100; i++) {
                System.out.println("A" + i);
                if(i%10 == 0) Thread.yield();


            }
        }).start();

        new Thread(()->{
            for(int i=0; i<100; i++) {
                System.out.println("------------B" + i);
                if(i%10 == 0) Thread.yield();
            }
        }).start();
    }

    static void testJoin() {
        Thread t1 = new Thread(()->{
            for(int i=0; i<100; i++) {
                System.out.println("A" + i);
                try {
                    Thread.sleep(500);
                    //TimeUnit.Milliseconds.sleep(500)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(()->{

            try {
                t1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            for(int i=0; i<100; i++) {
                System.out.println("A" + i);
                try {
                    Thread.sleep(500);
                    //TimeUnit.Milliseconds.sleep(500)
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t1.start();
        t2.start();
    }
}

六、多线程的三大特性

        有序性、可见性和原子性。

        可见性:一个线程修改的共享变量,其他线程可以立即看到,对于串行程序,不需要考虑可见性问题,前一个线程修改的,后一个线程一定可以感知到;在多线程中如果没有正确的同步,则可能出现问题。在cpu中存在三级缓存区(l1,l2,l3),在进行读取时,会先读取读取缓存区,缓存区没有才会去内存中查找,有缓存一致性原则(mesl)实现缓存区和内存值的一致性。也可以使用volatile实现,底层触发lock. 

        有序性:从观察到的结果推测,代码执行的顺序和代码组织的顺序不同,线程本身就存在乱序的可能性(线程的重排序),因为CPU读取寄存器的速度是读取内存速度的100倍,在等待内存操作的时候,如果下一个是寄存器取数,可能就会乱序,但在出现乱序的情况:不影响单线程的数据一致性原则。但是在多线程可能出现问题。 

        原子性:多个操作作为一个整体,不可分割中断。


 

        

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

!春明!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值