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

    1,线程和进程

    进程:就是计算机上运行的程序,它是一次动态执行过程,它需要经历从代码加载,代码执行到执行完毕的一个完整的过程,例如在window系统是一个多进程操作系统,用户可以一次打开多个进程(程序),然后程序在使用的时候去获取CPU的时间片,由于CPU的执行速度非常的快,使得所有程序好像在重新运行一样.

    关于cpu的两个小知识点

    CPU执行资格:可以被CPU处理,在处理队列中排队

    CPU执行权:正在被CPU处理,排队排到它了

    线程:就是进程中一个负责程序执行的控制单元(执行路径),一个进程中可以有多个执行路径,称为多线程,开启多个线程,是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务.

    在JVM虚拟机启动的时候,至少有两个线程,一个是执行main函数的线程,一个是堆内存中负责垃圾回收的线程

    2,Java中线程的实现

    在Java中实现多线程的方法有两种,一种是继承Thread,一种是实现Runnable接口

    2.1继承Thread类

    步骤:定义一个类继承Thread类 , 覆盖Thread类中的run方法

          创建Thread类的子类对象, 使用start方法开启线程,调用线程的任务run方法(可以通过Thread的getName来获取线程的名称,编号 Thread-编号 编号从0开始)

    一个类通过继承Thread来实现的话,那么只能调用一次start()方法,如果调用多次的话,会出现IllegalThreadStateException异常

    为什么要覆盖run方法?

    线程创建的目的是为了开启一条执行路径,去运行指定代码,或者和其他代码实现同时运行,同时运行的代码就是这个执行路径的任务

    Jvm创建的主线程的任务都定义在主函数main中.

    那么自定义的线程它的任务在哪里?

    Thread类用于描述线程,线程是需要任务的,Thread类也有对任务的描述,它是通过run方法来体现的,也就是说run方法就是封装的运行任务的函数

    Run方法中就是定义的线程要运行的任务代码

    开启线程是为了运行指定的代码,所以要继承Thread类,复写run方法

    以前单线程的栈内存中只有一条马路(main),现在到了多线程了,有开启几个线程就会在栈中多造几条马路,这个时候在栈中,有多线程的马路和main的马路

    currentThread方法是拿到当前正在运行的thread的对象

    2.3 实现Runnable接口

    步骤:定义类实现runnable接口,覆盖接口中的run方法,

           将线程的任务代码封装到run方法中 通过Thread类创建线程对象,将runnable的子类对象通过Thread构造函数的参数,传入Thread中 启动线程starts()

           这里如果不在Thread的构造函数中传入runnable的子类对象的话,线程start的时候,调用的是Thread中默认的线程代码,不是我们自己需要运行的线程代码

           它的出现仅仅是将线程的任务进行了封装

    实现Runnable和继承Thread的区别

    实现Runnable接口的好处

    1,将线程的任务从线程的子类中分离出来进行单独封装,按照面向对象的思想对任务封装成对象

    2,避免了java单继承的局限性

    所以实现Runnable这个方法比较常用

    在多线程的启动中为什么使用的是start(),而不是使用run()?

    在Thread类中,start的部分定义

    public synchronized void start() {
            if (threadStatus != 0)
                throw new IllegalThreadStateException();
    
            group.add(this);
    
            boolean started = false;
            try {
                start0();
                started = true;
            } finally {
                try {
                    if (!started) {
                        group.threadStartFailed(this);
                    }
                } catch (Throwable ignore) {
                    /* do nothing. If start0 threw a Throwable then
                      it will be passed up the call stack */
                }
            }
        }
    
        private native void start0();

    从上面代码中,可以看出,我们在调用start方法的时候,实际上使用的是start0()这个方法,该方法在声明出有个native关键字的声明,此关键字表示的是调用的是本机的操作系统的函数,因为多线程的实现需要依赖底层操作系统的支持

    Run和start的区别

    Start:

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

    Run:

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

    总结:调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。

     

     

    3,线程的状态

    线程状态图

    临时堵塞状态:在可执行的状态下,如果调用了sleep(),wait()等方法的时候,线程都进入了堵塞的状态,在堵塞的时候,线程不会进入排队的队列,只有当引起堵塞的原因被消除后,线程就能在进入队列,进行运行.

    4,同步和死锁

    一个多线程的程序如果是通过实现Runnable接口实现的话,那么类中的属性被多个线程共享,那样会造成一个问题,如果这多个线程要操作同一资源的时候就有可能出现资源同步的问题,例如卖票的问题,如果没有同步的话,一个线程就有可能在还没有对票数进行减操作的之前,其他线程就已经将票数进行减少了,这样一来就出现了票数为负的情况.

    解决这样的问题,必须使用同步,所谓的同步,就是指多个操作在同一时间段内只能有一个线程进行,其他线程要等待该线程完成之后,才能继续执行

    package cn.wjd.threaddemo;
    
    /*
     * 需求:卖票 
     */
    class Ticket implements Runnable {
        private int num = 100;
    
        public void run() {
            sell();
        }
    
        public void sell() {
            while (true) {
            synchronized (this) 
                {
                    if (num > 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + "..."
                                + num--);
                    } else
                        break;
                }
            }
        }
    }
    
    public class ThreadTest {
        public static void main(String[] args) throws InterruptedException {
            Ticket t = new Ticket();
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            Thread t3 = new Thread(t);
            Thread t4 = new Thread(t);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    
    }

    在Java中同步的方法有两种,同步代码块和同步函数

    synchronized(同步对象){                                                                                                                            

        需要同步的代码

    }

    synchronized 方法返回值   方法名称(参数列表){

    }

    同步函数和同步代码块的区别

    同步函数的锁是固定的this

    同步代码块的锁是任意的对象

    建议使用同步代码块

    静态的同步函数使用的锁是该函数所属字节码文件对象,可以使用getClass方法获取,也可以用当前类名.class形式来表示

    注意:关于死锁和同步

    多个线程在操作同一资源的时候需要进行同步,以保证资源操作的完整性,但是过多的同步就有可能产生死锁的现象.

    5,多线程的经典案例  生产者和消费者

    线程间通讯:多个线程在处理同一资源,但是任务却不同.

    等待/唤醒机制

    涉及的方法:

    1,wait() 让线程处于冻结状态,被wait的线程会被存储在线程池中

    2,notify() 用于唤醒线程池中的一个线程(任意)

    3,notifyAll() 唤醒线程池中的所有线程

    这些方法都必须定义同步中,

    因为这些方法是用于操作线程状态的方法,必须要明确到底操作的是哪个锁上的线程

    为什么操作线程这3个方法定义在Object中??

    因为这些方法是监视器的方法,监视器其实就是锁,锁可以是任意的对象,任意的对象调用的方法一定定义在Object

    生产者和消费者会产生的两个问题

    (1)假设生产者刚进入,它只添加了信息的名称,还有添加信息的内容,这个时候消费者线程进来了,那么消费者线程取走数据的时候,会将这次生产这信息的名称,和上次生产者生产的内容一并取走,这样就出现了数据的张冠李戴了

    (2)生产者放了若干数据后,消费者才开始取数据,或者是消费者取完一个数据后,还没等到生产者放入数据后,又重复取走了已经取过的数据

    还有加入是jdk1.5之前的方法加入同步,唤醒等待机制的话,比如T0和T1负责是是生产者,T2和T3负责的是消费者,当T0生产完之后,它需要去唤醒其他的某个线程,这个时候假如是唤醒的T1线程的话,这个情况就只有生产者,而没有消费者的存在了,程序将出现死锁的情况.

    在jdk1.5之后新的解决方法

    对于多线程有了新技术的加入,一个锁可以配备多个监视器,当生产者生产完毕后,我们可以用同一个锁的监视器去唤醒消费者,这样就解决了线程死锁的问题了,

    在jdk1.5之后,将同步和锁封装成了对象,并将操作锁的隐式动作变成了显示动作

    Lock接口: 它替代了同步代码块和同步函数,将同步的隐式锁操作,变成显式锁操作,同时它变的更为灵活,在一个锁上可以有多个监视器

    Lock():获取锁

    unlock():释放锁 这个要定义在finally代码中,因为不管出现什么情况,锁比较要释放后,才能让其他线程进来操作数据

    Condition接口:它的出现提交了wait()notify()notifyAll()这些方法,将这些监视器方法单独进行了封装,变成了Condition对象,可以和任意的锁进行组合

    多生产,多消费问题

    If 只能判断一次,会让等待醒来的线程不进行标记的判断就直接进行生产,导致数据的错误

    While判断标记,解决了线程获取执行权后,是否要运行,这个可以判断多次

    notifyAll 解决了本方线程一定会唤醒对方线程,避免了线程死锁问题的出现

    wait和sleep的区别:

    1,wait可以指定时间,也可以不指定,

    Sleep必须指定时间

    2,在同步中时,对cpu的执行权和锁的处理不同

    Wait:释放执行权,释放锁

    Sleep:释放执行权,不释放锁.它不需要被人叫醒

    Wait和notify一般是配合使用的,有一个Demo类中有show和method两方法,这两方法中都有同步代码块,使用的是同一个锁,T0,T1和T2在show的同步代码块中等待,当T4在method的同步代码块中唤醒所有notifyAll的线程的时候,这个时候T0,T1,T3他们都会具有cpu的执行资格,但是只有当T4执行完它的代码后,走出同步代码块后,T4释放了锁,这个时候0,1,2会只有一个随即拿到锁,这个时候拿到锁的哥们就同时具有cpu的执行资格和执行权,然后它就能进入同步代码块工作了.

    综上,在同步代码块中,线程能否在代码块中运行的关键是它是否具有该同步代码块的锁.

    package cn.wjd.threaddemo;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class Res{
        private String name;
        private int count = 1;
        boolean flag = false;
        Lock lock = new ReentrantLock();
        Condition conset = lock.newCondition();
        Condition conout = lock.newCondition();
        public void set(String name){
            lock.lock();
            try{
            while(flag)
                try {
                    conset.await();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            this.name = name + count;
            System.out.println(Thread.currentThread().getName() + "生产.."+this.name);
            count++;
            flag = true;
            conout.signal();
            }finally{
                lock.unlock();
            }
        }
        public void out(){
            lock.lock();
            try{
            while(!flag)
                try {
                    conout.await();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            System.out.println(Thread.currentThread().getName()+"出售......"+this.name);
            flag = false;
            conset.signal();
            }finally{
                lock.unlock();
            }
        }
    }
    
    class Producer implements Runnable{
        Res r;
        public Producer(Res r){
            this.r = r;
        }
        public void run(){
            while(true)
                r.set("福特汽车");
        }
    }
    
    class Consumer implements Runnable{
        Res r;
        public Consumer(Res r){
            this.r = r;
        }
        public void run(){
            while(true){
                r.out();
            }
        }
    }
    
    public class ProducerConsumer {
        public static void main(String[] args) {
            Res r = new Res();
            Producer p = new Producer(r);
            Consumer c = new Consumer(r);
            Thread t0 = new Thread(p);
            Thread t1 = new Thread(p);
            Thread t2 = new Thread(c);
            Thread t3 = new Thread(c);
            t0.start();
            t1.start();
            t2.start();
            t3.start();
        }
    
    }
  • 相关阅读:
    mysql之存储过程
    Artificial Intelligence in Finance
    7 Exciting Uses of Machine Learning in FinTech
    Machine Learning in Finance – Present and Future Applications
    AI AND THE BOTTOM LINE: 15 EXAMPLES OF ARTIFICIAL INTELLIGENCE IN FINANCE
    5 Ways AI is Transforming the Finance Industry
    图学Kubernetes
    Free Professional Resume Examples and Writing Tips
    npm和yarn的淘宝镜像添加
    世界最具影响力的十大管理大师
  • 原文地址:https://www.cnblogs.com/driverwjd/p/3864956.html
Copyright © 2011-2022 走看看