zoukankan      html  css  js  c++  java
  • 多线程

    多线程的好处:多个线程之间互不影响(在不同的栈空间)

    java.lang.Thread类常用方法

    构造方法:
    public Thread() :分配一个新的线程对象
    public Thread(String name) :分配一个指定名字的新的线程对象
    public Thread(Runnable target) :分配一个带有指定目标新的线程对象
    public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字
    
    常用方法:
    public String getName() :获取当前线程名称
    public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法
    public void run() :此线程要执行的任务在此处定义代码
    public static void sleep(long millis) :使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
    public static Thread currentThread() :返回对当前正在执行的线程对象的引用
    获取当前正在执行的线程名称:Thread.currentThread().getName();
    设置线程的名称:(了解)
    1.使用Thread类中的方法setName(名字) void setName(String name) 改变线程名称,使之与参数 name 相同。 2.创建一个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类(Thread)给子线程起一个名字 Thread(String name) 分配新的 Thread 对象。

    创建线程的方式总共有两种,一种是继承Thread类方式,一种是实现Runnable接口方式

    创建多线程程序方式一:继承Thread类

    1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。
    2. 创建Thread子类的实例,即创建了线程对象
    3. 调用线程对象的start()方法来启动该线程

    例如:

    ①创建Thread类的子类
    public class MyThread extends Thread {
    
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println("run --> "+i);
            }
        }
    }
    
    ②创建Thread子类实例③调用start()方法启动线程
    public class Demo {
        public static void main(String[] args) {
            MyThread mt=new MyThread();
            mt.start();
    
            //主线程
            for (int i = 0; i < 20; i++) {
                System.out.println("main --> "+i);
            }
        }
    }
    
    执行结果:
    main --> 0
    run --> 0
    main --> 1
    run --> 1
    main --> 2
    run --> 2
    main --> 3
    run --> 3
    main --> 4
    run --> 4
    main --> 5
    main --> 6
    main --> 7
    main --> 8
    main --> 9
    main --> 10
    main --> 11
    main --> 12
    main --> 13
    main --> 14
    main --> 15
    main --> 16
    main --> 17
    main --> 18
    main --> 19
    run --> 5
    run --> 6
    run --> 7
    run --> 8
    run --> 9
    run --> 10
    run --> 11
    run --> 12
    run --> 13
    run --> 14
    run --> 15
    run --> 16
    run --> 17
    run --> 18
    run --> 19

    创建多线程程序方式二:实现java.lang.Runnable接口

    1.创建一个Runnable接口的实现类
    2.在实现类中重写Runnable接口的run方法,设置线程任务
    3.创建一个Runnable接口的实现类对象
    4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
    5.调用Thread类中的start方法,开启新的线程执行run方法

     

    实现Runnable接口创建多线程程序的好处:

    1.避免了单继承的局限性
    
      一个类只能继承一个类(一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
      实现了Runnable接口,还可以继承其他的类,实现其他的接口
    2.增强了程序的扩展性,降低了程序的耦合性(解耦)
      实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
      实现类中,重写了run方法:用来设置线程任务
      创建Thread类对象,调用start方法:用来开启新线程

    例如:

    //①创建一个Runnable接口的实现类
    public class RunnableImpl implements Runnable{
    
        //②在实现类中重写Runnable接口的run方法,设置线程任务
        @Override
        public void run() {
            for (int i = 0; i < 20; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }
    
    
    public class Demo02 {
        public static void main(String[] args) {
            //③创建一个Runnable接口的实现类对象
            RunnableImpl run = new RunnableImpl();
            //④创建Thread类对象,构造方法中传递Runnable接口的实现类对象
            Thread t = new Thread(run);
            //⑤调用Thread类中的start方法,开启新的线程执行run方法
            t.start();
    
            for (int i = 0; i < 20; i++) {
                System.out.println(Thread.currentThread().getName()+"-->"+i);
            }
        }
    }

    Thread类和Runnable的区别

    如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
    
    实现Runnable接口比继承Thread类所具有的优势:
    1. 适合多个相同的程序代码的线程去共享同一个资源。
    2. 可以避免java中的单继承的局限性。
    3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
    4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
    
    扩充:在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用
    java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进
    程。

    匿名内部类方式实现线程的创建

    package com.itheima.demo05.InnerClassThread;
    /*
        匿名内部类方式实现线程的创建
    
        匿名:没有名字
        内部类:写在其他类内部的类
    
        匿名内部类作用:简化代码
            把子类继承父类,重写父类的方法,创建子类对象合一步完成
            把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
        匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
    
        格式:
            new 父类/接口(){
                重复父类/接口中的方法
            };
     */
    public class Demo01InnerClassThread {
        public static void main(String[] args) {
            //线程的父类是Thread
            // new MyThread().start();
            new Thread(){
                //重写run方法,设置线程任务
                @Override
                public void run() {
                    for (int i = 0; i <20 ; i++) {
                        System.out.println(Thread.currentThread().getName()+"-->"+"线程A");
                    }
                }
            }.start();
    
            //线程的接口Runnable
            //Runnable r = new RunnableImpl();//多态
            Runnable r = new Runnable(){
                //重写run方法,设置线程任务
                @Override
                public void run() {
                    for (int i = 0; i <20 ; i++) {
                        System.out.println(Thread.currentThread().getName()+"-->"+"线程B");
                    }
                }
            };
            new Thread(r).start();
    
            //简化接口的方式
            new Thread(new Runnable(){
                //重写run方法,设置线程任务
                @Override
                public void run() {
                    for (int i = 0; i <20 ; i++) {
                        System.out.println(Thread.currentThread().getName()+"-->"+"线程C");
                    }
                }
            }).start();
        }
    }

    线程安全问题

    多线程访问了共享的数据,会产生线程安全问题
    
    线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;
    若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

    线程安全问题代码演示:卖票案例

    public class RunnableImpl implements Runnable {
    
        //定义一个多线程共享的票源
        private int ticket = 100;
    
        //设置线程任务:卖票
        @Override
        public void run() {
            //使用while循环模拟重复卖票
            while (true){
                //先判断票是否存在
                if(ticket>0){
                    //为了提高线程安全问题出现的概率,让线程睡眠一下
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
    
    
    //测试类
    public class Demo01 {
        public static void main(String[] args) {
            RunnableImpl run = new RunnableImpl();
            Thread t0 = new Thread(run);
            Thread t1 = new Thread(run);
            Thread t2 = new Thread(run);
    
            t0.start();
            t1.start();
            t2.start();
        }
    }

    运行结果:

    ...

    线程安全问题:卖出了重复的票和不存在的票

     解决线程安全问题的三种方式:同步代码块、同步方法、Lock锁

    //测试类
    /*
        模拟卖票案例
        创建3个线程,同时开启,对共享的票进行出售
     */
    public class Demo01Ticket {
        public static void main(String[] args) {
            //创建Runnable接口的实现类对象
            RunnableImpl run = new RunnableImpl();
            //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
            Thread t0 = new Thread(run);
            Thread t1 = new Thread(run);
            Thread t2 = new Thread(run);
            //调用start方法开启多线程
            t0.start();
            t1.start();
            t2.start();
        }
    }

    方式一:同步代码块

    //同步代码块
    /*
        卖票案例出现了线程安全问题
        卖出了不存在的票和重复的票
    
        解决线程安全问题的一种方案:使用同步代码块
        格式:
            synchronized(锁对象){
                可能会出现线程安全问题的代码(访问了共享数据的代码)
            }
    
        注意:
            1.同步代码块中的锁对象,可以使用任意的对象
            2.但是必须保证多个线程使用的锁对象是同一个
            3.锁对象作用:
                把同步代码块锁住,只让一个线程在同步代码块中执行
     */
    public class RunnableImpl implements Runnable{
        //定义一个多个线程共享的票源
        private  int ticket = 100;
    
        //创建一个锁对象
        Object obj = new Object();
    
        //设置线程任务:卖票
        @Override
        public void run() {
            //使用死循环,让卖票操作重复执行
            while(true){
               //同步代码块
                synchronized (obj){
                    //先判断票是否存在
                    if(ticket>0){
                        //提高安全问题出现的概率,让程序睡眠
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        //票存在,卖票 ticket--
                        System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                        ticket--;
                    }
                }
            }
        }
    }

    方式二:同步方法&静态同步方法

    //同步方法:锁对象是this
    /*
        卖票案例出现了线程安全问题
        卖出了不存在的票和重复的票
    
        解决线程安全问题的二种方案:使用同步方法
        使用步骤:
            1.把访问了共享数据的代码抽取出来,放到一个方法中
            2.在方法上添加synchronized修饰符
    
        格式:定义方法的格式
        修饰符 synchronized 返回值类型 方法名(参数列表){
            可能会出现线程安全问题的代码(访问了共享数据的代码)
        }
     */
    public class RunnableImpl implements Runnable {
    
        //定义一个多线程共享的票源
        private int ticket = 100;
    
        //设置线程任务:卖票
        @Override
        public void run() {
            //使用while循环模拟重复卖票
            while (true){
                payTicket1();
            }
        }
    
        public synchronized void payTicket1(){
            //先判断票是否存在
            if(ticket>0){
                //为了提高线程安全问题出现的概率,让线程睡眠一下
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                ticket--;
            }
        }
        
        public void payTicket2(){
            synchronized (this){
                //先判断票是否存在
                if(ticket>0){
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
    
        }
    }
    //----------------------------------------------------------------------
    静态同步方法:锁对象是RunnableImpl.class
    /*
        卖票案例出现了线程安全问题
        卖出了不存在的票和重复的票
    
        解决线程安全问题的二种方案:使用同步方法
        使用步骤:
            1.把访问了共享数据的代码抽取出来,放到一个方法中
            2.在方法上添加synchronized修饰符
    
        格式:定义方法的格式
        修饰符 synchronized 返回值类型 方法名(参数列表){
            可能会出现线程安全问题的代码(访问了共享数据的代码)
        }
     */
    public class RunnableImpl implements Runnable{
        //定义一个多个线程共享的票源
        private static int ticket = 100;
    
    
        //设置线程任务:卖票
        @Override
        public void run() {
            System.out.println("this:"+this);//this:com.itheima.demo08.Synchronized.RunnableImpl@58ceff1
            //使用死循环,让卖票操作重复执行
            while(true){
                payTicketStatic();
            }
        }
    
        /*
            静态的同步方法
            锁对象是谁?
            不能是this
            this是创建对象之后产生的,静态方法优先于对象
            静态方法的锁对象是本类的class属性-->class文件对象(反射)
         */
        public static /*synchronized*/ void payTicketStatic(){
            synchronized (RunnableImpl.class){
                //先判断票是否存在
                if(ticket>0){
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
    
        }
    
        /*
            定义一个同步方法
            同步方法也会把方法内部的代码锁住
            只让一个线程执行
            同步方法的锁对象是谁?
            就是实现类对象 new RunnableImpl()
            也是就是this
         */
        public /*synchronized*/ void payTicket(){
            synchronized (this){
                //先判断票是否存在
                if(ticket>0){
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
    
        }
    }

    方式三:Lock锁

    //Lock锁
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    /*
        卖票案例出现了线程安全问题
        卖出了不存在的票和重复的票
    
        解决线程安全问题的三种方案:使用Lock锁
        java.util.concurrent.locks.Lock接口
        Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
        Lock接口中的方法:
            void lock()获取锁。
            void unlock()  释放锁。
        java.util.concurrent.locks.ReentrantLock implements Lock接口
    
    
        使用步骤:
            1.在成员位置创建一个ReentrantLock对象
            2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
            3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
     */
    public class RunnableImpl implements Runnable{
        //定义一个多个线程共享的票源
        private  int ticket = 100;
    
        //1.在成员位置创建一个ReentrantLock对象
        Lock l = new ReentrantLock();
    
        //设置线程任务:卖票
        @Override
        public void run() {
            //使用死循环,让卖票操作重复执行
            while(true){
                //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
                l.lock();
    
                //先判断票是否存在
                if(ticket>0){
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                        //票存在,卖票 ticket--
                        System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                        ticket--;
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                        l.unlock();//无论程序是否异常,都会把锁释放
                    }
                }
            }
        }
    
        /*//设置线程任务:卖票
        @Override
        public void run() {
            //使用死循环,让卖票操作重复执行
            while(true){
               //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
               l.lock();
    
                //先判断票是否存在
                if(ticket>0){
                    //提高安全问题出现的概率,让程序睡眠
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                    //票存在,卖票 ticket--
                    System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
                    ticket--;
                }
    
                //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                l.unlock();
            }
        }*/
    }
  • 相关阅读:
    《Docker Deep Dive》Note
    使用 Angular RouteReuseStrategy 缓存(路由)组件
    我的 VSCode 配置
    TCP/IP协议
    Fiddler代理手机抓包
    基于 Docker 和 GitLab 的前端自动化部署实践笔记
    Vue.js 2.x render 渲染函数 & JSX
    服务器免密登陆脚本
    gitlab+jenkins+pm2+rsync实现node的自动化部署
    nginx常用
  • 原文地址:https://www.cnblogs.com/svipero/p/12516788.html
Copyright © 2011-2022 走看看