『黑马程序员』第三话<多线程>

来源:互联网 发布:淘宝怎么申请淘宝达人 编辑:程序博客网 时间:2024/05/21 19:21


『11.1』多线程概述

1.进程:是一个正在执行中的程序,每一个进程都有一个执行顺序(执行路径)(控制单元)

2.线程:就是进程中的一个独立的控制单元。线程控制进程执行。

『11.2』创建线程-继承Thread类

创建线程的第一种方法:继承Thread类

步骤:

1.定义类继承Thread

2.复写Thread类中的run方法,目的:将自定义代码存储在run方法中,让线程运行

3.调用线程的start方法,该方法有两个作用:启用线程,调用run方法。

public class D1 {


public static void main(String[] args)
{
Show s=new Show();
s.start(); //创建线程并调用
//s.run();  创建线程但不调用run
for(int x=0;x<100;x++)
System.out.println(x);
}
}
class Show extends Thread
{
public  void run()//run方法要复写掉Thread中的run方法
{
for(int x=0;x<100;x++)
System.out.println("show:"+x);
}
}

为什么要覆盖run方法呢?

因为Thread类中的run方法是public void run(){},如果不覆盖,start后运行就父类中的函数,没有任何指令。达不到要的目的。

『11.3』创建线程-run和start的特点

 d.start();//开启线程并执行该线程的run方法。   
             d.run();//仅仅是对象调用方法,而线程创建了,却并没有运行。

『11.4』练习

public class D2 {
public static void main(String[] args) 
{
Student s1=new Student("aaa");
Student s2=new Student("bbb");
s1.start();
s2.start();
}
}
class Student extends Thread
{
String name;  
Student(String name)
{
this.name=name;//这是给一个新变量name赋值
//super(name);//而super(name)是因为Thread(String name),这里的name指的是线程名
}
public void run()
{
for(int x=0;x<100;x++)
System.out.println((currentThread())+getName()+"........."+x);//这里的currentThread()和getName()都省略了this. Thread.
}
}
设置线程名称:setName或者构造函数。currentThread():获取当前线程对象

『11.5』线程运行状态

线程运行的五种状态:
(1)被创建
(2)运行
(3)阻塞(临时状态),具备资格但没有执行权
(4)冻结,放弃执行资格
(5)消亡
注:建立线程子类对象的同时线程也被建立

『11.6』获取线程对象和名称

1.两个属性:setName和getName
2.原来线程都有自己默认的名称,Thread-编号,该编号从0开始。

『11.7』售票的例子

class Ticket extends Thread
{
       private  int tick =100;
       public void run()
       {
              while(true)
              {
                     if(tick>0)
                     {
                            System.out.println(Thread.currentThread().getName()+"   sale  "+tick--);
                     }
              }
       }
}
class D3
{
       public static void main(String[] args)
       {
              Ticket t1 = new Ticket();//创建4个对象,就是有400张票,但要求只卖100张
              Ticket t2 = new Ticket();
              Ticket t3 = new Ticket();
               Ticket t4 = new Ticket();
              t1.start();
              t2.start();
             t3.start();
             t4.start();

/*Ticket t = new Ticket();------------------------------这方法才是共卖100张票

                Thread t1 = new Thread(t);  

                Thread t2 = new Thread(t);  

                Thread t3 = new Thread(t); 

                Thread t4 = new Thread(t);  

                             t1.start();    

                     t2.start();  

             t3.start();   

           t4.start()

*/

『11.8』创建线程-实现Runnable接口

   步骤:

1.定义实现Runnable接口

2.覆盖Runnable接口的run方法

3.通过Thread类来建立线程对象

4.将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。

5.调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

为什么要将Runnable接口的子类对象传递给Thread的构造函数?

因为自定义的run方法所属的对戏那个是Runnable接口的子类对象,所以要让线程去    控制指定对象的run方法。就必须明确该run方法所属的对象。 

2、实现方式和继承方式有什么不同?

实现方式的好处:避免了单继承的局限性;在定义线程时,建议使用实现方式。

3、两种方式的区别:

继承Thread:线程代码存放在Thread子类的run方法中。

实现Runnable:线程代码存放在接口的子类的run方法中。

『11.9』多线程的安全问题

class Ticket implements Runnable
{
       private int tick = 100;
     //  Object obj = new Object();
       public void run()
       {
              while(true)
              {
                 //    synchronized(obj)
                     {
                            if(tick>0)
                            {
                                   try{Thread.sleep(10);}catch(Exception e){}
                                   System.out.println(Thread.currentThread().getName()+"...sale:"+tick--);
                            }
                     }
              }
       }
}
class TicketDemo2
{
       public static void main(String[] args)
       {
              Ticket t = new Ticket();
              Thread t1 = new Thread(t);
              Thread t2 = new Thread(t);
              Thread t3 = new Thread(t);
              Thread t4 = new Thread(t);
 
              t1.start();
              t2.start();
              t3.start();
              t4.start();
       }
}

过分析,发现,打印出0,-1,-2等错票。多线程的运行出现了安全问题。

问题的原因:

       当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误。

解决办法:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行


『11.10』多线程的同步代码块

8、多线程中的同步代码块

Java对于多线程的安全问题提供了专业的解决方式,就是同步代码块。

synchronized(对象)

{

       需要被同步的代码

}

同步代码块:哪些代码需要同步,就看哪些语句在操作共享数据

对象如同锁,持有锁的线程可以在同步中执行。

没有持有锁的线程即使获取了cpu执行权,也进不去,因为没有获取锁。

同步的前提:

1.必须要有两个或者两个以上的线程。

2.必须是多个线程使用同一个锁。

必须保证同步中只能有一个线程在运行。

好处:解决了多线程的安全问题。

弊端:多个线程需要判断锁,较为消耗资源

『11.11』同步函数

在复写run()方法的函数里,程序不能抛,因为Thread类中没有该方法

需求:银行有一个金库,有两个储户分别存300元,每次存100,存三次。

目的:该程序是否有安全问题,如果有,如何解决?

如何找问题:

1.明确哪些代码是多线程运行代码。

2.明确共享数据。

3.明确多线程运行代码中哪些语句是操作共享数据的。

synchronized有两种表现形式1.同步代码块;2.同步函数。

class Bank
{
       private int sum;
       //Object obj = new Object();
       public synchronized void add(int n)//第二种表现形式是同步函数
       {
              //synchronized(obj)//第一种表现形式是同步代码块
              //{
                     sum+=n;
                     try{Thread.sleep(10);}catch(Exceptione){}
                     System.out.println("sum="+sum);
              //}
       }
}
class Cus implements Runnable
{
       private Bank b = new Bank();
       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 c1 = new Thread(c);
              Thread c2 = new Thread(c);
              c1.start();
              c2.start();
       }
}


『11.12』同步函数的锁是this

验证:使用两个线程来卖票,一个线程在同步代码块中,一个在同步函数中,都在执行卖票

class Ticket implements Runnable
{
       private int tick =100;
       //Object obj = new Object();
       boolean flag = true;
       public void run()
       {
              if(flag)
              {
                     while(true)
                     {
                            synchronized(this)
                            {
                                   if(tick>0)
                                   {
                                          try{Thread.sleep(10);}catch(Exception e){}
                                          System.out.println(Thread.currentThread().getName()+"....code: "+tick--);
                                   }
                            }
                     }
              }
              else
                     while(true)
                            show();
       }
       public synchronized void show()//this
       {
              if(tick>0)
                     {
                            try{Thread.sleep(10);}catch(Exception e){}
                            System.out.println(Thread.currentThread().getName()+"...show...:"+tick--);
                     }
       }
}
class ThisLockDemo
{
       public static void main(String[] args)
       {
              Ticket t = new Ticket();
              Thread t1 = new Thread(t);
              Thread t2 = new Thread(t);
 
              t1.start();
              try{Thread.sleep(10);}catch(Exception e){}
              t.flag =false;
              t2.start();
       }
}

『11.13』静态函数的锁是Class对象

通过验证,发现不再是this,因为静态方法中也不可以定义this。

静态进内存,内存中没有本类对象,但是一定有该类对应的字节码文件对象。

类名.class       该对象的类型是Class   synchronized(类名.class)

静态的同步方法,使用的锁是该方法所在类的字节码文件对象:类名.class


『11.14』单例设计模式

懒汉式用于延时加载,多线程加载的时候容易出现问题,可以用同步代码块解决问题,但效率稍低;而加上双重判断的时候则可以提高效率,加同步的时候,该类所属的字节码对象:类名.class

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;
       }
}

懒汉式和饿汉式有什么不同?

解答:懒汉式的特点在于实例的延时加载;懒汉式的延时加载有没有问题?有,多线程访问时会出现安全问题,可以加同步来解决,而用双重判断的方法可以解决效率问题;加同步的时候,使用的锁是哪个?该类所属的字节码对象


『11.15』死锁

class Test implements Runnable
{
       private boolean flag;
       Test(boolean flag)
       {
              this.flag = flag;
       }
       public void run()
       {
              if(flag)
              {
                     synchronized(MyLock.locka)
                     {
                            System.out.println("if locka");
                            synchronized(MyLock.lockb)
                            {
                                   System.out.println("iflockb");
                                  
                            }
                     }
              }
              else
              {
                     synchronized(MyLock.lockb)
                     {
                            System.out.println("elselockb");
                            synchronized(MyLock.locka)
                            {
                                   System.out.println("elselocka");
                                  
                            }
                     }
              }
       }
}
 
class MyLock
{
       static Object locka = new Object();
       static Object lockb = new Object();
}
class DeadLockTest
{
       public static void main(String[] args)
       {
              Thread t1 = new Thread(new Test(true));
              Thread t2 = new Thread(new Test(false));
              t1.start();
              t2.start();
       }
}

『12.1』线程间通讯信

指的是,多个线程在操作同一个资源,但是操作的动作不同。

wait();

notify();

nitifyAll();

都是要对有锁的线程操作,所以要在同步中使用

为什么以上方法都是Objectj里的方法?

因为这些方法在操作同步中线程时,都必须要标识它们锁操作线程持有的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。不可以对不同锁中的线程进行唤醒。

也就是说:等待和唤醒必须是同一个锁

而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中

『12.2』线程间通讯-解决安全问题

『12.3』线程间通讯-等待唤醒机制

class Res
{
       String name;
       String sex;
       boolean flag = false;
}
class Input implements Runnable
{
       private Res r;
       //Object obj = new Object();
       Input(Resr)
       {
              this.r = r;
       }
       public void run()
       {
              int x=0;
              while(true)
              {
                     synchronized(r)
                     {
                            if(r.flag)
                                   try{r.wait();}catch(Exceptione){}
                            if(x==0)
                            {
                                   r.name= "mike";
                                   r.sex= "man";
                            }
                            else
                            {
                                   r.name= "丽丽";
                                   r.sex= "女女女女女女";
                            }
                            x = (x+1)%2;
                            r.flag = true;
                            r.notify();
                     }
              }
       }
}
 
class Output implements Runnable
{
       private Res r;
       //Object obj = new Object();
       Output(Resr)
       {
              this.r = r;
       }
       public void run()
       {
              while(true)
              {
                     synchronized(r)
                     {
                            if(!r.flag)
                                   try{r.wait();}catch(Exceptione){}
                            System.out.println(r.name+"......"+r.sex);
                            r.flag = false;
                            r.notify();
                     }
              }
       }
}
 
class InputOutputDemo
{
       public static void main(String[] args)
       {
              Res r = new Res();
 
              Input in = new Input(r);
              Output out = new Output(r);
 
              Thread t1 = new Thread(in);
              Thread t2 = new Thread(out);
 
              t1.start();
              t2.start();
       }
}

『12.4』线程间通信-代码优化

class Res
{
       private String name;
       private String sex;
       private boolean flag = false;
      
       public synchronized void set(String name,String sex)//
同步函数,让锁变得唯一,其中这里的锁是this
       {
              if(flag)
                     try{this.wait();}catch(Exceptione){}
              this.name = name;
              this.sex = sex;
              flag = true;
              this.notify();
       }
       public synchronized void out()
       {
              if(!flag)
                     try{this.wait();}catch(Exceptione){}
              System.out.println(name+"......"+sex);
              flag = false;
              this.notify();
       }
}
class Input implements Runnable
{
       private Res r;
       Input(Resr)
       {
              this.r = r;
       }
       public void run()
       {
              int x=0;
              while(true)
              {
                     if(x==0)
                            r.set("mike","man");
                     else
                            r.set("丽丽","女女女女女女");
                     x= (x+1)%2;
              }
       }
}
 
class Output implements Runnable
{
       private Res r;
       Output(Resr)
       {
              this.r = r;
       }
       public void run()
       {
              while(true)
              {
                     r.out();
              }
       }
}
 
class InputOutputDemo2
{
       public static void main(String[] args)
       {
              Res r = new Res();
              new Thread(new Input(r)).start();
              new Thread(new Output(r)).start();
       }
}

『12.5』线程间通信-生产者消费者

当生产者与消费者均有多个的时候,需要循环判断flag标记,

因此这里就需要将if换成while语句进行循环判断;

而为了防止多个线程出现失去执行权的时候,这里就需要将全部线程唤醒,用notifyAll();

class ProducerConsumerDemo 
{
       public static void main(String[] args)
       {
              Resource res = new Resource();
              Producer pro = new Producer(res);
              Consumer con = new Consumer(res);
 
              Thread t1 = new Thread(pro);
              Thread t2 = new Thread(pro);
              Thread t3 = new Thread(con);
              Thread t4 = new Thread(con);
 
              t1.start();
              t2.start();
              t3.start();
              t4.start();
       }
}
 
class Resource
{
       private String name;
       private int count = 1;
       private boolean flag = false;
       public synchronized void set(String name)
       {
              while(flag)//while循环可进行多次判断
                     try{this.wait();}catch(Exceptione){}
              this.name = name+"---"+count++;
              System.out.println(Thread.currentThread().getName()+".....生产者....."+this.name);
              flag = true;
              this.notifyAll();//既唤醒本方又唤醒对方
       }
 
       public synchronized void out()
       {
              while(!flag)
                     try{this.wait();}catch(Exceptione){}
              System.out.println(Thread.currentThread().getName()+"........消费者........."+this.name);
              flag = false;
              this.notifyAll();
       }
}
 
class Producer implements Runnable
{
       private Resource res;
       Producer(Resourceres)
       {
              this.res = res;
       }
       public void run()
       {
              while(true)
              {
                     res.set("商品");
              }
       }
}
 
class Consumer implements Runnable
{
       private Resource res;
       Consumer(Resourceres)
       {
              this.res = res;
       }
       public void run()
       {
              while(true)
              {
                     res.out();
              }
       }
}

当生产者与消费者均有多个的时候,需要循环判断flag标记,因此这里就需要将if换成while语句进行循环判断;而为了防止多个线程出现失去执行权的时候,这里就需要将全部线程唤醒,用notifyAll();

对于多个生产者和消费者,为什么要定义while判断标记呢?

原因:让被唤醒的线程再一次判断标记。

为什么定义notifyAll?

因为需要唤醒对方线程,因为只用notify,容易出现只唤醒奔放线程的情况,导致程序中的所有线程都等待。

 

Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,

以便通过将这些对象与任意Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。

其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法

的使用。 Lock替代synchronized,Condition替代Object


『12.6』线程间通信-生产者消费者JDK5.0升级版

将同步synchronized替换成现实lock操作。

将Object中的wait,notify,notifyAll替换成了Condition对象。

该对象可以通过Lock锁进行获取。

该示例中,实现了本方只唤醒对方的操作。

import java.util.concurrent.locks.*;//记得导入相应的包文件
class ProducerConsumerDemo2
{
       public static void main(String[] args)
       {
              Resource res = new Resource();
 
              Producer pro = new Producer(res);
              Consumer con = new Consumer(res);
 
              Thread t1 = new Thread(pro);
              Thread t2 = new Thread(pro);
              Thread t3 = new Thread(con);
              Thread t4 = new Thread(con);
 
              t1.start();
              t2.start();
              t3.start();
              t4.start();
       }
}
class Resource
{
       private String name;
       private int count = 1;
       private boolean flag = false;
       private Lock lock = new ReentrantLock();
       private Condition condition_pro = lock.newCondition();
       private Condition condition_con = lock.newCondition();
       public  void set(String name)throws InterruptedException
       {
              lock.lock();
              try
              {
                     while(flag)
                            condition_pro.await();//await()会出现异常,需要在函数上抛出
                     this.name= name+"---"+count++;
                     System.out.println(Thread.currentThread().getName()+".....生产者....."+this.name);
                     flag= true;
                     condition_con.signal();
              }
              finally
              {
                     lock.unlock();//释放锁的动作一定要执行。
              }
       }
 
       public  void out()throws InterruptedException
       {
              lock.lock();
              try
              {
                     while(!flag)
                            condition_con.await();
                     System.out.println(Thread.currentThread().getName()+"........消费者........."+this.name);
                     flag= false;   
                     condition_pro.signal();
              }
              finally
              {
                     lock.unlock();
              }
             
       }
}
class Producer implements Runnable
{
       private Resource res;
       Producer(Resourceres)
       {
              this.res = res;
       }
       public void run()
       {
              while(true)
              {
                     try
                     {
                            res.set("商品");
                     }
                     catch(InterruptedException e)
                     {
                     }
              }
       }
}
 
class Consumer implements Runnable
{
       private Resource res;
       Consumer(Resourceres)
       {
              this.res = res;
       }
       public void run()
       {
              while(true)
              {
                     try
                     {
                            res.out();
                     }
                     catch(InterruptedException e)
                     {
                     }
              }
       }
}

『12.7』停止线程

1.定义循环结束标记

       因为线程运行代码一般都是循环,只要控制了循环即可。

2.使用interrupt(中断)方法。

       该方法是结束线程的冻结状态,是线程回到运行状态中来。

注意:stop方法已经过时不再使用。

3.setDaemon :将该线程标记为守护线程或用户线程。

特点:该方法必须在启动线程钱调用;当正在运行的线程都是守护线程时,Java虚拟机退出。

4.stop方法已经过时,如何停止线程?

只有一种,run方法结束。开启多线程运行,运行代码通常是循环结构。

只要控制住循环体,就可以让run方法结束,也就是线程结束。

特殊情况:

当线程处于了冻结状态。就不会读取到标记,那么线程就不会结束。

当没有指定的方式让冻结的线程回复到运行状态时,这时就需要对冻结进行清除。强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。

Thread类提供了该方法:interrupt();

class StopThread implements Runnable
{
       private boolean flag = true;
       public synchronized void run()
       {
              while(flag)
              {
                     try
                     {
                            wait();
                     }
                     catch(InterruptedException e)
                     {
                            System.out.println(Thread.currentThread().getName()+"....Exception");
                            flag = false;
                     }
                     System.out.println(Thread.currentThread().getName()+"....run");
              }
       }
       public void changeFlag()
       {
              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.setDaemon(true);
              t2.setDaemon(true);
              t1.start();
              t2.start();
 
              int num=0;
              while(true)
              {
                     if(num++==60)
                     {
                            //st.changeFlag();
                            //t1.interrupt();
                            //t2.interrupt();
                            break;
                     }
                     System.out.println(Thread.currentThread().getName()+"...main...."+num);
              }
              System.out.println("over");
       }
}
『12.8』守护线程

1、关键字: void Daemon(boolean on)     将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java虚拟机退出。且该方法必须在线程启动前调用

守护线程属于“后台线程”,随着前台线程的退出而退出。

 『12.9』join方法

join:等待该线程终止。

当A线程执行到了B线程的.join()方法是,A就会等待,等B线程都执行完,A才会执行。

join可以用来临时加入线程执行。

class Demo implements Runnable
{
       public void run()
       {
              for(int x=0; x<60; x++)
              {
                     System.out.println(Thread.currentThread().getName()+"...."+x);
              }
       }
}
class JoinDemo
{
       public static void main(String[] args) throws Exception
       {
              Demo d = new Demo();
              Thread t1 = new Thread(d);
              Thread t2 = new Thread(d);
 
              t1.start();
              t2.start();
              t1.join();//让t1申请线程,只有当t1线程执行完后,才会执行其余的。
              /*
              当t1.join();在t1.start();下面一行时,t1线程会首先执行完后,t2和主线程就会交替执行完;
              当t1.join();在t2.start();下面时,t1会与t2交替执行,直到t1线程执行完,没有执行完的t2线程就会与主线程交替执行。

              */
              for(int x=0;x<70;x++)
              {
                     System.out.println(Thread.currentThread().getName()+"....main");
              }
              System.out.println("over");
       }
}



『12.10』优先级&yield方法

toString()、yield、setPriority

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

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

3.void setPriority(int newPriority) :   更改线程的优先级,其中,所有线程的默认优先级是5

数据固定的用常量(所有字母大写),数据共享有static静态,全局常量使用格式publicstatic final int MAX_PRIORITY


0 0