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

    多线程概述

    日常生活中很多事都是可以同时进行的,例如:人可以同时进行呼吸、血液循环、思考问题等活动。

    多线程是指一个应用程序中有多条并发执行的线索,每条线索都被称作为一个线程,它们会交替执行,彼此之间可以进行通信。

    多线程之主线程概念:JVM启动后,会有一个执行路径(线程)从main方法开始的,一直执行到main方法结束,这个线程在Java中称之为主线程。

    线程对象调用 run方法和调用start方法区别?

     调用run方法不开启线程。仅是对象调用方法。

     调用start开启线程,并让jvm调用run方法在开启的线程中执行。

    Thread

    创建线程

        方式一继承Thread

    创建线程的步骤

    1. 定义一个类继承Thread类。
    2. 重写run方法。
    3. 创建子类对象

    调用start方法,开启线程并让线程执行,同时还会告诉JVM去调用run方法。

     

    public class Demo {
        public static void main(String[] args) {
            //创建自定义线程对象
            MyThread myThread = new MyThread();
            //开启新线程    
            myThread.start();
            while (true) {// 死循环
                System.out.println("Main 方法在运行");
            }
        }
    }

    方式二 实现Runnable接口

    1. 定义类实现Runnable接口
    2. 覆盖接口中的run方法
    3. 创建Thread类的对象
    4. Runnable接口的子类对象作为参数传递给Thread类的构造函数
    5. 调用Thread类的start方法开启线程

    方式三实现callable接口:此方法不常用就不做探究了,还有别的开启线程的方法,我最常用的是上面俩种。

    线程安全问题

    个人理解:线程安全就是说多线程访问同一代码,不会产生不确定的结果,而不安全就是多个线程对同一个对象进行操作,对象本身会产生不确定的结果。

    举个例子:你去食堂打饭(你是一个线程),你来晚了,就剩最后一份糖醋排骨了(不纠结喜不喜欢吃的问题)你正在向阿姨要最后一份排骨,这时候另外一个人(另外一个线程)过来插你队也要糖醋排骨。这时候阿姨按理来说应该把排骨给你,可是这个插你队的是阿姨侄子,阿姨可以给你也可以给他侄子(就看公私分不分明)。这样阿姨就处在不确定的情况下。而线程安全就是直接强行规定死了,只能你这个线程打完饭,后面的人才能去打饭,只有等你释放了食堂阿姨这个资源之后下面的人才能去进行(打饭)操作。你不打完饭释放资源,下面的不可能去操作食堂阿姨这个角色。(个人理解有问题欢迎━(*`∀´*)ノ亻!指正)

    不喜欢吃糖醋排骨我再来一个:买票

    多线程模拟火车站的售票窗口,每一个线程表示一个售票窗口,共出售100张票

    ublic class Demo2 {
        public static void main(String[] args) {
            TicketWindow window = new TicketWindow();
            new Thread(window,"窗口1").start();
            new Thread(window,"窗口2").start();
            new Thread(window,"窗口3").start();
            new Thread(window,"窗口4").start();
        }
    }
    class TicketWindow implements Runnable{
        private int tickets = 100;
        @Override
        public void run() {
            while(true){
                if(tickets > 0){
                    try {
                        //睡眠让问题更容易“暴露”
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    String threadName = Thread.currentThread().getName();
                    System.out.println(threadName + "正在发售第"+tickets--+"张票");
                }else{
                    break;
                }
            }
        }
    }
    View Code

    上面的例子,由于多个线程共用了同一个资源(票),会造成线程安全问题,票售出了-1张

    错误截图

    线程安全问题都是由全局变量静态变量以及共享的变量引起的。若每个线程中对全局变量、静态变量以及共享的变量只有读操作,那么这个全局变量是线程安全的;

    若有多个线程同时执行写操作(修改),都需要考虑线程同步,否则就可能发生线程安全问题。

    Java中提供了线程同步机制,它能够解决上述的线程安全问题。

    方式一:同步代码块 

    方式二:同步方法

    同步代码块: 在代码块声明上,加上synchronized

    synchronized (锁对象(lock、被锁对象)) {
        可能会产生线程安全问题的代码
    }

    同步代码块中的锁对象可以是任意的对象;但多个线程时,要使用同一个锁对象才能够保证线程安全。

     lock 是一个锁对象,是同步代码块的关键,锁对象可以是任意类型的对象,是多个线程共享的锁对必须是唯一的,“任意”说的是共享锁对象的类型。所以,锁对象的创建代码不能放到run()方法中,否则每个线程都会创建一个新的对象,每个锁都有自己的标志,那就没有意义了

    被锁对象:表示如果当前线程访问"被锁对象"synchronized的代码块时,其它线程不能访问此代码块,另外,也不能访问"被锁对象"中的其它synchronized的代码块;

    同步的好处:解决了多线程并发访问的问题,使多线程并发访问的共享数据能够保持一致性

    同步的弊端:使当前的对象的工作效率降低;因为要处理线程同步的问题;

    同步方法:在方法声明上加上synchronized

    public synchronized void method(){
           可能会产生线程安全问题的代码
    }


    同步方法的也是一样锁住同步的代码,但是锁对象的是Runable实现类对象,也就是this,谁调用方法,就是谁,在这里就是创建的run对象

    第三种方法:Lock接口

    java.util.concurrent.locks.Lock
    Lock接口中的方法:
    void lock():获取锁
    void unlock():释放锁

    使用方法:
    1、在Runable实现类的成员变量创建一个ReentrantLock对象
    2、在可能产生线程安全问题的代码前该对象调用lock方法获取锁
    3、在可能产生线程安全问题的代码后该对象调用unlock方法获取锁

    public class xiancheng implements Runnable {
        int ticket = 100;//火车票100张
        Object o = new Object();//创建一个锁对象
        ReentrantLock r = new ReentrantLock();
        // java除了使用关键字synchronized外,还可以使用ReentrantLock实现独占锁的功能。
        // 而且ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景
        // 在Runable实现类的成员变量创建一个ReentrantLock对象
    
        @Override
        public void run() {
    
            while (true) {
                if (ticket > 0) {
                    try {
                        r.lock();//在可能出现线程安全的地方上锁
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        // unlock方法放在finally里面,无论程序是否有出现异常,该方法都会执行,也就是都会释放锁
                        r.unlock();// 3、在可能产生线程安全问题的代码后该对象调用unlock方法获取锁
    
                    }
                }
    
            }
        }
    }
  • 相关阅读:
    BadUSB 利用
    java 将函数作为参数传递
    odoo12 修行提升篇之 常用的高阶函数 (二)
    odoo12 修行提升篇之 异步定时任务 (一)
    odoo12 修行基础篇之 利用kanban做分析 点击跳转分析模型列表 (九)
    odoo12 修行基础篇之 kanban (八)
    odoo12 修行基础篇之 记录批处理 (七)
    odoo12 修行基础篇之 列表的筛选和分组 (六)
    odoo12 修行基础篇之 添加记录编码 (五)
    odoo12 修行基础篇之 添加工作流和操作记录 (四)
  • 原文地址:https://www.cnblogs.com/cmm123/p/13207727.html
Copyright © 2011-2022 走看看