Java程序设计多线程基础

Posted BkbK-

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java程序设计多线程基础相关的知识,希望对你有一定的参考价值。

多线程基础

在这里插入图片描述

一、线程的概念

(1)进程

进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。

进程的特征

  • 结构特征
  • 动态性
  • 并发性
  • 独立性
  • 异步性

进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态概念,竞争计算机系统资源的基本单位。

多进程:在操作系统中能同时运行多个任务(程序);

(2)线程

线程(英语:thread)是操作系统能够进行运算调度的最小单位。

  • 它被包含在进程之中,是进程中的实际运作单位。
  • 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

线程:是进程的一个执行单元,是进程内可调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。

多线程:在同一个应用程序中有多个顺序流同时执行。
多线程的目的是为了最大限度的利用CPU资源。

二、线程的创建

Java中实现多线程的方法有两种:

  • 继承java.lang包中的Thread类。
  • 用户在定义自己的类中实现Runnable接口。

(1)Thread

1.Thread类

  • 直接继承了Object类,并实现了Runnable接口。位于java.lang包中
  • 封装了线程对象需要的属性和方法
  • 继承Thread类——创建多线程的方法之一
    • 从Thread类派生一个子类,并创建子类的对象
      子类应该重写Thread类的run方法,写入需要在新线程中执行的语句段。
    • 调用start方法来启动新线程,自动进入run方法。

2.Thread类的子类来创建线程

定义一个Thread的子类并重写其run方法:

class MyThread extends Thread {
   public void run() {...}
}

生成该类的对象:

MyThread myThread = new MyThread();

启动或运行线程,Java虚拟机会自动启动线程,从而由Java虚拟机进一步统一调度线程,实现各个线程一起并发地运行。

myThread.start();

在这里插入图片描述

  • 运行结果每一次都不同。因为多个线程都获取cpu的执行权,cpu执行到谁,谁就运行。明确一点,在某一时刻,只能有一个程序在运行(多核除外)。
  • cpu在做着快速的切换,已达到看上去是同时运行的效果。我们可以形象把多线程的运行行为看作在相互抢夺cpu的执行权。
  • 多线程的一个特性:随机性,谁抢到,谁执行,至于执行多长时间,由cpu决定。

3.在新线程中完成计算某个整数的阶乘

public class FactorialThreadTester 
 {
   public static void main( String [] args ) 
   {
		  System.out.println("main thread starts");
		  FactorialThread thread
                      =new FactorialThread(10);
       thread.start();    //将自动进入run()方法   
       System.out.println("main thread ends " );        
   }
} 
class FactorialThread extends Thread
 {
      private int num;  
      public FactorialThread( int num ) 
      {    this.num=num;      }

      public void run() 
      {  
          int i=num; 
          int result=1;   
          System.out.println("new thread started" );        
          while(i>0) 
          {     result=result*i;   i=i-1;   } 
                System.out.println("The factorial of "+num+" is "+result);  
                System.out.println("new thread ends");  
          }
       }     
} 

运行结果

main thread starts
main thread ends
new thread started
The factorial
of 10 is 3628800
new thread ends

结果说明

  • main线程已经执行完后,新线程才执行完
  • main函数调用thread.start()方法启动新线程后并不等待其run方法返回就继续运行,thread.run函数在一边独自运行,不影响原来的main函数的运行

4.构造函数

public Thread()
public Thread(Runnable target)
public Thread(String name)
public   Thread(Runnable target, String name);

参数说明:
name:新线程对象的名字

5.常用方法

public void start()

启动该线程,将导致run方法被自动调用。该方法将立即返回,新线程将运行

public void run()

必须覆盖该方法,在方法体中添加你想要在该线程中执行的代码。

public static void sleep(long millis) throws InterruptedException

使当前正在执行的线程睡眠指定的时间。sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。 并不释放对象锁。也就是如果有Synchronized同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常。

public static void yield()

与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。

public final boolean isAlive()

用于测试某个线程是否还活着

public final void setPriority(int newPriority)

设置线程的优先级

public final int getPriority() ; 

获得线程的优先级

public static Thread currentThread()

返回代表当前正在执行的线程的Thread对象

public final void wait(long timeout) throws InterruptedException

当前线程被中断,并进入到一个对象的等待列表中,直到另外的线程调用同一个对象上的notify() 或notifyAll() 方法

public final void notify()

用于将对象等待列表中的任选的一个线程唤醒,使它再次成为可运行的线程

public final void notifyAll()

用于将对象等待列表中的所有线程唤醒,使它们再次成为可运行的线程。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(2)Runnable

1.Runnable接口

  • Thread类实现了Runnable接口
  • 只有一个run()方法
  • 更便于多个线程共享资源
  • Java不支持多继承,如果已经继承了某个基类,便需要实现Runnable接口来生成多线程
  • 以实现runnable的对象为参数建立新的线程
  • start方法启动线程就会运行run()方法

2.Runnable接口的使用

使用Runnable接口实现计算某个整数的阶乘

public class FactorialThreadTester {
   public static void main( String [] args ) {
      System.out.println("main thread starts");
      FactorialThread t=new FactorialThread(10);//实现了Runnable的
      new Thread(t).start();  //运行FactorialThread的run
     //Thread thead = new Thread(t);  
     // thead.start();
      System.out.println("new thread started,main thread ends " );   
   }
} 
class FactorialThread implements Runnable {
   private int num;  
   public FactorialThread( int num ) {
      this.num=num;
   } 
   public void run()  {  
      int i=num; 
      int result=1;   
      while(i>0) {
      	   result=result*i;
      	   i=i-1;
    }
    System.out.println("The factorial of "+num+" is "+result);  
      System.out.println("new thread ends");  
    }     
} 

3个Runnable类型的对象

public class ThreadSleepTester {
   public static void main( String [] args ) {
   	  TestThread thread1 = new TestThread();
      TestThread thread2 = new TestThread();
      TestThread thread3 = new TestThread();        
      System.out.println( "Starting threads" );
      
      new Thread(thread1,"Thread1").start();  
      new Thread(thread2,"Thread2").start();
      new Thread(thread3,"Thread3").start();
        
      System.out.println( "Threads started, main ends\\n" );        
   }     
}
class TestThread implements Runnable {
   private int sleepTime;   
   public TestThread() {
      sleepTime = ( int ) ( Math.random() * 6000 );
   }       
   public void run() {      
      try {
         System.out.println( 
              Thread.currentThread().getName() + " going to sleep for "   
                                                                               + sleepTime );
         Thread.sleep( sleepTime ); 
      } 
      catch ( InterruptedException exception ) {};
      System.out.println( Thread.currentThread().getName()+ "finished" );
   }     
}

在这里插入图片描述

三、线程间的数据共享

独立的同时运行的线程有时需要共享一些数据并且考虑到彼此的状态和动作

  • 用同一个实现了Runnable接口的对象作为参数创建多个线程
  • 多个线程共享同一对象中的相同的数据
public class ShareTargetTester  {
   public static void main( String [] args )  {
   	 TestThread threadobj = new TestThread();        
      System.out.println( "Starting threads" );
      
      new Thread(threadobj,"Thread1").start();  
      new Thread(threadobj,"Thread2").start();
      new Thread(threadobj,"Thread3").start();
        
      System.out.println( "Threads started, main ends\\n" );        
   }     
}
class TestThread implements Runnable {
   	private int sleepTime;   
   	public TestThread()  {
        sleepTime = ( int ) ( Math.random() * 6000 );
   	}       
   	public void run() {      
       try {
            System.out.println( 
            Thread.currentThread().getName() + " going to sleep for " +   		sleepTime );            
            Thread.sleep( sleepTime ); 
       } 
       catch ( InterruptedException exception ) {};
	   System.out.println( Thread.currentThread().getName() + "finished" );
	}     
}

运行结果
Starting threads

Thread1 going to sleep for 966
Thread2 going to sleep for 966
Threads started, main ends
Thread3 going to sleep for 966
Thread1 finished
Thread2 finished
Thread3 finished

说明:
因为是用一个Runnable类型对象创建的3个新线程,这三个线程就共享了这个对象的私有成员sleepTime,在本次运行中,三个线程都休眠了966毫秒

四、多线程的同步控制

(1)同步方法

把synchronized当作函数修饰符

public synchronized (static) void aMethod() { 
    // do something 
}
  • synchronized锁定的是调用这个同步方法对象。
    • 当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。
    • 但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。
  • 静态同步synchronized方法与synchronized(class)代码块持有的锁一样,都是Class锁,Class锁对对象的所有实例起作用。

(2)同步代码段

synchronized线程同步关键字,实现互斥:
用于指定需要同步的代码段或方法,也就是监视区
可实现与一个锁旗标的交互。例如:

synchronized(对象){ 代码段 } 
  • synchronized的功能是:首先判断对象的锁旗标是否在,如果在就获得锁旗标,然后就可以执行紧随其后的代码段;如果对象的锁旗标不在(已被其他线程拿走),就进入等待状态,直到获得锁旗标
    当被synchronized限定的代码段执行完,就释放锁旗标

  • Java 使用监视器机制

    • 每个对象只有一个“锁旗标” ,利用多线程对“锁旗标”的争夺实现线程间的互斥
    • 当线程A获得了一个对象的锁旗标后,线程B必须等待线程A完成规定的操作、并释放出锁旗标后,才能获得该对象的锁旗标,并执行线程B中的操作

(3)模拟存票、售票过程

假定开始售票处并没有票,一个线程往里存票,另外一个线程则往出卖票
新建一个票类对象,让存票和售票线程都访问它。本例采用两个线程共享同一个数据对象来实现对同一份数据的操作

1.不进行同步控制

public class ProducerAndConsumer {
	public static void main(String[] args) {
		Tickets t=new Tickets(10); //建立票对象,票总数10
		new Consumer(t).start();  //开始卖票线程
		new Producer(t).start();   //开始存票线程	
    }
}
class Tickets {
	int number=0;          //票号
	int size;                   //总票数
	boolean available=false;  //表示目前是否有票可售
	public Tickets(int size)  //构造函数,传入总票数参数
	{
		this.size=size;     
	}		
}
class Producer extends Thread
{
	Tickets t=null;
	public Producer(Tickets t)
	{	this.t=t;  	}
	public void run()
	{
		while( t.number < t.size)
		{
		     System.out.println("Producer puts ticket " +(++t.number));
		     t.available=true;  
		}			
	}
}
class Consumer extends Thread  //售票线程
{
	Tickets t=null;
	int i=0;
	public Consumer(Tickets t)
	{      this.t=t;  	}
	 public void run()
	 {
	        while(i<t.size) 
            {
		   if(t.available==true && i<t.number) 			        	  
		   		System.out.println("Consumer buys ticket "+(++i));
		   if(i==t.number) //现有的票号卖完了
		        t.available=false;				
		}				
	}
}

2.同步代码段

将需要互斥的语句段放入synchronized(object){}语句中,且两处的object是相同的

class Producer extends Thread {
	Tickets t=null;
	public Producer(Tickets t) { this.t=t;}
	public void run() {
		while((t.number)<t.size) {
		    synchronized(t) { // 申请对象t的锁旗标 
		         System.out.println("Producer puts ticket" +(++t.number));
		         t.available=true;		
		     }     // 释放对象t的锁旗标
		}
		System.out.println("Producer ends!");			
	}
}
class Consumer extends Thread {
	Tickets t=null;
	int i=0;
	public Consumer(Tickets t) {   this.t=t;  }
	public void run() {
	    while(i<t.size) {
		synchronized(t) {  //申请对象t的锁旗标
		    if(t.available==true && i<=t.number)
		         System.out.println("Consumer buys ticket "+(++i));
		    if(i==t.number) {
                            try{ Thread.sleep(1); } catch(Exception e){}
		              t.available=false;
		             }
		     }	//释放对象t的锁旗标			
		}
		System.out.println("Consumer ends");		
	}
} 

3.同步方法

将互斥方法放在共享的资源类Tickets中

class Tickets {
	int size;  //票总数
	int number=0;  //存票序号
	int i=0;   //售票序号
	boolean available=false;  //是否有待售的票
	public Tickets(int size) {  this.size=size;  }
	public synchronized void put() {  //同步方法,实现存票的功能
		 System.out.println("Producer puts ticket "+(++number));
	    available=true;		
	}	
	public synchronized void sell() {  //同步方法,实现售票的功能
	    if(available==true && i<=number)
	    System.out.以上是关于Java程序设计多线程基础的主要内容,如果未能解决你的问题,请参考以下文章

Java程序设计多线程基础

Java程序设计多线程基础

Java多线程基础

多线程编程学习一(Java多线程的基础)

Java多线程基础

多线程 Thread 线程同步 synchronized