zoukankan      html  css  js  c++  java
  • 多线程学习(一)

    查看多线程教学视频,请点击 《狂神说java》: https://www.bilibili.com/video/BV1V4411p7EF?p=1 记得投币三连呀~~

    线程创建

    三种创建线程的方式

    继承Thread


    .start() 方法启动线程执行的是 run() 里的线程体!

    测试一:

    // 创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
    //总结:注意,线程开启不一定立即执行,由cpu调度执行
    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){
            //main线程,主线程
    
            //创建一个线程对象
            TestThread1 testThread1 = new TestThread1();
            //调用start()方法开启线程
            testThread1.start();
    
            for(int i= 0 ;i<200;i++){
                System.out.println("我在学习多线程----"+i);
           }
        }
    }
    
    

    输出结果:

    可以发现 start() 开启的线程和主线程是交替执行的!!!

    而直接调用 run() 方法,会发现先执行run()方法,再执行主线程:

    public static void main(String[] args){
            //main线程,主线程
    
            //创建一个线程对象
            TestThread1 testThread1 = new TestThread1();
    
            //调用run()方法开启线程
            testThread1.run();
    
            for(int i= 0 ;i<200;i++){
                System.out.println("我在学习多线程----"+i);
            }
        }
    

    输出结果

    以上对比直接调用 run() 和调用 start()可以得出以下结论:

    为了更好地验证上述结论,我们进行测试二:

    测试二:

    //练习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://pics7.baidu.com/feed/a50f4bfbfbedab64cddfc490f31fc9c578311ee4.jpeg?token=d6cc78140999ca5390cd8002a754d9a4","1.jpg");
            TestThread2 t2 = new TestThread2("https://images2015.cnblogs.com/blog/1168144/201706/1168144-20170628161248586-632368241.png","2.jpg");
            TestThread2 t3 = new TestThread2("https://images2015.cnblogs.com/blog/1168144/201706/1168144-20170628162624243-370438535.png","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方法出现问题");
            }
    
        }
    
    

    下载结果为:(发现并不是按1,2,3的顺序下载)

    从而更好的说明,start() 方法开启的子线程和主线程是并行交替执行的!

    实现Runnable

    测试一:

    //创建线程方式2:实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类Thread中,调用start()。
    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 Thread(testThread3);
            thread.start();
    
            for(int i= 0 ;i<200;i++){
                System.out.println("我在学习多线程----"+i);
            }
        }
    
    }
    

    输出结果:

    测试二:让TestThread2实现Runnable接口下载图片,而非继承Thread

    //实现Runnable接口下载图片
    public class TestThread2 implements Runnable {
    
        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://pics7.baidu.com/feed/a50f4bfbfbedab64cddfc490f31fc9c578311ee4.jpeg?token=d6cc78140999ca5390cd8002a754d9a4","1.jpg");
            TestThread2 t2 = new TestThread2("https://images2015.cnblogs.com/blog/1168144/201706/1168144-20170628161248586-632368241.png","2.jpg");
            TestThread2 t3 = new TestThread2("https://images2015.cnblogs.com/blog/1168144/201706/1168144-20170628162624243-370438535.png","3.jpg");
    
            new Thread(t1).start();
            new Thread(t2).start();
            new Thread(t3).start();
    
        }
    
    }
    

    下载结果:(交替下载,而非顺序下载)

    小结

    初识并发问题

    //多个线程同时操作同一个对象
    //买车票的例子
    
    //发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
    public class TestThread4 implements Runnable {
    
        //票数
        private int ticketNums = 10;
    
        @Override
        public void run(){
            while(true){
                if (ticketNums<=0){
                    break;
                }
                //模拟延时
                try{
                    Thread.sleep(200);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
    
                System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");
            }
        }
    
        public static void main(String[] args) {
            TestThread4 ticket = new TestThread4();
            new Thread(ticket,"小明").start();
            new Thread(ticket,"老师").start();
            new Thread(ticket,"黄牛").start();
        }
    }
    
    

    输出结果:(发现线程不安全)

    案例:龟兔赛跑—Race

    //模拟龟兔赛跑
    public class Race implements Runnable {
    
        //胜利者
        private static String winner;
    
        @Override
        public void run(){
            for (int i = 1; i<=100 ; i++){
    
                //模拟兔子休息
                if(Thread.currentThread().getName().equals("兔子") && i%10==0){
                    try{
                        Thread.sleep(10);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
    
                //判断比赛是否结束
                boolean flag = gameOver(i);
                //如果比赛结束了,就停止程序
                if (flag){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"---->跑了"+i+"步");
            }
        }
    
        //判断是否完成比赛
        private boolean gameOver(int steps){
            //判断是否有胜利者
            if (winner!=null){ //已经有胜利者
                return true;
            }else{
                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接口(了解即可)

    //线程创建方式三:实现callable接口
    /*
    * callable的好处
    * 1.可以定义返回值
    * 2.可以抛出异常
    * */
    public class TestCallable implements Callable<Boolean> {
    
    
        private String url;  //网络图片地址
        private String name;  //保存的文件名
    
        public TestCallable(String url,String name){
            this.url=url;
            this.name=name;
        }
    
        //下载图片的线程执行体
        @Override
        public Boolean call(){
            WebDownloader webDownloader = new WebDownloader();
            webDownloader.downloader(url,name);
            System.out.println("下载的文件名为:"+name);
            return true;
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            TestCallable t1 = new TestCallable("https://pics7.baidu.com/feed/a50f4bfbfbedab64cddfc490f31fc9c578311ee4.jpeg?token=d6cc78140999ca5390cd8002a754d9a4","1.jpg");
            TestCallable t2 = new TestCallable("https://images2015.cnblogs.com/blog/1168144/201706/1168144-20170628161248586-632368241.png","2.jpg");
            TestCallable t3 = new TestCallable("https://images2015.cnblogs.com/blog/1168144/201706/1168144-20170628162624243-370438535.png","3.jpg");
    
            //创建执行服务
            ExecutorService ser = Executors.newFixedThreadPool(3);
            //提交执行
            Future<Boolean> r1 = ser.submit(t1);
            Future<Boolean> r2 = ser.submit(t2);
            Future<Boolean> r3 = ser.submit(t3);
            //获取结果
            boolean rs1 = r1.get();
            boolean rs2 = r2.get();
            boolean rs3 = r3.get();
    
            System.out.println(rs1);
            System.out.println(rs2);
            System.out.println(rs3);
            //关闭服务
            ser.shutdownNow();
    
        }
    
    }
    
    //下载器
    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方法出现问题");
            }
    
        }
    }
    

    下载结果:

    静态代理

    //静态代理模式总结:
        //真实对象和代理对象都要实现同一个接口
        //代理对象要代理真实角色
    
    //好处:
        //代理对象可以做很多真实对象做不了的事情
        //真实对象专注做自己的事情
    
    public class StaticProxy {
    
        public static void main(String[] args) {
    
            You you = new You();//你要结婚
            WeddingCompany weddingCompany = new WeddingCompany(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("结婚前,布置现场");
        }
    

    和上文实现Runnable接口创建线程作对比:

    ​ Marry接口——>Runnable接口

    ​ 实现Marry接口的You类——>实现Runnable接口的TestThread3类

    ​ 实现Marry接口的WeddingCompany类——>实现Runnable接口的Thread类

    Lambda表达式



    /**
     * 推导lambda表达式: 
     *      1.创建外部类——>2.创建静态内部类——>3.创建局部内部类——>4.创建匿名内部类——>5.使用lambda表达式
     */
    public class TestLambda {
    
        //3.静态内部类
        static class Like2 implements ILike{
            @Override
            public void lambda(){
                System.out.println("I like lambda2");
            }
        }
    
        public static void main(String[] args) {
    		
            //调用外部类创建对象
            ILike like1 = new Like();
            like1.lambda();
    		
            //调用静态内部类创建对象
            ILike like2 = new Like2();
            like2.lambda();
    
    
            //4.局部内部类
            class Like3 implements ILike{
                @Override
                public void lambda(){
                    System.out.println("I like lambda3");
                }
            }
            ILike like3 = new Like3();
            like3.lambda();
    
            //5.匿名内部类,没有类名字,必须借助接口或者父类
            ILike like4 = new ILike() {
                @Override
                public void lambda() {
                    System.out.println("I like lambda4");
                }
            };
            like4.lambda();
    
            //6.用lambda简化
            ILike like5 = ()->{
                System.out.println("I like lambda5");
            };
            like5.lambda();
        }
    }
    
    //1.定义一个函数接口
    interface ILike{
        void lambda();
    }
    
    //2.实现外部类
    class Like implements ILike{
        @Override
        public void lambda(){
            System.out.println("I like lambda");
        }
    }
    

    在函数式接口的抽象函数有参数的情况下,使用lambda表达式:

    public class TestLambda2 {
    
        public static void main(String[] args) {
            
    		//使用lambda表达式,不做任何化简
            ILove love1 = (int a)->{
                    System.out.println("I love you-->"+a);
                };
            love1.love(1);
    
            //1.简化参数类型
            ILove love2 = (a)->{
                System.out.println("I love you-->"+a);
            };
            love2.love(2);
    
            //2.简化括号和参数类型
            ILove love3 = a->{
                System.out.println("I love you-->"+a);
            };
            love2.love(3);
    
            //3.简化括号和参数类型和花括号
            ILove love4 = a->System.out.println("I love you-->"+a);
            love4.love(4);
    
            //总结:
                //lambda表达式只能在方法体有一行代码的情况下才能简化成为一行(即去掉花括号),如果有多行,那么就用代码块包裹(即使用花括号)
                //lambda的使用前提是接口为函数式接口(即接口里只有一个函数方法)
                //多个参数也可以去掉参数类型(参数类型不同也可以),要去掉就都去掉,但必须加括号
                //当使用lambda有返回值时,方法体即使只有一行代码,也要用花括号包裹!
        }
    }
    
    //函数式接口
    interface ILove{
        void love(int a);
    }
    

    线程状态

    线程的五大状态

    停止线程

    使用我们自定义的stop方法,通过改变标志位让线程停止:

    //测试stop
    //1.建议线程正常停止————>利用次数,不建议死循环
    //2.建议使用标志位————>设置一个标志位
    //3.不要使用stop或者destroy等过时或者JDK不建议使用的方法
    public class TestStop implements Runnable{
    
        //1.设置一个标识位
        private boolean flag = true;
    
        @Override
        public void run(){
            int i = 0;
            while(flag){
                System.out.println("run ..... Thread"+i++);
            }
        }
    
        //2.设置一个公开的方法停止线程,转换标志位
        public void stop(){
            this.flag = false;
        }
    
        public static void main(String[] args) {
    
            TestStop testStop = new TestStop();
            new Thread(testStop).start();
    
            for (int i=0;i<1000;i++){
                System.out.println("main"+i);
                if (i==900){
                    //调用 stop方法切换标志位,让线程停止
                    testStop.stop();
                    System.out.println("线程该停止了!!!");
                }
            }
        }
    }
    

    线程在执行第1060次时停止:

    线程休眠

    import com.ztx.dem01.TestThread4;
    
    //模拟网络延时:放大问题的发生行
    public class TestSleep implements Runnable{
    
        //票数
        private int ticketNums = 10;
    
        @Override
        public void run(){
            while(true){
                if (ticketNums<=0){
                    break;
                }
                //模拟延时
                try{
                    Thread.sleep(200);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
    
                System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");
            }
        }
    
        public static void main(String[] args) {
            TestThread4 ticket = new TestThread4();
            new Thread(ticket,"小明").start();
            new Thread(ticket,"老师").start();
            new Thread(ticket,"黄牛").start();
        }
    }
    

    //模拟倒计时
    public class TestSleep2 {
    
        public static void main(String[] args) {
    
            try {
                tenDown();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    
        //模拟倒计时
        public static void tenDown() throws InterruptedException{
            int num = 10;
            while(true){
                Thread.sleep(1000);
                System.out.println(num--);
                if (num<=0){
                    break;
                }
            }
        }
    }
    
    

    //模拟倒计时
    public class TestSleep2 {
    
        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();
                }
            }
        }
    }
    

    线程礼让

    //测试礼让线程
    //礼让不一定成功,看cpu心情
    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()+"线程停止执行");
        }
    }
    

    理想情况下,当把 Thread.yield(); 注释掉,输出情况应为:(实际上,不可能这么理想!)

    加上礼让代码后,输出情况可能就不会很规整:(让执行的线程暂停,但不阻塞)

    join

    //测试join方法
    //想象为插队
    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);
            }
        }
    }
    

    输出结果:(在“main199”之前,线程是交替执行的,main199之后,线程VIP开始插队直到执行结束,main在开始继续执行!!!)

    线程状态观测

    //观测线程的状态
    public class TestState {
    
        public static void main(String[] args) throws InterruptedException {
    
            Thread thread = new Thread(()->{
                for (int i=0;i<10;i++){
                    try{
                        Thread.sleep(1000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
                System.out.println("///////");
            });
    
            //观察状态
            Thread.State state = thread.getState();
            System.out.println(state);  //状态为:new,即新生的
    
            //观察启动后的状态
            thread.start();//启动线程
            state = thread.getState();
            System.out.println(state);//状态应为:run
    
            while(state!=Thread.State.TERMINATED){//只要线程不终止,就一直输出状态
                Thread.sleep(1000);
                state = thread.getState(); //更新线程状态
                System.out.println(state);
            }
    
            thread.start();
        }
    }
    

    线程优先级

    测试线程优先级:

    //测试线程的优先级
    public class TestPriority {
    
        public static void main(String[] args) {
            //主线程默认优先级
            System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
    
            MyPriority myPriority = new MyPriority();
    
            Thread t1 = new Thread(myPriority);
            Thread t2 = new Thread(myPriority);
            Thread t3 = new Thread(myPriority);
            Thread t4 = new Thread(myPriority);
            Thread t5 = new Thread(myPriority);
            Thread t6 = new Thread(myPriority);
    
            //先设置优先级,再启动
            t1.start();
    
            t2.setPriority(1);
            t2.start();
    
            t3.setPriority(4);
            t3.start();
    
            t4.setPriority(Thread.MAX_PRIORITY);
            t4.start();
    
    
            t5.setPriority(8);
            t5.start();
    
            t6.setPriority(7);
            t6.start();
        }
    }
    
    class MyPriority implements Runnable{
        @Override
        public void run(){
            System.out.println(Thread.currentThread().getName()+"--->"+Thread.currentThread().getPriority());
        }
    }
    

    运行结果:

    守护(daemon)线程

    测试守护线程:

    //测试守护线程
    //上帝守护你
    public class TestDaemon {
    
        public static void main(String[] args) {
            God god = new God();
            You you = new You();
    
            Thread thread = new Thread(god);
            thread.setDaemon(true); //默认为false表示用户线程,正常的线程都是用户线程
    
            thread.start();//上帝守护线程开启
    
            new Thread(you).start();//you 用户线程开启
        }
    }
    
    //上帝
    class God implements Runnable{
        @Override
        public void run(){
            //这里设置为一直循环,但虚拟机不会等到该线程执行结束(该线程也不会自动结束),就会将其停止,它只要确保用户线程执行完毕即可
            while(true){    
                System.out.println("s上帝保佑着你!");
            }
        }
    }
    
    //你
    class You implements Runnable{
        @Override
        public void run(){
            for (int i=0;i<36500;i++){
                System.out.println("你一生都开心的活着");
            }
            System.out.println("======goodbye! world!=======");
        }
    }
    

    输出结果:

    这里的用户线程执行完毕后,守护线程依旧执行是因为虚拟机完毕需要时间,在这期间守护线程还可以一直运行!

  • 相关阅读:
    To the Virgins, to Make Much of Time
    瓦尔登湖
    贪心算法
    R语言实战 —— 常见问题解决方法
    R语言实战(四)—— 基本数据管理
    R语言实战(三)——模拟随机游走数据
    Vim——回顾整理
    Clion下载安装使用教程(Win+MinGW)
    【ACM】孪生素数问题
    【ACM】一种排序
  • 原文地址:https://www.cnblogs.com/churujianghudezai/p/13185847.html
Copyright © 2011-2022 走看看