• 并发编程@线程基础知识回顾


    1 理解线程、创建线程

    1.1 线程:程序中某一条执行线索

    1.2 创建线程的方式

    继承Thread和实现Runnale接口

        /**
         * Description:两种创建线程的方法,extends Thread和 implements Runnable
         */
        
        // 1. 通过extends继承Thread
        class CreateThread1 extends Thread {
            @Override
            public void run() {
                while (true) {
                    System.out.println("This is " +
                            Thread.currentThread().getName());
                }
            }
        }
        
        // 2. 通过implements实现runnable接口,创建资源对象
        class CreateThread2 implements Runnable {
            @Override
            public void run() {
                while (true) {
                    System.out.println("This is " +
                            Thread.currentThread().getName());
                }
        
            }
        }
        
        public class TestCreateThread {
            public static void main(String[] args) {
                //以第一种方式创建线程并启动 (extends Thread)
                CreateThread1 thread1 = new CreateThread1();
                thread1.start();
        
                //以第二种方式创建线程并启动 (implements Runnale)
                CreateThread2 t = new CreateThread2();
                Thread thread2 = new Thread(t);
                thread2.start();
        
                while (true) {
                    System.out.println("This is " +
                            Thread.currentThread().getName());
                }
            }
        }
    

    1.3 两种创建线程方法的对比

    1 Extends创建对象:通过new Thread()方式直接创建线程对象,会直接产生一个该线程访问的资源对象
    
    2 Implements创建对象:只能通过new Thread(Runnable target)方式创建对象,只要target对象是同一个,那么创建出来的线程访问的资源对象都相同
    
    3 相比之下,实现Runnale接口比继承Thread类会有以下几点优势:
    
        i   适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效分离,较好地体现了面向对象的设计思想
        
        ii  避免了Java单继承机制带来的局限,可以实现“把继承了某一父类的子类放入线程中”
        
        iii 有利于程序的健壮性,某一资源代码可以被多个线程共享,代码与数据是独立的。多个线程可以操作相同的数据,与它们的代码无关。
    

    1.4 后台线程、联合线程(了解)

    1 后台线程:后台线程是相对前台线程而言的,正常创建并启动的是前台线程,如果在某一线程调用start()启动前,调用了setDaemon(true)方法,这个线程就变成了后台线程 
    
    2 联合线程:在某一线程T在执行时,调用另一个正在执行的线程otherThread的join()方法,将另外一个线程合并到自己的线程上,otherThread会合并到T线程上,并在该线程上执行,这时候T线程会暂停执行,这就是联合线程 
    T线程什么时候恢复执行? 
    
        i  当otherThread执行完毕并终止,T线程才会继续执行 
    
        ii 指定的join(time)中的time时间到,会自动分离两个线程,T线程继续执行
    
    

    2 线程同步

     线程安全:多个线程共享同一个资源,在对必须看成整体的某个代码块(具有原子性的代码块),进行操作时,操作还未完成就被打断,会造成数据的前后不一致,因此造成线程的不安全 
    
     解决方法:线程同步——同步代码块、同步函数 关键字Synchronized 
    
     在这里必须引入某些概念: 
    
      1. java的每个对象都具有一个锁旗标(俗:锁标记) 
    
      2. Synchronized检查的对象被称为监视器 
    
      3. 某线程要进入Synchronized修饰的同步代码块或同步函数,必须持有监视器的锁旗标: 
    
       i   如果经检查该线程拥有监视器的锁旗标,该线程可以进入同步代码块或同步函数并执行,当执行完毕,会自动释放本监视器的锁旗标,同时,有可能再次获取到本监视器的锁旗标 
    
       ii  如果经检查该线程没有监视器的锁旗标,该线程不被允许进入同步代码块或同步函数,会被分配到与该监视器相关联的锁池中,并等待机会获取锁旗标,执行代码 
    
       iii 如果某线程本身持有其他对象的锁旗标,但并不具备本监视器的锁旗标,同样会被加入本监视器的锁池,并且其自身拥有的锁旗标不会释放 
    
    1. 死锁:
     i   A线程拥有T1监视器的锁旗标,在执行T1监视器中的代码时候,企图获取B线程所拥有的T2监视器的锁旗标 
    
     ii  B线程拥有T2监视器的锁旗标,在执行T2监视器中的代码时候,企图获取A线程所拥有的T1监视器的锁旗标 
    
     iii 结果:双方都抱着自己所拥有的锁不放,从而线程无法继续
    
    Exp:
        
        /**
         * DeadLock:
         * (1)  A线程拥有T1监视器的锁旗标,在执行T1监视器中的代码时候,企图获取B线程所拥有的T2监视器的锁旗标
         * (2)  B线程拥有T2监视器的锁旗标,在执行T2监视器中的代码时候,企图获取A线程所拥有的T1监视器的锁旗标
         * res:
         * 双方都抱着自己所拥有的锁不放,从而线程无法继续
         */
        
        class A implements Runnable {
            static byte[] t1 = new byte[0];
        
            @Override
            public void run() {
                Thread.currentThread().setName("Thread A");
                //(1)A线程拥有T1监视器的锁旗标
                synchronized (t1) {
                    try {
                        Thread.currentThread().sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() +
                            " attend to call B.t2");
                    //企图获取B线程所拥有的T2监视器的锁旗标
                    synchronized (B.t2) {
                        System.out.println(B.t2);
                    }
                }
            }
        }
        
        class B implements Runnable {
            static byte[] t2 = new byte[0];
        
            @Override
            public void run() {
                Thread.currentThread().setName("Thread B");
                //B线程拥有T2监视器的锁旗标
                synchronized (t2) {
                    try {
                        Thread.currentThread().sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() +
                            " attend to call A.t1");
                    //企图获取B线程所拥有的T2监视器的锁旗标
                    synchronized (A.t1) {
                        System.out.println(A.t1);
                    }
                }
            }
        }
        
        public class TestDeadLock {
        
            public static void main(String[] args) {
                new Thread(new A()).start();
                new Thread(new B()).start();
            }
        
        }
    

    3 线程通信

    生产者消费者问题:有什么问题?

    3.1线程安全

    问题描述:若生产过程或消费过程被打断,数据会产生不一致 
    
    解决方法:所以必须对生产过程和消费过程加锁 
    

    3.2并发执行

    问题描述: 
     (1)若生产过快,消费会漏掉某些生产品 
     (2)若消费过快,会产生某一产品被消费多次(现实是不可能事件) 
    
    解决方法: 
     现实生活中,如果要做到生产1个消费一个,那么 
     (1) 当生产过快,那么生产者每生产一个就必须停下来,等消费者消费完一个,再继续执行生产 
     (2) 当消费过快,那么消费者每消费一个就必须停下来,等生产者生产完一个,再继续执行消费(不得不这样) 
     程序中,可通过wait(),notify,notifyAll,模拟实现 
    

    3.3 模拟生产者消费者问题

    Exp:
        /**
         *生产者消费者问题
         **/
        class Product {
            private int pno = 0;
            private String pname = null;
            private boolean isfull = false;
        
            public synchronized void putProduct(int pno, String pname) {
                //若生产满了、即生产过快,那么使生产者暂停生产
                if(isfull) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
        
                this.pno = pno;
                //人为制造意外,生产过程未完成就被打断
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.pname = pname;
                //每生产一个就显示出来
                System.out.println("Producer make " + pname);
        
                isfull = true;
                notify();
            }
        
            public synchronized void getProduct() {
                if(!isfull) {
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("Consumer pick " + pname);
        
                isfull = false;
                notify();
            }
        
        }
        
        class Producer implements Runnable {
            private Product p = null;
            private int i = 1;
        
            public Producer(Product p) {
                this.p = p;
            }
        
            @Override
            public void run() {
                while(true) {
                    if(1 == i % 2) {
                        i++;
                        p.putProduct(1, "Product1");
                    } else {
                        i++;
                        p.putProduct(2, "Product2");
                    }
                }
            }
        }
        
        class Consumer implements Runnable {
            Product p = null;
        
            public Consumer(Product p) {
                this.p = p;
            }
        
            @Override
            public void run() {
                while(true) {
                    p.getProduct();
                }
            }
        }
        
        
        
        public class TestProCon {
        
            public static void main(String[] args) {
                Product p = new Product();
                Producer producer = new Producer(p);
                Consumer consumer = new Consumer(p);
                Thread t1 = new Thread(producer);
                Thread t2 = new Thread(consumer);
                t1.start();
                t2.start();
            }
        
        }
        
        Res:
                Producer make Product1
                Consumer pick Product1
                Producer make Product2
                Consumer pick Product2
                ……
    

    3.4 wait(),notify,notifyAll

        wait()方法,谁调用它,谁就沉默,沉默就放弃自己拥有的锁旗标 
    
        notify()方法,唤醒同一对象监视器中调用wait()的第一个线程 
       
        notifyAll()方法,唤醒同一对象监视器中调用 wait()的所有线程,被唤醒的线程会加入锁池中,等待锁旗标,一旦拥有锁旗标就进入Runnable状态
    

    4 线程的生命控制

    4.1线程的生命周期

    线程的生命周期

        1 初始状态,线程创建,线程对象调用 start() 方法。
    
        2 可运行状态,也就是等待 Cpu 资源(os调度),等待运行的状态。
    
        3 运行状态,获得了 cpu 资源,正在运行状态。
    
        4 阻塞状态,也就是让出 cpu 资源,进入一种等待状态,而且不是可运行状态,有三种情况会进入阻塞状态。
    
            i 如等待输入(输入设备进行处理,而 CPU 不处理),则放入阻塞,直到输入完毕,阻塞结束后会进入可运行状态。
    
            ii 线程休眠,线程对象调用 sleep() 方法,阻塞结束后会进入可运行状态。
    
            iii 线程对象 2 调用线程对象 1 的 join() 方法,那么线程对象 2 进入阻塞状态,直到线程对象 1 中止。
    
        5 中止状态,也就是执行结束。
    
        6 锁池状态
    
        7 等待队列 
    

    4.2 控制线程的生命

    (用boolean型的flag)

    5 避免无谓的线程控制

    Exp:
        class Test {
            private byte[] resource1 = new byte[0];
            private byte[] resource2 = new byte[0];
    
            public synchronized void method1() {
                //处理resource1
            }
    
            public synchronized void method2() {
                //处理resource1
            }
    
            public synchronized void method3() {
                //处理resource2
            }
    
            public synchronized void method4() {
                //处理resource2
            }
    
        }
    
        分析:
                已知:
                method1和method2都是对resource1进行处理,需要同步控制
                method3和method4都是对resource1进行处理,需要同步控制
                method1和method3、4,method2和method3、4处理的对象不同,它们之间本可以一起进行,是互不影响
                但是此程序将监视器对象设置为调用对象this本身,那么在执行1的时候,3和4也是无法拿到监视器的锁,造
                成3、4无法进行、这就是无谓的同步控制
    
                Alter:
    
        class Test {
            private byte[] resource1 = new byte[0];
            private byte[] resource2 = new byte[0];
            private byte[] lock1 = new byte[0];
            private byte[] lock2 = new byte[0];
    
            public void method1() {
                //处理resource1
                synchronized (lock1) {
    
                }
            }
    
            public void method2() {
                //处理resource1
                synchronized (lock1) {
    
                }
            }
    
            public void method3() {
                //处理resource2
                synchronized (lock2) {
    
                }
            }
    
            public void method4() {
                //处理resource2
                synchronized (lock2) {
    
                }
            }
    
        }
    
        分析:
                通过创建两个不同的对象当监视器,使method1和method2关联lock1,method3和method4关联lock2
                从而避免了无谓的同步控制,使程序性能得以提升
    
  • 相关阅读:
    docker 会这些也够
    Linux 学会这些基本可以啦
    xxxxxxxxx
    Angular
    mongo
    node
    git clone 解决Permission Denied (publickey)问题
    vue项目如何刷新当前页面
    vue——动态路由以及地址传参
    vue 单页应用点击某个链接,跳转到新页面的方式
  • 原文地址:https://www.cnblogs.com/qq438649499/p/12190813.html
Copyright © 2020-2023  润新知