• Java 多线程 01


    多线程· Runnable 和 Thread

    多线程的引入

    * A:什么是线程
      * 线程是程序执行的一条路径,一个进程中可以包含多条线程
      * 多线程并发执行可以提高程序的效率,可以同时完成多项工作

    * B:多线程的应用场景
      * 红蜘蛛同时共享屏幕给多个电脑
      * 迅雷开启多条线程一起下载
      * QQ同时和多个人一起视频
      * 服务器同时处理多个客户端请求

    多线程并行和并发的区别

    * 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也同时进行(需要多核CPU)

    * 并发是指两个任务都请求运行,而处理器只能接收一个任务,于是就安排轮流执行这两个任务,由于时间间隔较短,因此用户感觉两个任务同时运行

    * 比如:我和两个网友聊天,左手操作一个电脑,右手操作另一个电脑,就叫并行
      * 如果用一台电脑先给一个人发消息,再用这台电脑给另一个人发消息,这就叫并发

    Java程序运行的原理和JVM的启动

    * A:Java程序运行原理
      * Java命令会启动Java虚拟机,启动JVM就等于启动了一个应用程序,也就是启动了一个进程,
      * 该进程会自动启动一个“主线程”,然后主线程区调用某个类的main方法

    * B:JVM的启动
      * JVM的启动至少启动了垃圾线程和主线程,所以是多线程的

    package com.heima.thread;
    
    public class Demo1_Thread {
        // 证明JVM是多线程的
        public static void main(String[] args) {
            for (int i = 0; i < 390000; i++) {
                new Demo();
            }
            for (int i = 0; i < 10000; i++) {
                System.out.println("我是主线程的执行代码");
            }
        }
    }
    
    class Demo { // 垃圾清理线程
        @Override
        public void finalize() throws Throwable {
            System.out.println("垃圾被清扫了");
        }
    }
    thread

    Thread 实现多线程

    * A:继承Thread
      * 定义类继承Thread
      * 重写 run()方法
      * 把新线程要做的事写在 run()方法中
      * 创建线程对象
      * 开启新线程,内部自动会执行 run()方法

    package com.heima.thread;
    
    public class Demo2_Thread {
    
        public static void main(String[] args) {
            MyThread mt = new MyThread(); // 创建线程的子类对象
            // mt.run(); // 未开启线程
            mt.start(); // 使该线程开始执行,开启线程需要时间,类似赛道上的发令枪
            
            for (int i = 0; i < 1000; i++) { // 调用主线程
                System.out.println("bb"); // 结果:bb 和 aaaaaa 成块状间隔打印
            }
        }
    }
    
    class MyThread extends Thread { // 继承Thread
        public void run() { // 重写run方法
            for (int i = 0; i < 1000; i++) { // 将想要执行的代码写在run方法中
                System.out.println("aaaaaaaaaa");
            }
        }
    }
    thread

    Runnable 实现多线程

    * A:实现 Runnable
      * 定义类实现 Runnable接口
      * 实现 run()方法
      * 把新线程要做的事写在 run()方法中
      * 创建Thread对象,传入Runnable的子类对象
      * 调用 start()开启新线程,内部会自动调用 Runnable的 run()方法

    package com.heima.thread;
    
    public class Demo3_Thread {
    
        public static void main(String[] args) {
            MyRunnable mr = new MyRunnable(); // 创建Runnable子类对象
    
            // Thread t = new Thread(mr); // 将Runnable的子类当作参数传递给Thread
            // t.start(); // 开启线程
            new Thread(mr).start(); // 使用匿名类开启线程
    
            for (int i = 0; i < 1000; i++) {
                System.out.println("bb"); // 穿插打印 aaaaa 和 bb
            }
    
        }
    }
    
    class MyRunnable implements Runnable { // 定义一个类实现Runnable接口
        @Override
        public void run() { // 重写run方法
            for (int i = 0; i < 1000; i++) { // 将想要执行的代码写在run方法中
                System.out.println("aaaaaaaaaaaa");
            }
        }
    
    }
    Runnable

    两种方式的区别

    * 查看源码的区别:
      * a:继承 Thread,由于子类重写了 Thread类的 run()方法,当调用 start()时,直接找子类的 run()方法
      * b:实现 Runnable,构造函数中传入了 Runnable的引用,成员变量记住了它,
        start()调用 run()方法时内部判断成员变量 Runnable的引用是否为空,不为空编译时看的是 Runnable的 run(),运行时运行的是子类的 run()方法

    * 继承 Thread
      * 好处是:可以直接使用 Thread类中的方法,代码简单
      * 弊端是:如果已经有了父类,就不能用这种方法

    * 实现 Runnable接口
      * 好处是:即使自己定义的线程类有了父类也没关系,而且接口是多实现的
      * 弊端是:不能直接使用 Thread中的方法,需要先获取到线程对象后,才能得到 Thread的方法,代码复杂

     

    匿名内部类实现多线程的两种方式

    * 继承 Thread类

    * 实现 Runnable接口

    package com.heima.thread;
    
    public class Demo4_Thread {
        // 使用匿名类继承Thread,实现Runnable,来开启多线程
        public static void main(String[] args) {
            new Thread() { // 继承Thread类
                public void run() { // 重写run方法
                    for (int i = 0; i < 1000; i++) { // 将要执行的代码写在run方法中
                        System.out.println("aaaaaaa");
                    }
                }
            }.start(); // 开启线程
    
            new Thread(new Runnable() { // 实现 Runnable接口
                @Override
                public void run() { // 重写run方法
                    for (int i = 0; i < 1000; i++) { // 将要执行的代码写在run方法中
                        System.out.println("bb");
                    }
                }
            }).start(); // 开启线程
        }
    }
    thread

    Thread 获取线程名和设置线程名

    * A:获取名字
      * 通过 getName()方法获取线程对象的名字

    * B:设置名字
      * 通过构造函数传入 String类型的名字
      * 通过 setName(String)方法可以设置线程对象的名字

    package com.heima.threadmethod;
    
    public class Demo1_Name {
    
        public static void main(String[] args) {
            // demo1();
            // demo2();
        }
        
        public static void demo2() {
            new Thread() {
                public void run() {
                    this.setName("zwb"); // 使用 setName()方法设置线程名,因为是在类的内部,所以用this关键字表示对此类命名
                    System.out.println(this.getName()+ " aaaaa");
                }
            }.start();
            
            Thread t1 = new Thread() {
                public void run() {
                    System.out.println(this.getName()+ " bb");
                }
            };
            t1.setName("cly"); // 使用 setName()对线程命名,因为在类外,所以通过对象名调用
            t1.start();
        }
        
        public static void demo1() {
            new Thread("zwb") { // 通过构造方法给线程命名
                public void run() {
                    System.out.println(this.getName() + " aaaaaaa"); // 调用 getName()方法获取线程名
                }
            }.start();
    
            new Thread("cly") { // 通过构造方法给线程命名
                public void run() {
                    System.out.println(this.getName() + " bb"); // 调用 getName()方法获取线程名
                }
            }.start();
        }
    }
    thread

    获取当前线程的对象

    * Thread.currentThred(),主线程也可以获取

    package com.heima.threadmethod;
    
    public class Demo2_CurrentThread {
    
        public static void main(String[] args) {
            new Thread() {
                public void run() {
                    System.out.println(getName() + " aaaaa");
                }
            }.start();
            
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " bb"); // 获取当前正在执行的线程
                }
            }).start();
            
            Thread.currentThread().setName("zwb"); // 设置当前正在执行的线程的名字,即主线程
            System.out.println(Thread.currentThread().getName()); // 获取主线程的名字
        }
    }
    currentThread

    休眠线程

    * Thread.sleep(毫秒值,纳秒值),控制当前线程休眠若干毫秒
    * 1秒 = 1 000毫秒 = 1 000 000 微秒 = 1 000 000 000 纳秒

    package com.heima.threadmethod;
    
    public class Demo3_Sleep {
    
        public static void main(String[] args) throws InterruptedException {
            // demo1();
            // demo2();
        }
    
        public static void demo2() {
            new Thread() {
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        try {
                            Thread.sleep(30); // 线程睡30毫秒
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(getName() + " aaaaa");
                    }
                }
            }.start(); // 开启线程1
            
            new Thread() {
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        try {
                            Thread.sleep(30); // 线程睡30毫秒
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(getName() + " bb");
                    }
                }
            }.start(); // 开启线程2
        }
    
        public static void demo1() {
            for(int i = 20; i >= 0; i--) {
                try {
                    Thread.sleep(1000); // 线程睡1秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("倒计时 第" + i + "秒");
            }
        }
    }
    sleep

    守护线程

    * setDaemo(),设置一个线程为守护线程,该线程不会单独执行,当其他非守护线程都执行结束后,自动退出

    package com.heima.threadmethod;
    
    public class Demo4_Daemon {
        // 非守护线程一旦结束,守护线程也会随之结束
        public static void main(String[] args) {
            Thread t1 = new Thread("cly") { // 创建线程1
                public void run() {
                    for (int i = 0; i < 2; i++) {
                        System.out.println(getName() + " aaaa");
                    }
                }
            };
            
            Thread t2 = new Thread("zwb") { // 创建线程2
                public void run() {
                    for (int i = 0; i < 50; i++) {
                        System.out.println(getName() + " bb");
                    }
                }
            };
            
            t2.setDaemon(true); // 当传入true就是设置为守护线程
            t1.start(); // 开启线程
            t2.start();
        }
    }
    Daemon

    加入线程

    * join(),当前线程暂停,等到指定的线程执行结束后,当前线程再继续

    * join(int),可以等待指定的毫秒之后继续

    package com.heima.threadmethod;
    
    public class Demo5_Join {
    
        public static void main(String[] args) {
            Thread t1 = new Thread() {
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        System.out.println(getName() + " aaaaaaaa");
                    }
                }
            }; // 创建线程1
            
            Thread t2 = new Thread() {
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        if (i == 2) { 
                            /*try {
                                t1.join();// t2执行2次之后暂停,等到t1全部执行完之后再让t2执行完剩下的所有
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }*/
                            try {
                                t1.join(20); // t2执行2次之后暂停,让t1单独先执行20毫秒,之后两条线程再交替执行
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println(getName() + " bb");
                    }
                }
            }; // 创建线程2
            
            t1.start(); // 开启线程
            t2.start();
        }
    }
    join

    礼让线程

    * yield 让出CPU

    package com.heima.threadmethod;
    
    public class Demo6_Yield {
    
        public static void main(String[] args) {
            new MyThread().start(); // 开启线程1
            new MyThread().start(); // 开启线程2
        }
    }
    
    class MyThread extends Thread {
        public void run() {
            for (int i = 0; i < 1000; i++) {
                if (i % 10 == 0) { // 判断是否满足条件
                    Thread.yield(); // yield让出CPU,但真正运行时可能不会让
                }
                System.out.println(getName() + " " + i);
            }
        }
    }
    yield

    设置线程优先级

    * setPriority(),设置线程的优先级

    package com.heima.threadmethod;
    
    public class Demo7_Priority {
        
        public static void main(String[] args) {
            Thread t1 = new Thread() {
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        System.out.println(getName() + " aaaaaaaa");
                    }
                }
            }; // 创建线程1
            
            Thread t2 = new Thread() {
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        System.out.println(getName() + " bb");
                    }
                }
            }; // 创建线程2
            
            // t1.setPriority(10); // 设置最大优先级,优先级高的相对先执行
            // t2.setPriority(1); // 设置最小优先级
            
            t1.setPriority(Thread.MIN_PRIORITY); // 设置最小的线程优先级,1
            t2.setPriority(Thread.MAX_PRIORITY); // 设置最大的线程优先级,10
            
            t1.start(); // 开启线程
            t2.start();
            
        }
    }
    Priority

    同步代码块

    * A:什么情况下需要同步
      * 当多线程并发,有多段代码同时执行时,我们希望某一段代码执行的过程中CPU不要切换到其他线程工作,这时就需要同步
      * 如果两段代码是同步的,那么同一时间只能执行一段,在一段代码没执行结束之前,不会执行另外一段代码

    * B:同步代码块
      * 使用 synchronized关键字加上一个锁对象来定义一段代码,这就叫同步代码块
      * 多个同步代码块如果使用相同的锁对象,那么他们就是同步的

    package com.heima.syn;
    
    public class Demo1_Synchronized {
    
        public static void main(String[] args) {
            final Printer p = new Printer();
    
            new Thread() {
                public void run() {
                    int i = 0;
                    while (i < 100) {
                        p.print1();
                        i++;
                    }
                }
            }.start(); // 开启线程1
    
            new Thread() {
                public void run() {
                    int i = 0;
                    while (i < 100) {
                        p.print2();
                        i++;
                    }
                }
            }.start(); // 开启线程2
        }
    }
    
    class Printer {
        Demo d = new Demo();
    
        public void print1() {
            synchronized (d) { // 同步代码块的锁机制,锁对象可以是任意的对象,但不能是匿名对象
                System.out.print("z");
                System.out.print("w");
                System.out.print("b");
                System.out.println();
            }
        }
    
        public void print2() {
            // synchronized (new Demo()) { // 不能是匿名对象
            synchronized (d) {
                System.out.print("c");
                System.out.print("l");
                System.out.print("y");
                System.out.println();
            }
        }
    }
    
    class Demo {
    }
    synchronized

    同步方法

    * 使用 synchronized关键字修饰一个方法,该方法中的所有代码都是同步的

    package com.heima.syn;
    
    
    public class Demo2_Synchronized {
    
        public static void main(String[] args) {
            Print2 p = new Print2();
            
            Thread t1 = new Thread() {
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        p.print1();
                    }
                }
            }; // 创建线程1
            
            Thread t2 = new Thread() {
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        p.print2();
                    }
                }
            }; // 创建线程2
            
            t1.start(); // 开启线程
            t2.start();
        }
    }
    
    class Print1 {
        // 非静态的同步方法的锁对象是 this
        public synchronized void print1() { // 同步方法只需要再方法上加上synchronized即可
            System.out.print("z");
            System.out.print("w");
            System.out.print("b");
            System.out.println();
        }
    
        public void print2() {
            synchronized (this) {
                System.out.print("c");
                System.out.print("l");
                System.out.print("y");
                System.out.println();            
            }
        }
    }
    
    class Print2 {
        // 静态的同步方法的锁对象是  字节码对象  也就是 Print2.class 或  getClass()
        public static synchronized void print1() { 
            System.out.print("z");
            System.out.print("w");
            System.out.print("b");
            System.out.println();
        }
    
        public void print2() {
            synchronized (getClass()) {
                System.out.print("c");
                System.out.print("l");
                System.out.print("y");
                System.out.println();            
            }
        }
    }
    synchronized

    线程安全问题

    * 多线程并发操作同一数据时,就有可能出现线程安全问题
    * 使用绒布技术可以解决这种问题,把操作数据的代码进行同步,不要多个线程一起操作

    * 举例:铁路售票

    package com.heima.syn;
    
    public class Demo3_Ticket {
    
        public static void main(String[] args) {
            new Ticket("1").start(); // 开启线程
            new Ticket("2").start();
            new Ticket("3").start();
            new Ticket("4").start();
        }
    }
    
    class Ticket extends Thread {
        public Ticket() {
            super();
        }
        
        public Ticket(String name) { // 有参构造,给线程命名
            super(name);
        }
        
        private static int ticket = 1000; // 定义静态的票数,使得变量是公共的
        private static Object o = new Object(); // 如果用引用数据类型成员变量当作锁对象,必须是静态的,随着类的加载而加载,也是公共的
        
        public void run() { // 重写 run()方法
            while (true) { // 定义无限循环
                synchronized (Ticket.class) { // 用字节码对象当作锁对象
                    if (ticket == 0) { // 判断条件跳出
                        break;
                    }
                    
                    try {
                        Thread.sleep(1); // 线程1睡,线程2睡,线程3睡,线程4睡
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    
                    System.out.println(getName() + "  这是第" + ticket-- + "号票");                
                }
            }
        }
    }
    继承Thread

    死锁问题

    * 多线程同步的时候,如果同步代码嵌套,使用相同的锁,就有可能出现死锁
      * 尽量不要嵌套使用

    package com.heima.syn;
    
    public class Demo5_DeadLock {
        private static String s1 = "筷子左"; // 锁1
        private static String s2 = "筷子右"; // 锁2
    
        public static void main(String[] args) {
            new Thread() {
                public void run() { // 重写 run()方法
                    for (int i = 0; i < 100; i++) {
                        synchronized (s1) {
                            System.out.println(getName() + ":获取" + s1 + " 等待" + s2);
                            synchronized (s2) {
                                System.out.println(getName() + ":拿到" + s2 + "开吃");
                            }
                        }
                    }
                }
            }.start(); // 开启线程1
            
            new Thread() {
                public void run() {
                    for (int i = 0; i < 100; i++) {
                        synchronized (s2) {
                            System.out.println(getName() + ":获取" + s2 + " 等待" + s1);
                            synchronized (s1) {
                                System.out.println(getName() + ":拿到" + s1 + "开吃");
                            }
                        }
                    }
                }
            }.start(); // 开启线程2
        }
    }
    DeadLock

    以前的线程安全类的回顾

    * A:回顾以前说过的线程安全问题
      * 看源码:Vector,StringBuffer,Hashtable,Collections.synchronized(xxx)

      * Vector 是线程安全的,ArrayList 是线程不安全的
      * StringBuffer 是线程安全的,StringBuilder 是线程不安全的
      * Hashtable 是线程安全的,HashMap 是线程不安全的
      * Collections.synchronized(xxx),可以将线程不安全的变成线程安全的

  • 相关阅读:
    ASP.NET异步处理
    C# TPL学习
    canvas 动画库 CreateJs 之 EaselJS(上篇)
    kafka消息的可靠性
    流式处理框架storm浅析(下篇)
    流式处理框架storm浅析(上篇)
    网易严选后台系统前端规范化解决方案
    Question | 移动端虚拟机注册等作弊行为的破解之道
    Puppeteer入门初探
    ThreeJs 3D 全景项目开发总结
  • 原文地址:https://www.cnblogs.com/zhaochuming/p/12722082.html
Copyright © 2020-2023  润新知