Java基础--多线程

原创 2015年07月07日 15:00:54


一、进程和线程

    进程:每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销,一个进程包含1--n个线程。

      线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。

二、多线程的概念

        在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制线程。 像这种在一个进程中有多个线程执行的方式,就叫做多线程。 

三. 创建线程两种方法

       1.继承Thread类,重写run()方法

        创建步骤:

          a.定义类继承Thread。

          b.复写Thread中的run方法。

          c.创建定义类的实例对象。相当于创建一个线程。

          d.用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。

//定义MyThread 类继承Thread。
class MyThread extends Thread{
	//复写Thread中的run方法。
	public void run(){
	for(int x=0;x<10;x++){
		System.out.println("x= "+x);
	}
  }
}
public class Test1 {

	public static void main(String[] args) {
     MyThread mt=new MyThread(); //创建线程MyThread的实例对象。
     mt.run();  //开启线程
	}
}

    2.实现Runnable接口,重写run()方法

          创建步骤:

           a.定义类实现Runnable的接口。

           b.覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。

           c.通过Thread类创建线程对象。

           d.将Runnable接口的子类对象作为实参传递给Thread类的构造方法。

    //定义MyThread类实现Runnable的接口。
class MyThread implements Runnable{
   //覆盖Runnable接口中的run方法。
	public void run() {
		for(int x=0;x<10;x++){
			System.out.println("x= "+x);
		}
	}
}
public class Test2 {

	public static void main(String[] args) {
      MyThread mt=new MyThread();  //创建线程MyThread的实例对象。
      Thread t=new Thread(mt);  //创建线程对象
      t.start();  //开启线程
	}
}
四、实现Runnable接口的好处

        1.适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了 面向对象的设计思想。 

        2.可以避免由于Java的单继承特性带来的局限。

        3.有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。

五.线程生命周期及状态

      1.线程生命周期:

          生命周期开始:当Thread对象创建完成时,线程生命周期开始。

          生命周期结束:当run()方法中代码正常执行完毕结束或者线程抛出一个未捕获的异常或者错误时,线程生命周期结束。

       2.线程状态:

          新建状态:创建一个线程对象后,还没有在其上调用start()方法。

          运行状态:具有执行资格和执行权。

          阻塞状态:有执行资格,但是没有执行权。

          冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法        时,获得执行资格,变为临时状态。

          消忙状态:run方法执行结束,或者线程抛出一个未捕获的异常,失去运行资格。

六.线程调度

       1.概念

          JAVA中多个线程是并发执行的,线程的执行必须先得到CPU的使用权,JAVA虚拟机会按照特定的机制为程序中每个线程分配CPU的使用权。
       2.线程调度的模型

           两种模型:分时调度和抢占式调度

       3.线程休眠

          Thread.sleep(long millis)静态方法强制当前正在执行的线程休暂停执行,以“减慢线程”。当线程睡眠时,在指定时间内是不会执行的。当睡眠时间到期,则返回到可运行状态。

          3.1对线程休眠的总结:

           a).线程睡眠是帮助所有线程获得运行机会的最好方法。

           b).线程睡眠到期自动苏醒,并返回到可运行状态,不是运行状态。sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就开始执行。

            c).sleep()是静态方法,只能控制当前正在运行的线程。

程序示例:(模拟火车站售票案例)

class TicketWidow implements Runnable{
	private int tickets=10;//定义变量tickets,并赋值10
	Object obj=new Object();//定义任意一个对象,用作同步对象的锁
	public void run(){
		while(true){
			synchronized(obj){//定义同步代码块
				try{
					Thread.sleep(10);//经过的线程休眠10秒
				}
				catch(InterruptedException e){
					e.printStackTrace();
				}
				if(tickets>0){
					System.out.println(Thread.currentThread().getName()+"出售   "+tickets--);
				}
				else //如果tickets小于10,退出循环
					break;
			}
		}
	}
}
public class LiXi {
	public static void main(String[] args) {
     TicketWidow tw=new TicketWidow();//创建TicketWidow对象
     //创建并开启3个进程
     new Thread(tw,"线程一").start();
     new Thread(tw,"线程二").start();
     new Thread(tw,"线程三").start();
	}
}

        程序解析:此程序创建了一个TicketWidow对象,然后创建了三个线程,程序中调用了Thread.sleep(10)方法,目的是让一个线程打印一次后休眠10ms,从而使其他线程获得执行的机会,这样就可以实现多个线程交替执行,确保三个线程访问的是同一个Tickets变量。

      4.线程让步

        线程的让步是通过Thread.yield()来实现的。yield()方法的作用是:暂停当前正在执行的线程对象,并执行其他线程。

        yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。

      5.线程插队

         join()方法:当某个线程中调用了join()方法时,调用的线程将会被堵塞,直到被join()方法加入的线程执行完成后它才继续运行。
           例如:
             Thread t = new MyThread();

              t.start();

              t.join();
七、多线程安全问题

     1.产生原因

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

      2.解决方法

         对多条操作共享数据的语句进行同步,一个线程在执行过程中其他线程不可以参与进来。

      3.Java中多线程同步是什么?

         同步是用来解决多线程的安全问题的,在多线程中,同步能控制对共享数据的访问。如果没有同步,当一个线程在修改一个共享数据时,而另外一个线程正在使用或者更新同一个共享数据,这样容易导致程序出现错误的结果。

      4.什么是锁?锁的作用是什么?

         锁就是对象。

         锁的作用是保证线程同步,解决线程安全问题。持有锁的线程可以在同步中执行,没有锁的线程即使获得cpu执行权,也进不去。        

      5.同步的前提

         1)必须保证有两个以上线程

         2)必须是多个线程使用同一个锁,即多条语句在操作线程共享数据

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

      6.同步的好处和弊端

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

         弊端:多线程都需要判断锁,比较消耗资源。

八、线程间通信

        1.概念

         多线程间通讯就是多个线程在操作同一资源,但是操作的动作不同。

       2.为什么要通信

        多线程并发执行的时候, 如果需要指定线程等待或者唤醒指定线程, 那么就需要通信。比如生产者消费者的问题,生产一个消费一个,生产的时候需要负责消费的进程等待,生产一个后完成后需要唤醒负责消费的线程,同时让自己处于等待,消费的时候负责消费的线程被唤醒,消费完生产的产品后又将等待的生产线程唤醒,然后使自己线程处于等待。这样来回通信,以达到生产一个消费一个的目的。                  

      3.怎么通信

         在同步代码块中, 使用锁对象的wait()方法可以让当前线程等待, 直到有其他线程唤醒为止. 使用锁对象的notify()方法可以唤醒一个等待的线程,或者notifyAll唤醒所有等待的线程。

      4.Lock和Condition

          Condition 实现提供比synchronized方法和语句可获得的更广泛的锁的操作,可支持多个相关的Condition对象

          Lock是个接口,锁是控制多个线程对共享数据进行访问的工具。

          JDK1.5中提供了多线程升级的解决方案: 将同步synchonized替换成了显示的Lock操作,将Object中的wait、notify、notifyAll替换成了Condition对象,该对象可以Lock锁进行获取 

          Lock的方法摘要:

          void   lock() : 获取锁。

         Condition  newCondition() :返回绑定到此 Lock 实例的新 Condition 实例。

          void  unlock() :释放锁。

          Condition方法摘要:

          void await() :造成当前线程在接到信号或被中断之前一直处于等待状态。

          void  signal() :唤醒一个等待线程。         

          void  signalAll(): 唤醒所有等待线程。          

       5.停止线程

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

          特殊情况:当线程属于冻结状态,就不会读取循环控制标记,则线程就不会结束。为解决该情况,可引入Thread类中的Interrupt方法结束线程的冻结状态;当没有指定的方式让冻结线程恢复到运行状态时,需要对冻结进行清除,强制让线程恢复到运行状态        

       6.interrupt

           void interrupt() 中断线程:

           中断状态将被清除,它还将收到一个 InterruptedException

       7.守护线程(后台线程)

           setDaemon(booleanon):将该线程标记为守护线程或者用户线程。

           特点:当主线程结束,守护线程自动结束。

程序示例:

//多生产者与多消费的示例
class Resource{
	private String name;
	private int count=1;
	private boolean flag=false;
	Lock lock=new ReentrantLock(); //创建一个锁对象
	//通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者。
	Condition con1=lock.newCondition(); 
	Condition con2=lock.newCondition();
	public void set(String name){
		lock.lock();//获取锁
		try{
			while(flag)//如果flag标记为true
			try{con1.await();}catch(Exception e){} //con1的监视器处于等待状态
			this.name =name+count;
			count++;
			System.out.println(Thread.currentThread().getName()+"...生产了..."+this.name);
			flag=true; //将flag标记改为true
			con2.signal();//唤醒con2监视器
		}finally{
			lock.unlock(); //释放锁
		}
	}
	public void out(){
		lock.lock();
		try{
		    while(!flag)//如果flag标记为flase
			try{con2.await();}catch(Exception e){}//con2的监视器处于等待状态
			System.out.println(Thread.currentThread().getName()+"...消费了..."+this.name);
			flag=false; //将flag标记改为flase
			con1.signal(); //唤醒con1监视器
	}finally{
		lock.unlock(); //释放锁
	}
  }
}
//创建生产者
class Producer implements Runnable{
	private Resource r;
	Producer(Resource r){
		this.r=r;
	}
	public void run() {
		while(true){
			r.set("商品"); //调用r的set()方法,并为name赋值为"商品"
		}
	}	
}
//创建消费者
class Consumer implements Runnable{
	private Resource r;
	Consumer(Resource r){
		this.r=r;
	}
	public void run() {
		while(true){
	       r.out(); //调用r的out()方法
		}
	}
}
public class test1 {
	public static void main(String[] args) {
		//创建资源对象
		Resource r=new Resource();  
		//创建任务
		Producer p=new Producer(r);
		Consumer c=new Consumer(r);
		//创建线程,两个生产者路径,两个消费者路径
		Thread t0=new Thread(p);
		Thread t1=new Thread(p);
		Thread t2=new Thread(c);
		Thread t3=new Thread(c);
		//开启线程
		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}
}

程序部分运行结果:


问题:1.创建线程是为什么要复写run方法?

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

            2.start()和run方法有什么区别?

           调用start方法方可启动线程,而run方法只是thread的一个普通方法,调用run方法不能实现多线程

         3.wait(),sleep()有什么区别?

            wait():既可以指定时间也可以不指定时间,释放cpu执行权,释放锁。

            sleep():必须指定睡眠时间,释放cpu执行权,不释放锁。


相关文章推荐

Java基础[05-多线程]

  • 2012年10月06日 09:50
  • 81KB
  • 下载

java基础多线程练习题(1)

  • 2017年08月08日 15:26
  • 7KB
  • 下载

java基础复习(集合、泛型、IO流、多线程、Junit 、内省 、Properties、 路径问题)

集合---|Collection: 单列集合 ---|List: 有存储顺序, 可重复 ---|ArrayList: 数组实现, 查找快, 增删慢 ...

JAVA基础再回首(二十八)——网络编程概述、IP地址、端口号、TCP和UDP协议、Socket、UDP传输、多线程UDP聊天

JAVA基础再回首(二十八)——网络编程概述、IP地址、端口号、TCP和UDP协议、Socket、UDP传输、多线程UDP聊天 版权声明:转载必须注明本文转自程序员杜鹏程的博客:http://bl...
  • m366917
  • m366917
  • 2016年10月02日 15:32
  • 2041

java基础多线程

  • 2015年05月18日 11:52
  • 272KB
  • 下载

13.尚硅谷_java基础_多线程

  • 2013年09月26日 14:50
  • 1.58MB
  • 下载

JAVA多线程和并发基础面试题

多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题。(校对注:非常赞同这个观...

java多线程基础篇讲解

  • 2010年03月11日 10:30
  • 55KB
  • 下载
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Java基础--多线程
举报原因:
原因补充:

(最多只允许输入30个字)