您的位置:首页 > 职场人生

黑马程序员——多线程 笔记第九篇

2014-11-16 23:57 260 查看


多线程的笔记


-----Java培训、Android培训、期待与您交流!
-------

1线程的概述

进程:正在运行的程序称作为进程。负责了内存的划分。

线程:在一个进程中线程是负责了代码的执行,线程就是一个进程中代码执行路径。

多线程:在一个进程中存在多个线程同时在执行多个任务代码。

 

疑问:一个java的应用程序至少有几个线程?

         2个 线程, 一个是执行main方法的线程---主线程。 还有一个就是垃圾回收器线程。

 

1.1多线程好处:

         1.解决了一个进程中同时执行多个任务的问题。

         2.提高了资源的利用率,而不是提供效率。

 

1.2多线程的弊端:

        1. 增强了cpu的负担。

        2. 降低了进程中线程的执行概率。

        3. 出现线程安全问题。

        4. 引发死锁现象。

 

1.3线程常用方法:

Thread(String name)     初始化线程的名字

getName()             返回线程的名字

setName(String name)    设置线程对象名        

sleep() 静态的方法,那个线程执行了sleep代码,那么就那个线程睡眠。线程睡眠指定的毫秒数。

currentThread()      返回正在执行该句代码的当前线程对象.     

getPriority()             返回当前线程对象的优先级   默认线程的优先级是5

setPriority(int newPriority) 设置线程的优先级    虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10 ,最小的1 , 默认是5)。

 

1.4线程的使用细节:

1.         线程的启动使用父类的start()方法

2.         如果线程对象直接调用run(),那么JVN不会当作线程来运行,会认为是普通的方法调用。

3.         线程的启动只能由一次,否则抛出异常

4.         可以直接创建Thread类的对象并启动该线程,但是如果没有重写run(),什么也不执行。

5.         匿名内部类的线程实现方式

 

1.5线程的状态

创建:新创建了一个线程对象。

可运行:线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取cpu的执行权。

运行:就绪状态的线程获取了CPU执行权,执行程序代码。

阻临时塞: 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。

死亡:线程执行完它的任务时。

 

2 线程的创建方式:

2.1继承Thread类

方式1:

1.  自定义一个类继承Thread类

2.  重写Thread的run方法,把自定义线程的任务定义在run方法中。

3. 创建Thread的子类对象,并调用start方法启动线程。线程就会执行run方法中的代码。

public
class
Demo1 extends Thread {
    @Override
    public
void
run() {
       //给自定义线程分配一个任务代码。
       for(inti = 0 ;i<100 ;i++){
           System.out.println("自定义线程:"+i);
       }
    }
   
    public
static void
main(String[]
args) {
       //创建了一个线程对象。
       Demo1 d = new Demo1();
       //启动一个线程。
       d.start();
       for(inti = 0 ;i<100 ;i++){
           System.out.println("main线程:"+i);
       }
    }
}

 

2.2使用Runnable接口.

方式2:

        1. 自定义一个类实现Runnable接口。

        2. 实现Runnable接口 的run方法,把自定义线程的任务定义在run方法上。

        3. 创建Runnable实现类的对象。

        4. 创建Thread类的对象,然后把Runnable实现类的对象作为参数传递进去.

        5. 调用Thread类对象的start方法开启线程执行任务。

 

public
class
Demo4 implements Runnable{
    @Override
    public
synchronized void
run() {
       //this对象是代表着 Demo4的对象
       //当前线程对象代表的是Thread对象。
       for(inti = 0;i<100 ;i++){      
    System.out.println(Thread.currentThread().getName()+":"+i);
       }
    }
    public
static void
main(String[]
args) {
       //创建了Runnable实现类的对象
       Demo4 d = new Demo4();
       //创建一个Thread的对象,把Runnable实现类的对象作为参数传递进去。
       Thread thread1 =
new
Thread(d); // Thread使用了target变量记录了传递进入的Runnable实现类对象。
       //调用start方法开启线程.
       thread1.start();
       for(inti = 0;i<100 ;i++){      
    System.out.println(Thread.currentThread().getName()+":"+i);
       }
    }
}

推荐使用:实现Runnable接口的这种, 因为java是单继承的.

疑问1: 请问Runnable实现类的对象是不是一个线程对象?

         Runnable实现类的对象并不是一个线程对象,如果是一个线程对象肯定有具备开启线程的start方法。只有Thread类以及Thread的子类才是线程对象。

疑问2: 为什么要Runnable实现类的对象作为参数传递给Thread对象,其作用是什么?

         可以把Runnable实现类对象的run方法作为了线程的任务方法去执行了。

 

2.3线程安全问题

出现原因:

1.      必须是两个或者两个以上的线程共享着一个资源。

2.      操作资源的代码有多局

 

sun提供了同步机制解决线程安全问题:

方式1:使用同步代码块去解决

Synchronized(锁对象){

    需要被同步的代码;

}

同步代码块要需要注意的事项:

1、  锁对象可以是任意的一个对象

2、  多个线程使用的锁对象必须是唯一的,否则锁不会起作用

3、  如果一个线程在同步代码块中sleep了,并不会释放锁对象

 

方式2:同步函数  使用sychronized 修饰的函数

格式:

修饰符  synchronized  返回值类型  函数名(形参列表){

         函数体;

}

同步函数要注意的事项:

         1. 同步函数的锁对象是固定的。

         2. 非静态同步函数的锁对象是this对象。静态函数的锁对象是该函数所述的类的字节码对象。

推荐使用同步代码块的原因:

         1. 同步代码块的锁对象可以让我们自主 指定,同步函数的锁对象是固定的,不能让我们执行,这样子我们使用同步代码块的会更加 的灵活。

         2. 同步代码块使用起来效率更高。

 

 

//创建线程第一种方式实现卖票的例子
class SaleTicketextends Thread{
    static Objecto =new Object();
    static
int
tickets = 50;
//票数  
非静态的成员变量,非静态成员在每一个对象中都是维护着一份数据。
    public  SaleTicket(Stringname) {
       super(name);
    }
   
    @Override 
//写自定义线程的任务代码。
    public
void
run() {
       while(true){
           synchronized ("锁"){ //开
              if(tickets>0){
                  System.out.println(Thread.currentThread().getName()+"卖出了第"+tickets+"号票");
                  tickets--;
                  try {
                     this.sleep(100);
                  } catch (InterruptedExceptione) {
                     e.printStackTrace();
                  }
              }else{
                  System.out.println("售罄了...");
                  break;
              }
           }
       }
    }
}
 
public
class
Demo4 {
   
    public
static void
main(String[]
args) {
       //创建三个线程对象模拟三个窗口
       SaleTicket thread1 =
new
SaleTicket("窗口1");
       SaleTicket thread2 =
new
SaleTicket("窗口2");
       SaleTicket thread3 =
new
SaleTicket("窗口3");
       //开启线程去卖票
       thread1.start();
       thread2.start();
       thread3.start();
    }
}
/*
 * 需求:模拟车站卖票,卖50张票。
 */
 
//实现线程第二方式实现卖票的例子
class SaleTicketimplements Runnable{
    int
tickets = 50;
    @Override
    public
void
run() {
       while(true){
           synchronized (this) {//同步代码块的锁
              if(tickets>0){
                  System.out.println(Thread.currentThread().getName()+"卖了第"+tickets
+"号票" );
                  tickets--;
              }else{
                  System.out.println("已经售罄了..");
                  break;
              }
           }
       }
    }
}
 
public
class
Demo5 {
   
    public
static void
main(String[]
args) {
       SaleTicket saleTicket =new SaleTicket();
       //创建三个线程。
       Thread thread1 =
new
Thread(saleTicket,"窗口1");
       Thread thread2 =
new
Thread(saleTicket,"窗口2");
       Thread thread3 =
new
Thread(saleTicket,"窗口3");
       //开启线程
       thread1.start();
       thread2.start();
       thread3.start();
    }
}
 

死锁:两个或者两个以上的线程在互相等待对方的资源。

死锁现象出现的根本原因

      1. 必须要存在两个或者两个以上的线程对象。

      2. 多个线程在共享这个两个或者两个以上的资源。

死锁现象的解决方案: 没有方案。只能尽量去避免出现死锁现象。

 

public
class
DeadLock {

    public
static void
main(String[] args) {

       new Thread(new Runnable() {//创建线程,代表中国人

                  public
void
run() {

                     synchronized ("刀叉") {//中国人拿到了刀叉

                         System.out.println(Thread.currentThread().getName()

                                + ": 你不给我筷子,我就不给你刀叉");

                         try {

                            Thread.sleep(10);

                         } catch (InterruptedException e) {

                            e.printStackTrace();

                         }

                         synchronized ("筷子") {

                             System.out.println(Thread.currentThread()

                                   .getName() + ":给你刀叉");

                         }

                     }

                  }

              }, "中国人").start();

       new Thread(new Runnable() {//美国人

                  public
void
run() {

                     synchronized ("筷子") {//美国人拿到了筷子

                         System.out.println(Thread.currentThread().getName()

                                + ": 你先给我刀叉,我再给你筷子");

                         try {

                            Thread.sleep(10);

                         } catch (InterruptedException e) {

                            e.printStackTrace();

                         }

                         synchronized ("刀叉") {

                             System.out.println(Thread.currentThread()

                                   .getName() + ":好吧,把筷子给你.");

                         }

                     }

                  }

              }, "美国人").start();

    }

}

 

3线程的通讯

线程通讯:当一个线程完成了自己的任务的时候要通知另外一个线程做其他的任务。

线程通讯主要用到的方法:

wait() 等待         如果一个线程执行了wait方法,那么该线程会进入一个以锁对象为标识建立的线程池中进行等待。

notify() 唤醒    如果一个线程执行了notify方法,那么就会唤醒以锁为标识建立的线程池中 等待线程中其中一个。

notifyAll()  唤醒线程池中所有等待的线程。

 

使用wait与notify方法要注意的事项:

1. wait方法与notify不是属于Thread类的方法,是属于Obejct对象的方法。

2. 调用wait以及notify方法时,必须要在同步代码块调用。否则报错.

3. 调用wait与notify方法的时候必须要由锁对象调用,否则也报错。

4. 如果一个线程执行了wait方法,那么该线程会释放锁对象的。

 

//生产者
class Producer extends Thread{
    Product p;
//产品
    //构造函数
    public  Producer(Productp) {
       this.p =p;
    }
   
    @Override
    public
void
run() {
       int
i  = 0 ;
       while(true){
           synchronized (p) {
              if(p.flag==false){
                  if(i%2==0){
                     p.name  ="苹果";
                     p.price  = 5.5;
                  }else{
                     p.name="香蕉";
                     p.price = 2.0;
                  }
                  System.out.println("生产者已经成了"+p.name+"价格是:"+p.price);
                  i++;
                  p.flag =true;   //把产品的标识改成true,代表生产完毕,
                  p.notify(); //唤醒消费者去消费。
              }else{
                  //已经生产完毕,生产者线程就应该等待消费者消费。
                  try {
                     p.wait();
                  } catch (InterruptedExceptione) {
                     e.printStackTrace();
                  }
              }
           }
       }
    }
}
 
 
 
//消费者
class Customerextends Thread{
    Product p;
    public  Customer(Productp) {
       this.p =p;
    }
    @Override
    public
void
run() {
       while(true){
           synchronized (p) {
              if(p.flag ==true){
                  System.out.println("消费者消费了"+p.name+"价格:"+p.price);           
                  p.flag =false;//消费完毕,要修改产品的标识。
                  p.notify();
//唤醒生产者去生产。
              }else {
                  try {
                     p.wait();//没有生产完毕,应该等待生产者去生产。
                  } catch (InterruptedExceptione) {
                     e.printStackTrace();
                  }
              }
           }
       }
    }
}
 
 
public
class
Demo3 {
    public
static void
main(String[]
args) {
       //创建一个产品对象
       Product p= new Product();
       //生产者线程对象
       Producer producer =
new
Producer(p);
       //消费者线程对象
       Customer customer =
new
Customer(p);
       //启动线程
       producer.start();
       customer.start();
    }
}

 

4 线程的停止

 

停止线程要注意的事项:

1.一般我们停止一个线程都是通过一个变量去控制的。

 2. 如果这个线程目前是处于等待状态,那么一般是我们就要先让线程回到可运行状态,回到可运行状态方法有两个,notify、 interrupt。     

interrupt是一个比较粗暴的方法,被清除状态的线程会收到一个InterruptedException,被notify方法唤醒 的线程并不会接收到一个异常的。

 

 

public
class
Demo6 extends Thread {
    boolean
flag = true;
    public Demo6(Stringname){
       super(name);
    }
    @Override
    public
synchronized void
run() {
       int
i = 0 ;
       while(flag){
           try {
              this.wait(); //等待...
              System.out.println(Thread.currentThread().getName()+":"+i);
              i++;
           } catch (InterruptedExceptione) {
              System.out.println("接收到了InterruptedException了...");
           }
       }
    }
       public
static void
main(String[]
args) {
       //创建一个线程对象
       Demo6 d = new Demo6("狗娃");
       //开启线程
       d.start();
       for(inti = 0 ;i< 100 ;i++){
           System.out.println(Thread.currentThread().getName()+":"+i);
           //需求:当main线程的i到80的时候,那么停止狗娃线程。
           if(i==80){
              //d.stop();  //stop()可以停止一个线程,但是已经过时的方法。
              //d.interrupt();  //interrupt()是无法停止一个线程的。
              d.flag =false;
              d.interrupt(); 
//这个方法的作用:如果一个线程状态处于临时阻塞状态,那么interrupt就是把一个线程的临时阻塞状态清除掉,然后进入可运行状态。
              //d.notify();
              /*synchronized (d) {  //不加锁对象时会报错
                  d.notify();
              }*/
           }
       }
    }
}

 

5后台线程

后台线程:就是隐藏起来一直在默默运行的线程,直到进程结束。

实现:

      setDaemon(boolean on)

 5.1后台线程特点

当所有的非后台线程结束时,程序也就终止了同时还会杀死进程中的所有后台线程,也就是说,只要有非后台线程还在运行,程序就不会终止,执行main方法的主线程就是一个非后台线程。

必须在启动线程之前(调用start方法之前)调用setDaemon(true)方法,才可以把该线程设置为后台线程。

一旦main()执行完毕,那么程序就会终止,JVM也就退出了。

可以使用isDaemon() 测试该线程是否为后台线程(守护线程)。

 

/*
 守护线程(后台线程):如果一个进程中只剩下了守护线程,那么该守护线程也会死亡。
 需求:模拟QQ程序下载更新包。
 一个线程默认的时候都不是守护线程。
 */
public
class
Demo7 extends Thread {
 
    public Demo7(Stringname){
       super(name);
    }
    @Override
    public
void
run() {
       int
i = 0 ;
       while(true){
           System.out.println(this.getName()+"目前下载更新到
"+i+"%");
           if(i==100){
              System.out.println("下载完毕,准备安装更新包...");
              break;
           }
           i++;
           try {
              Thread.sleep(10);
           } catch (InterruptedExceptione) {
              e.printStackTrace();
           }
       }
    }
    public
static void
main(String[]
args) {
       Demo7 d = new Demo7("后台线程");
       d.setDaemon(true);  //把该线程设置成守护线程。
       //System.out.println("是守护线程吗?"+ d.isDaemon()); // isDaemon()判断一个线程是否为守护线程,如果是返回true,否则返回false.
       d.start();
       // 当main线程执行完毕的时候,那么后台线程也要死亡。
       for(inti = 0 ;i< 100;i++){
           System.out.println(Thread.currentThread().getName()+":"+i);
       }
    }
}

 

5.2 Thread的join方法

当A线程执行到了B线程Join方法时A就会等待,等B线程都执行完A才会执行,Join可以用来临时加入线程执行

/*
join方法。
需求:模拟打酱油.
 */
class Mon
extends
Thread{
    @Override
    public
void
run() {
       System.out.println("老妈洗菜..");
       System.out.println("老妈切菜..");
       System.out.println("老妈准备炒菜菜,发现没有酱油了..");
       //叫儿子打酱油
       Son son = new Son();
       son.start();
       try {
           son.join(); 
//join()线程让步,加入。 执行了join方法的线程会让步给加入的线程先完成任务,然后自己才会继续的工作。
       } catch (InterruptedExceptione) {
           e.printStackTrace();
       }
       System.out.println("老妈继续炒菜..");
       System.out.println("全家一起吃饭..");
    }
}
 
class Son
extends
Thread{
    @Override
    public
void
run() {
       try {
           System.out.println("儿子拿着钱下楼梯");
           Thread.sleep(1000);
           System.out.println("儿子一直往前走....");
           System.out.println("儿子卖到了酱油...");
           System.out.println("回来,上楼梯");
           Thread.sleep(1000);
           System.out.println("把酱油给老妈..");
       } catch (InterruptedExceptione) {
           e.printStackTrace();
       }
    }  
}
 
public
class
Demo8 {
    public
static void
main(String[]
args) {
       Mon mon = new Mon();
       mon.start();
    }
}

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  黑马程序员 线程