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

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
            从而避免了无谓的同步控制,使程序性能得以提升
posted @ 2020-01-14 10:48  默月  阅读(166)  评论(0编辑  收藏  举报