黑马程序员——5.多线程

来源:互联网 发布:淘宝发安能物流好恶心 编辑:程序博客网 时间:2024/05/01 16:52

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-

多线程:

进程:正在进行中的程序(直译)

线程:进程中一个负责程序执行的控制单元(执行路径)
线程控制着进程的执行

  1. 一个进程中可以有多个执行路径,称之为多线程。
  2. 一个进程中至少要有一个线程。
  3. 开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。

多线程的好处:解决了多部分代码同时运行的问题。
多线程的弊端:线程太多,会导致效率的降低。

JVM启动时启动了多条线程,至少有两个线程可以分析的出来:

  1. 执行main函数的线程,该线程的任务代码都定义在main函数中。
  2. 负责垃圾回收的线程。

创建线程方式一:继承Thread类

  1. 定义一个类继承Thread类。
  2. 覆盖Thread类中的run方法。
  3. 直接创建Thread的子类对象创建线程。
  4. 调用start方法开启线程并调用线程的任务run方法执行。
//需要多线程执行的类要继承Thread类class Demo extends Thread{    private String name ;    Demo(String name){        this.name = name;    }    //Run方法里定义的是线程要运行的任务代码    public void run(){        for(int x = 0; x < 10; x++){            //Thread.currentThread ():获取调用这个方法的线程对象;            //.getName():通过线程对象获取线程名称            System.out.println(name + "...x=" + x + "...ThreadName=" + Thread.currentThread ().getName());        }    }}class ThreadDemo{    //主线程    public static void main(String[] args){        //创建类的两个对象        Demo d1 = new Demo("旺财");        Demo d2 = new Demo("xiaoqiang");        //开启线程,调用run方法。        d1.start(); //开启额外的线程1        d2.start(); //开启额外的线程2        //线程1、线程2会与主线程抢占cpu资源        for(int x = 0; x < 20; x++){            System.out.println("x = " + x + "...over..." + Thread.currentThread().getName());        }    }}

创建线程方式二:实现Runnable接口

  1. 定义类实现Runnable接口。
  2. 覆盖接口中的run方法,将线程的任务代码封装到run方法中。
  3. 通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
  4. 调用线程对象的start方法开启线程。

实现Runnable接口的好处:

  1. 将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。
  2. 避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。
//创建类实现Runnable接口class Demo implements Runnable{    //将要运行的代码封装到run方法中    public void run(){        show();    }    public void show(){        for(int x = 0; x < 20; x++){            //获取线程名称            System.out.println(Thread.currentThread().getName() + "..." + x);        }    }}class ThreadDemo{    public static void main(String[] args){        //创建Demo类的一个对象        Demo d = new Demo();        //把Demo类对象作为参数创建Thread的两个对象        Thread t1 = new Thread(d);        Thread t2 = new Thread(d);        //执行Thread类对象,开启线程        t1.start();        t2.start();    }}

线程安全问题:

  1. 多个线程在操作共享的数据。
  2. 操作共享数据的线程代码有多条。

当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。

同步:

用于解决线程安全问题
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不可以参与运算。必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。

使用同步的前提:

  1. 必须有两个或两个以上线程
  2. 必须是多个线程使用同一个锁

同步的好处:解决了线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

把需要被同步的代码放进synchronized里面
同步代码块的格式:

synchronized(任意对象(即锁)){    需要被同步的代码;}
class Bank{    private int sum ;    public void add(int num){        //用synchronized把需要同步的代码括起来,锁设为this        synchronized(this ){            sum = sum + num;            System. out.println("sum = " + sum);        }    }}class Cus implements Runnable{    private Bank b = new Bank();    //把需要多线程执行的代码放在run方法里面    public void run(){        for(int x = 0; x < 3; x++){            b.add(100);        }    }}class BankDemo{    public static void main(String[] args){        Cus c = new Cus();        Thread t1 = new Thread(c);        Thread t2 = new Thread(c);        //开启新线程        t1.start();        t2.start();    }}

同步函数:

在需要同步的代码的函数上加上synchronized修饰符,锁默认固定为this
静态的同步函数使用的锁是该函数所属字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示。

class Bank{    private int sum ;    //在有安全问题的函数上加上synchronized修饰符    public synchronized void add(int num){        sum = sum + num;        System.out.println("sum = " + sum);    }}

多线程下的单例模式:

饿汉式不存在安全问题,因为不存在多个线程共同操作数据的情况。
懒汉式会有安全问题,因为他满足了同步问题的条件:

    1. 多个线程在操作共享的数据。    2. 操作共享数据的线程代码有多条。

所以要对懒汉式进行同步操作。

因为每次调用到对象都要先判断对象是否存在,如果直接使用同步之后再判断这种方式的话,创建对象之后的判断也会同步,效率太低,因此要使用双重判断的方式。

class Single{    private static Single s = null;    private Single(){}    public static Single getInstance(){        //先判断对象是否有被创建,解决创建了对象之后的效率问题        if(s ==null){            //之后再使用同步            synchronized(Single.class){                //使用同步之后再进行一次判断,解决安全问题                if(s == null)                    //如果对象不存在则创建                    s = new Single();            }        }        return s ;    }}

死锁:

死锁常见情景之一:同步的嵌套。

当一段代码需要拿到锁1跟锁2,而另一段代码需要拿到锁2跟锁1,就很容易出现一个线程拿到了锁1,而另一个线程拿到锁2,都在等待对方的锁,而又不释放锁,就会造成死锁。写代码时要注意避免这种情况。

线程间通信:

多个线程在处理同一资源,但是任务却不同,这时候就需要线程间通信。

等待/唤醒机制涉及的方法:

  1. wait():释放执行权,释放锁,让线程处于冻结状态,被wait的线程会被存储到线程池中。
  2. notify():唤醒线程池中的一个线程(任何一个都有可能)。
  3. notifyAll():唤醒线程池中的所有线程。

线程间通信容易出现死锁问题,要注意

JDK1.5新特性:

同步代码块就是对于锁的操作是隐式的。

JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。

Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
lock():获取锁。
unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。

Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
- Condition接口中的await方法对应于Object中的wait方法。
- Condition接口中的signal方法对应于Object中的notify方法。
- Condition接口中的signalAll方法对应于Object中的notifyAll方法。

/*
使用一个Lock、一个Condition修改多生产者-多消费者问题。
*/

import java.util.concurrent.locks.*;class Resource{       private String name ;       private int count = 1;       private boolean flag = false;      //创建一个锁对象      Lock lock = new ReentrantLock();       //通过已有的锁获取该锁上的监视器对象            Condition con = lock .newCondition();       public void set(String name){             //获取锁lock             lock.lock();             try{                   //判断标记,为假就进行生产操作,为真就进入等待池                   while(flag )                         try{                              //线程释放锁,进入等待池                              con.await();                        } catch(InterruptedException e){                              e.printStackTrace();                        }                   //进行生产操作                   this.name = name + count;                   count++;                   System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);                   //生产操作完成后,把标记置换为真                   flag = true ;                   //生产完成后唤醒该锁等待池中所有线程                   con.signalAll();            }finally{                   //最后一定要释放锁                   lock.unlock();            }      }       public void out(){            //获取锁lock            lock.lock();             try{                   //判断标记,为真就进行消费操作,为假就进入等待池                   while(!flag )                         try{                              con.await();                        } catch(InterruptedException e){                              e.printStackTrace();                        }                   //进行消费操作                   System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);                   //消费操作完成后,把标记置换为假                   flag = false ;                   //生产完成后唤醒该锁等待池中所有线程                   con.signalAll();            }finally{                   //最后一定要释放锁                   lock.unlock();            }      }}class Producer implements Runnable{       private Resource r ;       //通过构造函数传递资源对象       Producer(Resource r){             this.r = r;       }       public void run(){             while(true ){                   //循环生产商品“烤鸭”                   r.set( "烤鸭");            }      }}class Consumer implements Runnable{       private Resource r ;       //通过构造函数传递资源对象       Consumer(Resource r){             this.r = r;       }       public void run(){             while(true ){                   //循环消费商品                   r.out();            }      }}class ProducerConsumerDemo {       public static void main(String[] args){            Resource r = new Resource();            Producer pro = new Producer(r);            Consumer con = new Consumer(r);            //通过Producer、Consumer对象创建线程            Thread t0 = new Thread(pro);            Thread t1 = new Thread(pro);            Thread t2 = new Thread(con);            Thread t3 = new Thread(con);            //启动线程            t0.start();            t1.start();            t2.start();            t3.start();      }}

/*
使用一个Lock、两个Condition修改上面的多生产者-多消费者问题。
*/

    import java.util.concurrent.locks.*;    class Resource{           private String name ;           private int count = 1;           private boolean flag = false;           //创建一个锁对象           Lock lock = new ReentrantLock();           //通过已有的锁获取该锁上的监视器对象                 Condition con = lock .newCondition();           //通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者           Condition producer_con = lock .newCondition();           Condition consumer_con = lock .newCondition();           public void set(String name){                 lock.lock();                 try{                       while(flag )                             try{                                  //这时进入的是producer_con的等待池                                  producer_con.await();                            } catch(InterruptedException e){                                  e.printStackTrace();                            }                       this.name = name + count;                       count++;                       System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);                       flag = true ;                       //生产完之后唤醒consumer_con等待池中的一个线程来消费                       consumer_con.signal();                } finally{                       lock.unlock();                }          }           public void out(){                 lock.lock();                 try{                       while(!flag )                             try{                                   //进入的是consumer_con的等待池                                   consumer_con.await();                            } catch(InterruptedException e){                                  e.printStackTrace();                            }                       flag = false ;                       //消费完之后唤醒producer_con等待池中的一个线程接着生产                       producer_con.signal();                       System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);                } finally{                       lock.unlock();                }          }    }    class Producer implements Runnable{           private Resource r ;           Producer(Resource r){                 this.r = r;           }           public void run(){                 while(true ){                       r.set( "烤鸭");                }          }    }    class Consumer implements Runnable{           private Resource r ;           Consumer(Resource r){                 this.r = r;           }           public void run(){                 while(true ){                       r.out();                }          }    }    class ProducerConsumerDemo {           public static void main(String[] args){                Resource r = new Resource();                Producer pro = new Producer(r);                Consumer con = new Consumer(r);                Thread t0 = new Thread(pro);                Thread t1 = new Thread(pro);                Thread t2 = new Thread(con);                Thread t3 = new Thread(con);                t0.start();                t1.start();                t2.start();                t3.start();          }    }

线程的停止:

线程执行完毕后需要停止线程,停止线程一般使用判断条件的方法
任务中都会有循环结构,只要控制住循环就可以结束任务。

控制循环通常就用定义标记来完成。

class StopThread implements Runnable{       private boolean flag = true;       public void run(){             //while方法读到flase标记时退出循环,这时run方法中所有代码执行完毕,线程会自动停止             while(flag ){                  System. out.println(Thread.currentThread().getName() + "...");            }       }       public void setFlag(){             flag = false ;       }}class StopThreadDemo{       public static void main(String[] args){            StopThread st = new StopThread();            Thread t1 = new Thread(st);            Thread t2 = new Thread(st);            t1.start();            t2.start();             int num = 1;             for(;;){                   if(++num == 50){                        //当num==50时,把标记置换为假                        st.setFlag();                         break;                  }                  System. out.println("main..." + num);             }             System. out.println("over" );      }}

但是如果线程处于了冻结状态,无法读取标记,如何结束呢?

可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。强制动作会发生InterruptedException,一定要记得处理。

class StopThread implements Runnable{       private boolean flag = true;       public synchronized void run(){             while(flag){                   try{                        //把线程放入等待池                        wait();                  }                   //mian线程使用interrupt()方法强制将该线程恢复到运行状态,因此抛出了InterruptedException异常                  catch(InterruptedException e){                        System.out.println(Thread.currentThread().getName() + "..." + e);                        //这时将标记置换为假,退出while循环,线程停止                        flag = false;                  }                  System.out.println(Thread.currentThread().getName() + "......");            }      }       public void setFlag(){            flag = false;      }}class StopThreadDemo{       public static void main(String[] args){            StopThread st = new StopThread();            Thread t1 = new Thread(st);            Thread t2 = new Thread(st);            t1.start();            t2.start();             int num = 1;             for(;;){                   if(++num == 50){                        //num==50时,强制唤醒线程                        t1.interrupt();                        t2.interrupt();                         break;                  }                  System.out.println( "main..." + num);            }            System.out.println( "over");      }}

线程类的其他方法

setDaemon():
即后台线程,setDaemon()把线程改成守护线程,这个操作必须在线程启动前调用。守护线程跟一般线程没什么区别,只有一点,当当前运行的所有的线程都是守护线程时,守护线程会自动退出。

Join():
申请当前线程的执行权,当前线程会等待申请线程执行完再执行

setPriority():
设置优先级,优先级priority:1-10,默认优先级是5,优先级大的获取cpu执行权的可能性要大一点。参数一般使用Thread.MAX_PRIORITY(即10),Thread.MIN_PRIORITY(即1),Thread.NORM_PRIORITY(即5),

String toString()
返回该线程的字符串表示形式,包括线程名称、优先级和线程组。

static void yield()
暂停当前正在执行的线程对象,并执行其他线程。

0 0