黑马程序员---多线程

--------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IO开发S</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------

7、多线程

7.1进程

进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。

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

一个进程中至少有一个线程。

JVM启动的时候会有一个进程java.exe.该进程中至少一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。
扩展:其实更细节说明JVM,JVM启动不止一个线程,还有负责垃圾回收机制的线程。

7.2线程的状态及名称


原来线程都有自己默认的名称。
      Thread-编号 该编号从0开始。
static Thread currentThread():获取当前线程对象。
getName(): 获取线程名称。
设置线程名称:setName或者构造函数。

代码示例:

class Test extends Thread
{
        Test(String name)
        {
               super(name);
        }
        public void run()
        {
               for(int x=0; x<60; x++)
               //局部变量在每一个线程区域当中都有独立的一份
               {       //获取当前运行线程的名称
               //(Thread.currentThread()==this)
               System.out.println(+"..."+this.getName()+" run..."+x);
               }
        }
}
class ThreadTest 
{
        public static void main(String[] args) 
        {
               Test t1 = new Test("one---");
               Test t2 = new Test("two+++");
               t1.start();//t1线程开启,并调用run方法
               t2.start();//t2线程开启,并调用run方法

7.3线程的创建方式

如何在自定义的代码中,自定义一个线程呢?

通过对api的查找,java已经提供了对线程这类事物的描述。就Thread类。

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

1.  定义类继承Thread。

2.  子类覆盖父类中的run方法,将线程运行的代码存放在run中

3.  建立子类对象的同时,线程也被创建

4.  通过调用start方法开启线程(该方法两个作用:启动线程,调用run方法。)

a)  发现运行结果每一次都不同。因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。

b)  明确:在某一个时刻,只能有一个程序在运行。(多核除外)

c)  cpu在做着快速的切换,以达到看上去是同时运行的效果。

d)  我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。

e)  这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。

f)  为什么要覆盖run方法呢?

       i.     Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。

     ii.     也就是说Thread类中的run方法,用于存储线程要运行的代码。

创建线程方式之一示例代码:  
 class Demo extends Thread
{
    public void run()
    {
            for(int x=0; x<60; x++)
                   System.out.println("demo run----"+x);
    }
}
Demo d = new Demo();//创建好一个线程。
    d.start();//开启线程并执行该线程的run方法。(面试题)
    d.run();//仅仅是对象调用方法。而线程创建,并没有运行。

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

1. 定义类实现Runnable接口(最为常用

2. 子类覆盖接口中的run方法,将线程要运行的代码存放在该run方法中

3. 通过Thread类创建线程对象

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

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

思考:为什么要给Thread类的构造函数传递Runnable的子类对象?

原因:      因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去指定对象的run方法。就必须明确该run方法所属对象。
创建线程方式之二示例代码
class Ticket implements Runnable//1、实现Runnable接口
{
        private  int tick = 100;//资源被独立共享,只有100张
        public void run()//2、覆盖接口中的run方法
        {
               while(true)//3、运行代码放进run方法中
               {
                       if(tick>0)
                       {
System.out.println(Thread.currentThread().getName()+"sale : "+ tick--);
                       }
               }
        }
}
class  TicketDemo
{
        public static void main(String[] args) 
        {
//4、创建实现了Runnable接口的子类对象
               Ticket t = new Ticket();
//5、使用Thread类创建对象,将子类对象传入Thread类的构造函数
               Thread t1 = new Thread(t);//创建了一个线程;
               Thread t2 = new Thread(t);//创建了一个线程;
               Thread t3 = new Thread(t);//创建了一个线程;
               Thread t4 = new Thread(t);//创建了一个线程;
//6、通过Thread类的对象,调用start方法,开启线程
               t1.start();
               t2.start();
               t3.start();
               t4.start();

7.3.3两种创建线程的方式之实现方式和继承方式的区别

两种创建线程的方式之实现方式和继承方式的区别(面试题)

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

2、继承Thread:线程代码存放Thread子类run方法中。而实现Runnable,线程代码存在接口的子类的run方法。

7.4线程安全问题(必须考虑)

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

解决办法:对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。Java对于多线程的安全问题提供了专业的解决方式:就是同步代码块。

多个线程访问出现延迟,线程随机性

注意:线程安全问题在理想状态下,不容易出现,但是一旦出现对软件的影响是非常大的。

7.5同步(synchronized)

同步代码块格式:

synchronized(对象)

{

    需要同步的代码;

}

同步的好处:同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。

同步的前提: (必须保证同步中只能有一个线程在运行)

1、同步需要两个或者两个以上的线程

2、多个线程使用的是同一个锁

不满足这两个条件,不能称其为同步。

同步的弊端:

当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序运行的效率。

同步示例程序:

需求:银行有两个储户分别存300,每次存100,存3次。

目的:该程序是否有安全问题,如果有,如何解决?
如何找问题:
1,明确哪些代码是多线程运行代码。
2,明确共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。

示例代码:

class Bank
{
      private int sum;
      //Object obj = new Object();
      public synchronized void add(int n)//同步函数
      {
              //synchronized(obj)//同步代码块
              //{
                     sum = sum + n;
                     try{Thread.sleep(10);}catch(Exception e){}
                     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 t1 = new Thread(c);
              Thread t2 = new Thread(c);
              t1.start();
              t2.start();
      }
}

同步函数

格式:在函数上加上synchronized修饰符即可。

同步函数用的是this

如果同步函数被静态修饰后,使用的锁是什么呢?

通过验证,发现不再是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象(类名.class)该对象的类型是Class

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

同步代码块使用的是对象锁。

代码示例

class Ticket implements Runnable//实现多线程的定义方式
{
        private static int tick = 100;
        //Object obj = new Object();//为同步代码块定义的对象
        boolean flag = true;//定义开关
        public  void run()
        {
               if(flag)
               {
                       while(true)
                       {
                               synchronized(Ticket.class)//静态函数锁
                               {
                                      if(tick>0)
                                      {
        try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"code : "+ tick--);                              }
                               }
                       }
               }
               else
                       while(true)
                               show();//调用静态函数
        }
        public static synchronized void show()
        {
               if(tick>0)
               {
                       try{Thread.sleep(10);}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"show: "+ tick--);
               }
        }
}
class  StaticMethodDemo
{
        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();
        }
}

7.6单例设计模式的多线程应用

示例代码:

//饿汉式。
class Single
{
        private static final Single s = new Single();
        private Single(){}
        public static Single getInstance()
        {
               return s;
        }
}
//懒汉式
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、   懒汉式延迟加载有没有问题?

有,如果多线程访问时会出现安全问题

3、   怎么解决?

可以加同步来解决,加同步代码块和同步函数都行,但是稍微有些低效,用这个双重否定的形式能够解决这个效率问题。

4、   加同步的时候使用的是什么锁?

该类所属的字节码文件对象

5、   请写一个延迟加载的单例设计模式示例?

代码如上。

7.7死锁的避免

死锁示例程序

class Test implements Runnable
{
        private boolean flag;
        Test(boolean flag)
        {
               this.flag = flag;
        }
        public void run()
        {
               if(flag)
               {
                       while(true)
                       {
                               synchronized(MyLock.locka)
                               {
System.out.println(Thread.currentThread().getName()+"...iflocka ");
                                      synchronized(MyLock.lockb)
                                      {
System.out.println(Thread.currentThread().getName()+"..iflockb");                            
                                      }
                               }
                       }
               }
               else
               {
                       while(true)
                       {
                               synchronized(MyLock.lockb)
                               {
System.out.println(Thread.currentThread().getName()+"..elselockb");
                                      synchronized(MyLock.locka)
                                      {
System.out.println(Thread.currentThread().getName()+"...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();
        }
}

7.8线程间通信

7.8.1线程间通信

wait(),notify(),notifyAll(),都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。为什么这些操作线程的方法要定义在Object类中呢?

因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。

示例程序

class Res//定义的资源类
{
        private String name;
        private String sex;
        private boolean flag = false;//定义标识
        public synchronized void set(String name,String sex)
        {
               if(flag)//设置存入线程等待
                       try{this.wait();}catch(Exception e){}
               this.name = name;
               this.sex = sex;
               flag = true;//改变标识
               this.notify();//设置唤醒等待的线程
        }
        public synchronized void out()
        {
               if(!flag) //设置取出线程等待
                       try{this.wait();}catch(Exception e){}
               System.out.println(name+"........"+sex);
               flag = false;
               this.notify();
        }
}
class Input implements Runnable
{
        private Res r ;
        Input(Res r)
        {
               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(Res r)
        {
               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();
               /*以下的简化为上
               Input in = new Input(r);
               Output out = new Output(r);
               Thread t1 = new Thread(in);
               Thread t2 = new Thread(out);
               t1.start();
               t2.start();
               */
        }
}

7.8.2对于多个生产者和消费者。

为什么要定义while判断标记。原因:让被唤醒的线程再一次判断标记。

为什么定义notifyAll,因为需要唤醒对方线程。

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

示例代码:

class ProducerConsumerDemo 
{
        public static void main(String[] args) 
        {
               Resource r = new Resource();
               Producer pro = new Producer(r);
               Consumer con = new Consumer(r);
               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)
                       try{this.wait();}catch(Exception e){}         this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"生产者"+this.name);
               flag = true;
               this.notifyAll();
        }
public synchronized void out()
        {
               while(!flag)
                       try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"消费者"+this.name);
               flag = false;
              this.notifyAll();//唤醒所有线程,主要是为了唤醒对方线程
        }
}
class Producer implements Runnable
{
        private Resource res;
        Producer(Resource res)
        {
               this.res = res;
        }
        public void run()
        {
               while(true)
               {
                       res.set("+商品+");
               }
        }
}
class Consumer implements Runnable
{
        private Resource res;
        Consumer(Resource res)
        {
               this.res = res;
        }
        public void run()
        {
               while(true)
               {
                       res.out();
               }
        }
}
1、    问:生产者和消费者有什么替代方案?
JDK版本以后它提供了显式的锁机制,以及显式的锁对象上的等待唤醒操作机制,同时它把等待唤醒机制封装,一个锁可以对应多个condition对象。

7.8.3JDK1.5 中对多线程升级的解决方案

JDK1.5 中提供了多线程升级解决方案。将同步Synchronized替换成显式Lock操作。将Object中的wait,notify,notifyAll,替换了Condition对象。该对象可以Lock锁 进行获取。该示例中,实现了本方只唤醒对方操作。
Lock:替代了Synchronized
      lock 
      unlock
      newCondition()
Condition:替代了Object wait notify notifyAll
      await();
      signal();
      signalAll();

示例代码:

import java.util.concurrent.locks.*;
class ProducerConsumerDemo2 
{
        public static void main(String[] args) 
        {
               Resource r = new Resource();
               Producer pro = new Producer(r);
               Consumer con = new Consumer(r);
               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();
                       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(Resource res)
        {
               this.res = res;
        }
        public void run()
        {
               while(true)
               {
                       try
                       {
                               res.set("+商品+");
                       }
                       catch (InterruptedException e)
                       {
                       }
               }
        }
}
class Consumer implements Runnable
{
        private Resource res;
        Consumer(Resource res)
        {
               this.res = res;
        }
        public void run()
        {
               while(true)
               {
                       try
                       {
                               res.out();
                       }
                       catch (InterruptedException e)
                       {
                       }
               }
        }
}

7.9停止线程

如何停止线程?只有一种,run方法结束。开启多线程运行,运行代码通常是循环结构。只要控制住循环,就可以让run方法结束,也就是线程结束。

特殊情况:当线程处于了冻结状态。就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态是,这时需要对冻结进行清除。强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。Thread类提供该方法 interrupt();

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

示例代码:

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();
               t1.join();//主线程等待t1结束,此时只有t1运行
               t2.start();
               t1.join();//主线程等待t1结束,此时有t1、t2交替运行
               int num = 0;
               while(true)
               {
                       if(num++ == 60)
                       {
                               //st.changeFlag();
                               t1.interrupt();//强制线程中断
                               t2.interrupt();
                               break;
                       }
System.out.println(Thread.currentThread().getName()+"......."+num);
               }
               System.out.println("over");
        }
}

思考:wait(),sleep()有什么区别?

wait():释放CPU执行权,释放锁

sleep():释放CPU执行权,不释放锁。

7.10线程类的其他方法

7.10.1、void setDaemon(booleanb):

将该线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,java虚拟机退出,该方法必须在启动线程前调用。简单理解为后台线程,主线程结束,他们自动结束。

7.10.2、join():

等待该线程终止。当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行。join可以用来临时加入线程执行。join也会抛出中断异常,当A线程强行中断时发生。

7.10.3、toString():

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

7.10.4、setPriority(int newPriority):

更改线程的优先级。所有的线程包括主线程的优先级默认是5(范围是1~10,其中1、5、10是最明显的优先级,其对应的字段名称为:MIN_PRIORITY、NORM_PRIORITY(默认)、MAX_PRIORITY)。

7.10.5、yield():

暂停当前正在执行的线程对象,并执行其他线程。

7.11多线程的应用

当某些代码需要同时被执行时,就用单独的线程进行封装。

程序封装多线程的应用示例代码:

class ThreadTest 
{
        public static void main(String[] args) 
        {

7.11.1匿名内部类使用Thread类实现封装线程      

方式一:匿名内部类使用Thread类实现封装线程          
               new Thread()
               {
                       public void run()
                       {
                               for(int x=0; x<100; x++)
                               {
System.out.println(Thread.currentThread().getName()+"....."+x);
                               }
                       }
               }.start();//匿名内部类使用Thread类实现封装线程

7.11.2主线程       

方式二:主线程         
               for(int x=0; x<100; x++)
               {
System.out.println(Thread.currentThread().getName()+"....."+x);
               }

7.11.3匿名内部类使用Runnable类实现封装线程        

方式三:匿名内部类使用Runnable类实现封装线程        
               Runnable r  = new Runnable()
               {
                       public void run()
                       {
                               for(int x=0; x<100; x++)
                               {
System.out.println(Thread.currentThread().getName()+"....."+x);
                               }
                       }
               };
        new Thread(r).start();//匿名内部类使用Runnable封装线程

7.11.4使用单独的类之继承Thread类

方式四:使用单独的类之继承Thread
               //new Test1().start();
        }
}
/*
class Test1 extends Thread//也可以实现Runnable接口
{
        public void run()
        {
               for(int x=0; x<100; x++)
               {
System.out.println(Thread.currentThread().getName()+"....."+x);
               }
        }
}

7.11.5使用单独的类之实现Runnable接口

方式五:使用单独的类之实现Runnable接口
class StopThread implements Runnable
{
        public void run()
        {
               for(int x=0; x<100; x++)
               {
System.out.println(Thread.currentThread().getName()+"....."+x);
               }
        }
}
 


--------------------- <a href="http://edu.csdn.net"target="blank">ASP.Net+Android+IO开发S</a>、<a href="http://edu.csdn.net"target="blank">.Net培训</a>、期待与您交流! ----------------------

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值