黑马程序员-java基础之多线程

本文详细介绍了Java中多线程的基本概念、创建方法及其状态,包括进程与线程的区别、多线程的创建方式、线程的状态转换等内容,并通过具体示例讲解了线程安全问题与解决方案。

--------------------- android培训java培训、java学习型技术博客、期待与您交流! -------------------

多线程

一概述

1进程:正在进行中的程序。其实进程就是一个应用程序运行时的内存分配空间。

2线程:其实就是进程中一个程序执行控制单元,一条执行路径。进程负责的是应用程序的空间的标示。线程负责的是应用程序的执行顺序。

3一个进程至少有一个线程在运行,当一个进程中出现多个线程时,就称这个应用程序是多线程应用程序,每个线程在栈区中都有自己的执行空间,自己的方法区、自己的变量。

Jvm就是一个多线程程序,在启动时,首先有一个主线程,负责程序的执行,调用的是main函数。主线程执行的代码都在main方法中。当产生垃圾时,收垃圾的动作,是不需要主线程来完成,因为这样,会出现主线程中的代码执行会停止,效率较低,所以由另外单独一个线程来负责垃圾回收。

3多线程的利弊

       好处

       多线程的存在解决了部分代码同时运行的问题,比如在网络编程中当多个客服端连接到服务端时,服务端需要同时处理这些客服端,不能一个处理完再处理另一个,这时利用多线程就可以进行同时处理

       弊端

线程太多会导致效率降低,因为多线程的执行依靠的cpu的来回切换。

4cpu进行线程处理的特点

       随机性:cpu处理哪个线程是随机的,毫无规律可言,哪个线程被CPU执行或者说抢到cpu执行权,哪个线程就可以执行,执行一段时间后,被另外一个线程抢去了执行权又会去执行另外一个线程。

二多线程的创建

       多线程创建有两种方式,查阅API可知,在java中用Thread类来描述线程,因此第一种创建方式就是继承Thread类,通过复写Tread类中的run方法将需要多线程执行的代码放入run方法中,之所以需要将代码放入run方法中,是因为在Tread类中,描述线程时,就定义了一个方法,用于存放线程执行的代码,该方法就是run方法,所以要建立线程时需要将线程执行代码放入run方法中。

多线程创建步骤

       1定义类继承Thread类成为Thread子类

       2复写run方法,将需要多线程执行的代码放入run方法

       3创建Thread子类,这时已经创建了一个线程

       4调用start方法开启线程

代码示例

 

/*

创建两线程,和主线程交替运行。

*/ 

 

.//创建线程Test 

class Test extends Thread 

//  private String name; 

    Test(String name) 

    { 

        super(name); 

//      this.name=name; 

    } 

    //复写run方法 

    public void run() 

    { 

        for(int x=0;x<60;x++) 

        System.out.println(Thread.currentThread().getName()+"..run..."+x); 

//      System.out.println(this.getName()+"..run..."+x); 

    } 

 

class  ThreadTest 

    public static void main(String[] args)  

    { 

       new Test("one+++").start();//开启一个线程 

 

        new Test("tow———").start();//开启第二线程 

 

        //主线程执行的代码 

        for(int x=0;x<170;x++) 

        System.out.println("Hello World!"); 

    } 

}

因为cpu的随机性,执行结果会是交替执行的

第二种创建方式:

因为继承的局限性,当该类继承了其他类时就无法再继承Tread类,所以Thread中提供了第二种方式,实现Runtable接口。通过复写接口中的run方法,在将该类在Thread构造时传入。

步骤:

       1实现Runable接口,复写run方法。实现Runable是因为Tread类在创建时可以传入一个Runable对象,在传入Runable后,start方法调用的是Runable对象中的run方法,所以这时只要复写Runable中的run方法即可。

       2创建该类对象。

       3创建Thread对象,将Runable子类对象作为参数传入Thread对象构造函数

       4Tread对象调用start方法

代码示例

/*

需求:简单的卖票程序。

多个窗口卖票。

*/ 

class Ticket implements Runnable//extends Thread 

    private  int tick = 100; 

    public void run() 

    { 

        while(true) 

        { 

            if(tick>0) 

            { 

                //显示线程名及余票数 

                System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--); 

            } 

        } 

    } 

}  

 

 

class  TicketDemo 

{

   public static void main(String[] args)  

    { 

        //创建Runnable接口子类的实例对象 

        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(); 

    } 

}  

注意:

       run方法中存放的只是线程执行的代码,只有当start方法被调用时,start方法去调用run方法,线程才会去执行。

两种方式的区别

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

       实现Runable:线程代码存放在Runable子类的run方法中

因为实现Runable方法避免了继承的局限性,因此实际开发中多用第二种方法创建线程。

三线程的几种状态

       新建:new一个Thread对象或者其子类对象就是创建一个线程,当一个线程对象被创建,但是没有开启,这个时候,

            只是对象线程对象开辟了内存空间和初始化数据。           

       准备运行:新建的对象调用start方法,就开启了线程,线程就到了准备运行状态。

            在这个状态的线程对象,具有执行资格,没有执行权。

       运行:当线程对象获取到了CPU的资源。

            在这个状态的线程对象,既有执行资格,也有执行权。

       冻结:运行过程中的线程由于某些原因(比如wait,sleep),释放了执行资格和执行权。

         当冻结状态解除时,线程就回到了准备运行状态。

死亡:当线程对象调用的run方法结束,或者直接调用stop方法,就让线程对象死亡,在内存中变成垃圾      

注意:在线程已经开启时在调用start方法,会产生线程状态异常。

图解

停止线程

stop()方法现已过时,

停止线程的方法就只有一种,就是run方法结束。如何让run方法结束呢?

             开启多线程运行,运行代码通常是循环体,只要控制住循环,就可以让run方法结束,也就是结束线程。特殊情况:当线程属于冻结状态,就不会读取循环控制标记,则线程就不会结束。

             为解决该特殊情况,可引入Thread类中的Interrupt方法结束线程的冻结状态;

当没有指定的方式让冻结线程恢复到运行状态时,需要对冻结进行清除,强制让线程恢复到运行状态。

sleep()wait()的区别:

        这两个方法来自不同的类,sleep()Thread类中的特有方法,而wait()继承自Object类。

         sleep()释放资源不释放锁,

 wait()释放资源释放锁;

线程安全问题

       当程序的多条语句在操作线程共享数据时(如买票例子中的票就是共享资源),由于线程的随机性导致一个线程对多条语句,执行了一部分还没执行完,另一个线程抢夺到cpu执行权参与进来执行,此时就导致共享数据发生错误。比如买票例子中打印重票和错票的情况

代码示例

class Ticket implements Runnable//extends Thread 

    private  int tick = 100; 

    public void run() 

    { 

        while(true) 

        { 

            if(tick>0) 

            { 

                           

                 try{Thread.sleep(500);}

                           case(InterruptedException e){e.printStackTrace() }

//显示线程名及余票数 

                System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--); 

            } 

        } 

    } 

 

 

class  TicketDemo 

{

   public static void main(String[] args)  

    { 

        //创建Runnable接口子类的实例对象 

        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号票  

四同步

线程安全一般情况下不容易出现,但一旦出现后果就是十分严重的。

解决办法-同步

将多个线程操作的代码进行同步,只有当一个线程执行完后,其他线程才可以执行同步中的代码。

锁:锁的作用是保证线程同步,解决线程安全问题。

       持有锁的线程可以在同步中执行,没有锁的线程即使获得cpu执行权,也进不去

同步的前提:

       必须有多个线程进行操作

       必须多个线程使用同一个锁

同步代码块

格式:

       synchronized(对象)

{

    需要同步的代码

}

    同步代码块中传入的对象参数就是同步代码块的锁

    代码示例

    Class

    同步函数:用synchronized修饰的函数就是同步函数

    代码示例

    根据同步的前提,证明同步中所用的锁,就是本类对象。

    静态同步函数:

        当同步函数被静态修饰后,因为静态早与对象加载,所以同步函数使用的锁不可能还是本类对象,通过代码验证,静态同步函数所用的锁为字节码对象-类名.class

    死锁:

    当同步中嵌套同步时,程序可能会发生死锁问题,程序将会卡住,既不继续往下运行,也不停止运行。在开发中要尽量避免同步问题的发生。

    死锁代码示例

class LockTest implements Runnable 

//定义个标记

    private boolean flag; 

//定义两个锁

  Object locka=new Object();

Object locka=new Object();

    LockTest(boolean flag) 

   

        this.flag=flag; 

   

    public void run() 

   

        if(flag) 

      

           while(true) 

          

               synchronized(Locka)//a锁 

              

                   System.out.println(Thread.currentThread().getName()+"------if_locka"); 

  

                   synchronized(Lockb)//b锁 

                  

                   System.out.println(Thread.currentThread().getName()+"------if_lockb"); 

                   

                } 

          

       

        else 

      

            while(true) 

         

              synchronized(Lockb)//b锁 

             

                System.out.println(Thread.currentThread().getName()+"------else_lockb"); 

 

                  synchronized(Locka)//a锁 

               

                 System.out.println(Thread.currentThread().getName()+"------else_locka"); 

                 

            

         

    

 

class SiLockDemo

{

    public static void main(String[] args)

    {

        //创建两个线程并启动

    New Thread(new LockTest(true)).start();

New Thread(new LockTest(false)).start();

    }

}

同步的利弊:

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

    弊端:减低了程序效率。 

如何寻找程序中需要被同步的代码

1明确被多线程运行的代码

2明确共享数据

3明确多线程运行代码中操作共享数据的代码

线程间通信:

其实就是多个线程在操作同一个资源,但是操作的动作却不一样。

代码示例

 

/*

    有一个资源

一个线程如果里边没有就往里存东西。

一个线程如果里面有就往里取东西。

*/ 

 

//资源类

class Resource 

    private String name; 

    private String sex; 

    private boolean flag=false; 

     

   public synchronized void setInput(String name,String sex) 

   

        while(flag) 

       

            try{wait();}catch(Exception e){}//如果有资源时,等待资源取出 

       

        this.name=name; 

        this.sex=sex; 

 

        flag=true;//表示有资源 

        notify();//唤醒等待 

   

 

   public synchronized void getOutput() 

    {        

        while(!flag) 

       

            try{wait();}catch(Exception e){}//如果木有资源,等待存入资源 

       

        System.out.println("name="+name+"---sex="+sex);//这里用打印表示取出 

                 

        flag=false;//资源已取出 

        notify();//唤醒等待 

   

 

//生产线程 

class Input implements Runnable 

    private Resource r; 

    Input(Resource r) 

   

        this.r=r; 

   

    public void run()//复写run方法 

   

        int x=0; 

        while(true) 

       

            if(x==0)//交替打印张三和王羲之 

           

              r.setInput("生产1代",".....爹"); 

           

            else 

           

                r.setInput("生产2代","..son"); 

           

            x=(x+1)%2;//控制交替打印 

       

   

 

//消费线程 

class Output implements Runnable 

    private Resource r; 

    Output(Resource r) 

   

        this.r=r; 

   

    public void run()//复写run方法 

   

        while(true) 

      

            r.getOutput(); 

       

   

 

class ResourceDemo2  

    public static void main(String[] args)  

   

        Resource r = new Resource();//表示操作的是同一个资源 

 

        new Thread(new Input(r)).start();//开启生产者线程 

 

        new Thread(new Output(r)).start();//开启消费者线程 

   

线程活锁

    线程活锁虽然不像死锁一样是常见的问题,但是对于并发编程的设计者来说就像一次邂逅一样。当所有线程阻塞,或者由于需要的资源无效而不能处理,不存在非阻塞线程使资源可用。

    JavaAPI 中线程活锁可能发生在以下情形:

    当所有线程在程序中执行 Object.wait (0),参数为 0 的 wait 方法。程序将发生活锁直到在相应的对象上有线程调用 Object.notify ()或者 Object.notifyAll ()。

当所有线程卡在无限循环中。

上面示例代码,当开启多个生产者线程或多个消费者线程时就会发生活锁。需要将代码中的notify方法改为notifyAll。

Jdk1.5之后出现了Lock对象替代同步,Coundition替代了wait notify notifyAll等方法

Coundition可以通过Lock对象获得,且Lock可以对应多个Coundition。

代码示例

/* 生产者生产商品,供消费者使用

有两个或者多个生产者,生产一次就等待消费一次

有两个或者多个消费者,等待生产者生产一次就消费掉

 */ 

 

import java.util.concurrent.locks.*;  

class Resource  

{    

    private String name; 

   private int count=1; 

    private boolean flag = false; 

     

    //多态 

    private Lock lock=new ReentrantLock(); 

 

    //创建两Condition对象,分别来控制等待或唤醒本方和对方线程 

    Condition condition_pro=lock.newCondition(); 

    Condition condition_con=lock.newCondition(); 

 

    //p1、p2共享此方法 

    public void setProducer(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();//解锁,这个动作一定执行 

       

         

   

 

    //c1、c2共享此方法 

    public void getConsumer()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; 

   

    //复写run方法 

    public void run() 

   

        while(true) 

       

            try 

           

                res.setProducer("商品"); 

           

            catch (InterruptedException e) 

           

           

      

   

 

//消费者线程 

class Consumer implements Runnable 

    private Resource res; 

    Consumer(Resource res) 

   

        this.res=res; 

   

    //复写run 

    public void run() 

   

        while(true) 

       

            try 

           

                res.getConsumer(); 

           

           catch (InterruptedException e) 

           

           

       

   

 

 

class  ProducerConsumer 

    public static void main(String[] args)  

   

        Resource res=new Resource(); 

 

        new Thread(new Producer(res)).start();//第一个生产线程 p1 

        new Thread(new Consumer(res)).start();//第一个消费线程 c1 

        new Thread(new Producer(res)).start();//第二个生产线程 p2 

        new Thread(new Consumer(res)).start();//第二个消费线程 c2 

   

}

Thread中的一些其他方法

join()方法

    概述:A线程执行到了B线程的.join()方法时,A线程就会等待,等B线程都执行完,A线程才会执行。(此时B和其他线程交替运行。)

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

代码示例

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();

//            t2.setPriority(Thread.MAX_PRIORITY);

 

//            t1.join();//t1线程要申请加入进来,运行。临时加入一个线程运算时可以使用join方法。

 

              for(int x=0; x<50; x++)

              {

//                   System.out.println(Thread.currentThread()+"....."+x);

              }

       }

}

 

setDaemon()方法

概述:将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。又称后台程序

setPriorty()

概述:用于更改线程的优先级,从1-10,优先级越高,cpu执行的概率越大。

Thread中的静态成员变量:

MAX_PRIORITY 最高优先级10

MIN_PRIORITY 最低优先级1

 NORM_PRIORITY系统默认优先级5

Yield()方法

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

 作用:可以使两个线程交替执行。

--------------------- android培训java培训、java学习型技术博客、期待与您交流! -------------------

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值