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

——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()
暂停当前正在执行的线程对象,并执行其他线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值