多线程

1) 概念

计算机中的一个程序运行时至少要有一个进程,一个进程中至少包含一个线程,线程是程序中的一个独立流程

示例:

   假设一家餐厅,一个厨师C,一个服务员F

顾客A和顾客B去餐厅吃饭:

   A先进:F-A点菜  3个菜  F-C   炒菜

   B进:F-B点菜  3个菜  F-C     炒菜

 

  如果是两个服务员F1  F2,此时AB就餐时F1-AF2-BF1F2就餐整个流程相互不影响,只是公用一个厨师炒菜

 

  假设AB同时就餐,此时C就同时得到了两份菜单:

处理一:先把A3个菜全部抄完,然后再炒B3个菜,出现等待状态严重

处理二:A-B菜交替进行 效率相对较高,AB两位认为是同时就餐

类比:

餐厅:一个应用程序

厨师:CPU(中央处理器)  按照时间片运行 抢占式调度执行(不是按顺序执行,谁抢到谁执行)

线程:F1  F2(两个流程独立运行,相互不影响)

 

线程并发:线程并发是一种表象,只是因为cpu的运算时间非常快,所以我们认为线程是并发执行的

 

线程开发:在一个程序中产生多个线程,让这些线程彼此独立完成特定的功能

问题:如何在java中去产生多个线程

 

 

2) 线程开发 -------(重写run()方法)

 

① 继承Thread

② 实现Runnable接口(没有start()方法),所以要借助它的子类thread实现  

先创建一个Runnable线程对象扔给thread构造方法构建一个t2线程出来,得到thread中的start方法

 

  注意:

           创建一个线程后需要调用start()方法启动线程,线程启动后由JVM获取cpu时间片调用run()方法执行。

           线程中的run()是不能手动调用,如果手动调用即无线程概念,只是普通方法的调用

           线程的结果是无规则的,只能从线程运行的结果反推线程执行过程

         主线程:main  

获取当前类的对象方法:

Thread.currentThread().getName()

3) 线程的状态

① 基本状态

① 阻塞状态 sleep()

 

 

当运行中的线程需要等待外部资源或调用线程sleep()时,当前线程进入阻塞状态,当阻塞结束后当前线程进入可运行状态

          阻塞状态的线程不会释放掉当前对象的对象锁

        

        业务需求:父母给你1000块生活费,每次给100块,给10次

           对于父母:每次给你100块,给10次

           对于你:每次接收100块,接10次

           父母开始给钱

               给第1100块

               给第2100块

               …

               给第10100块

           父母给钱结束 

          问题:在一个线程中需要等到另一个线程全部执行完后才继续往后执行

        ③ join()方法  ----一定发生

           调用当前线程的join()方法,则会一直等待当前线程执行完

           join(int time):只等待指定时间,时间一到,放弃等待继续执行

        ④ yield()方法  -让行  ----可能会发生

           yield()方法不会进入到阻塞状态,而是处于可运行状态,当线程调用此方法后,当前线程释放掉系统资源,由系统再次调用执行,此时,当前线程继续抢占资源,有可能继续抢占到资源继、续执行。

        线程一旦终止会继续往后执行,不会重头执行

 

           sleepyield方法的区别
asleep会产生异常,yield不会产生异常;

          bsleep会进入阻塞状态,yield不会

c、当一个线程调用sleep方法,线程会等sleep时间结束之后会进入可运行或就绪状态,而一个线程调用yield方法后会立即重新抢占cpu时间片继续执行

 

        ⑤ 线程优先级

           线程的优先级表示线程的运行顺序,优先级高就代表先运行

           线程的优先级从1-10,级别越高,优先级越高,默认优先级为5

           线程的优先级越高并不代表优先执行,线程的执行时通过cpu的调度执行(做开发的时 候通过代码来控制线程执行顺序)

4) 线程安全问题

线程不安全的原因:多个线程同时访问同一个临界资源,如果破坏了操作的原子性,则可能会造成数据不一致,这种情况我们叫做线程不安全。举例,stack中两个线程同时对stack进行操作

临界资源:多个线程共同访问的同一个对象

原子操作:线程中若干行代码不能分开执行,若不同步执行则会发生线程不安全现象

银行转账:

 

这一系列的操作叫做原子操作,临界资源为银行账户

存钱:插卡---密码---放钱---输入转入账户---修改账户余额---确认成功---取卡---成功

取钱:插卡---密码---取钱---确认成功---取卡---成功

 

        解决方法:线程同步--前后有关系

 同步:Synchronized       位置:  package lgs.hm.practice.test.thread.stack;

   A同步代码块   某一时刻只允许一个线程进入加了锁的同步代码块

  this的原因:当一个线程进入到同步快执行的时候有当前对象,那么这个线程就获得了当前对象的对象锁 ,synchronize同步块必须要拿到对象锁,对象锁只有一把,所以有多个同步块的时候必须要拿到对象锁才能执行,没有对象锁的进入到锁池状态(当前对象的锁池),就要等其它同步块运行完释放掉对象锁才能调用。

 

 B、同步方法

 

       锁池状态

          当一个线程遇到synchronized同步关键字时,则当前线程立即获取到当前对象的对象锁,开始同步执行,此时,若其他线程同样遇到当前对象的synchronized关键字时,因对象锁只有一把,而进入同步块一定需要拿到对象锁,否则不能进入同步块,所以其他线程得不到对象锁时会进入到锁池状态。当对象锁被释放后,锁池状态中会随机选取一个线程获取到对象锁,从而进入到可运行状态。  如果当前线程执行完释放掉对象锁后,让他在锁池状态中休息不参与线程之间对对象锁的的获取,就在synchronized之外睡眠一段时间

  口述线程的状态及各种状态的特点

5) 线程的死锁

 ① 概念

synchronized(a){

  int I = 10;

  synchronized(b){

 

}

}

synchronized(b){

  int I = 10;

  synchronized(a){

 

}

}

    实例:

② 解决死锁方法:Object中提供的方法:wait()、notify()notifyAll()

     wait():当一个对象调用其wait()方法后,当前线程会释放掉当前对象的对象锁,当前线程进入到当前对象的等待池中。wait()方法一定是在synchronized同步块中调用。

     notifyAll():当一个对象调用其notifyAll()方法后,会在等待池中唤醒需要当前对象锁的所有线程,此时被唤醒的线程会进入到锁池状态等待抢锁,当线程抢到了当前对象锁后进入可运行状态。

notify():和notifyAll一样,只是唤醒的是等待池中随机的一个线程。

6) 线程状态图

 

7) 生产者和消费者问题

 

产品类
public class Product {
    //定义产品的唯一ID
    int id;
    //定义构造方法初始化产品id
    public Product(int id) {
        this.id=id;
        // TODO Auto-generated constructor stub
    }
}

仓库
public class Repertory {
    //定义一个集合类用于存放产品.规定仓库的最大容量为10.
    public LinkedList<Product> store=new LinkedList<Product>();
    public LinkedList<Product> getStore() {
        return store;
    }
    public void setStore(LinkedList<Product> store) {
        this.store = store;
    }
    /* 生产者方法
     * push()方法用于存放产品.
     * 参数含义:第一个是产品对象
     * 第二个是线程名称,用来显示是谁生产的产品.
     * 使用synchronized关键字修饰方法的目的:
     * 最多只能有一个线程同时访问该方法.
     * 主要是为了防止多个线程访问该方法的时候,将参数数据进行的覆盖,从而发生出错.
     */
    public synchronized void push(Product p,String threadName)
    {
        /* 仓库容量最大值为10,当容量等于10的时候进入等待状态.等待其他线程唤醒
         * 唤醒后继续循环,等到仓库的存量小于10时,跳出循环继续向下执行准备生产产品.
         */
        while(store.size()==10){
            try {
                //打印日志
                System.out.println(threadName+"报告:仓库已满--->进入等待状态--->呼叫老大过来消费");
                //因为仓库容量已满,无法继续生产,进入等待状态,等待其他线程唤醒.
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //唤醒所有等待线程
        this.notifyAll();
        //将产品添加到仓库中.
        store.addLast(p);
        //打印生产日志
        System.out.println(threadName+"生产了:"+p.id+"号产品"+" "+"当前库存来:"+store.size());
        try {
            //为了方便观察结果,每次生产完后等待0.1秒.
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
         
    }
    /* 消费者方法
     * pop()方法用于存放产品.
     * 参数含义:线程名称,用来显示是谁生产的产品.
     * 使用synchronized关键字修饰方法的目的:
     * 最多只能有一个线程同时访问该方法.
     * 主要是为了防止多个线程访问该方法的时候,将参数数据进行的覆盖,从而发生出错.
     */
    public synchronized void pop(String threadName){
        /* 当仓库没有存货时,消费者需要进行等待.等待其他线程来唤醒
         * 唤醒后继续循环,等到仓库的存量大于0时,跳出循环继续向下执行准备消费产品.
         */
        while(store.size()==0)
        {
            try {
                //打印日志
                System.out.println(threadName+"下命令:仓库已空--->进入等待状态--->命令小弟赶快生产");
                //因为仓库容量已空,无法继续消费,进入等待状态,等待其他线程唤醒.
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //唤醒所有等待线程
        this.notifyAll();
        //store.removeFirst()方法将产品从仓库中移出.
        //打印日志
        System.out.println(threadName+"消费了:"+store.removeFirst().id+"号产品"+" "+"当前库存来:"+store.size());
        try {
            //为了方便观察结果,每次生产完后等待1秒.
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

生产者
public class Producer implements Runnable {
    //定义一个静态变量来记录产品号数.确保每一个产品的唯一性.
    public static  Integer count=0;
    //定义仓库
    Repertory repertory=null;
    //构造方法初始化repertory(仓库)
    public Producer(Repertory repertory) {
        this.repertory=repertory;
    }
    /* run()方法因为该方法中存在非原子性操作count++;
     * 当多个线程同时访问时会发生count++的多次操作,导致出错
     * 为该方法添加同步错做,确保每一次只能有一个生产者进入该模块。
     * 这样就能保证count++这个操作的安全性.
     */
    @Override
    public void run() {
            while (true) {     
                synchronized(Producer.class){
                    count++;
                    Product product=new Product(count);
                    repertory.push(product,Thread.currentThread().getName());
                }
                             
             
        }
         
    }
}

消费者

public class Consumer implements Runnable {
    //定义仓库
    Repertory repertory=null;
    //构造方法初始化repertory(仓库)
    public Consumer(Repertory repertory) {
        this.repertory=repertory;
    }
    //实现run()方法,并将当前的线程名称传入.
    @Override
    public  void run() {
        while(true){
            repertory.pop(Thread.currentThread().getName());
        }
    }
 
}

.测试类

public class TestDemo {
  public static void main(String[] args) {
    //定义一个仓库,消费者和生产者都使用这一个仓库
    Repertory repertory=new Repertory();
    //定义三个生产者(p1,p2,p3)
    Producer p1=new Producer(repertory);
    Producer p2=new Producer(repertory);
    Producer p3=new Producer(repertory);
    //定义两个消费者(c1,c2)
    Consumer c1=new Consumer(repertory);
    Consumer c2=new Consumer(repertory);
    //定义5个线程(t1,t2,t3,t4,t5)
    Thread t1=new Thread(p1,"张飞");
    Thread t2=new Thread(p2,"赵云");
    Thread t3=new Thread(p3,"关羽");
    Thread t4=new Thread(c1,"刘备");
    Thread t5=new Thread(c2,"曹操");
    //因为关羽跟赵云的生产积极性高,所以把他们的线程优先级调高一点
    t2.setPriority(10);
    t3.setPriority(10);
    //启动线程
    t1.start();
    t2.start();
    t3.start();
    t4.start();
    t5.start();
  }
}

//转自https://www.cnblogs.com/hckblogs/p/7858545.html#blogTitle

 

posted @ 2019-08-07 09:46  小小超plus  阅读(204)  评论(0编辑  收藏  举报