在java的历史版本中,有两种创建多线程程序的方法
1. 通过创建Thread类的子类来实现
2. 通过实现Runable接口的类来实现(推荐)
一、通过Thread类实现多线程
- 设计Thread的子类
- 根据工作需要重新设计线程的run方法
n 线程类Thread中提供的run是一个空方法。为此,我们可以继承Thread,然后覆盖(override)其中的run,使得该线程能够完成特定的工作。
- 使用start方法启动线程,将执行权转交到run。
实例CODE:
import java.util.Random;
class MyThread extends Thread{
private int sleepTime;
private static Random generator=new Random();
public MyThread(String n){
super(n);
} //n:线程名称
public void run(){
for(int i=0;i<5;i++){
// 睡眠一段随机时间
try{
sleepTime=generator.nextInt(1000);
Thread.sleep(sleepTime);
}catch (InterruptedException e)
{e.printStackTrace(); }
//显示本线程名称
System.out.print(getName()+" ");
}
}
}
public class MultiThreadExample{
public static void main(String []args){
new MyThread("A").start(); //启动线程A
new MyThread("B").start(); //启动线程B
}
}
二、通过实现Runnable接口创建线程
n 创建线程最简单的方法就是创建一个实现Runnable 接口的类,这个类仅需实现其中一个run()方法。
n 主要步骤:
1. 创建某个类实现Runnable接口,实现run()方法。
2. 创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象。
3. 调用Thread的start方法。
示例CODE:
// Fig. 18.4: PrintTask.java
// PrintTask class sleeps for a random time from 0 to 5 seconds
import java.util.Random;
public class PrintTask implements Runnable
{
private final int sleepTime; // random sleep time for thread
private final String taskName; // name of task
private final static Random generator = new Random();
public PrintTask( String name )
{
taskName = name; // set task name
// pick random sleep time between 0 and 5 seconds
sleepTime = generator.nextInt( 5000 ); // milliseconds
} // end PrintTask constructor
// method run contains the code that a thread will execute
public void run()
{
try // put thread to sleep for sleepTime amount of time
{
System.out.printf( "%s going to sleep for %d milliseconds.\n",
taskName, sleepTime );
Thread.sleep( sleepTime ); // put thread to sleep
} // end try
catch ( InterruptedException exception )
{
System.out.printf( "%s %s\n", taskName,
"terminated prematurely due to interruption" );
} // end catch
// print task name
System.out.printf( "%s done sleeping\n", taskName );
} // end method run
} // end class PrintTask
// Fig. 18.5: ThreadCreator.java
// Creating and starting three threads to execute Runnables.
import java.lang.Thread;
public class ThreadCreator
{
public static void main( String[] args )
{
System.out.println( "Creating threads" );
// create each thread with a new targeted runnable
Thread thread1 = new Thread( new PrintTask( "task1" ) );
Thread thread2 = new Thread( new PrintTask( "task2" ) );
Thread thread3 = new Thread( new PrintTask( "task3" ) );
System.out.println( "Threads created, starting tasks." );
// start threads and place in runnable state
thread1.start(); // invokes task1's run method
thread2.start(); // invokes task2's run method
thread3.start(); // invokes task3's run method
System.out.println( "Tasks started, main ends.\n" );
} // end main
} // end class RunnableTester
n Java有两种创建线程的方法,为什么推荐实现Runnable接口的方式创建线程?
n Thread类定义了多种方法可以被派生类重载。对于所有的方法,唯一的必须被重载的是run()方法。这当然是实现Runnable接口所需的同样的方法。
n 很多Java程序员认为类仅在它们需要被加强或修改时被扩展。因此,如果你不重载Thread的其他方法,最好通过实现Runnable 接口,来创建线程。
二、使用Executor框架管理线程
n 虽然可以使用Thread类来显示的创建线程,但推荐的做法是使用Executor接口,让它来管理Runnable对象的执行。
n 通常Executor对象会创建并管理一组执行Runnable对象的线程,这组线程被称为线程池。
采用Executor的优势
n Executor对象能够复用已有的线程,从而消除了为每个任务创建新线程的开销,
n 它能通过优化线程的数量,提高程序性能,保证处理器一直处于忙碌状,而不必创建过多的线程使程序资源耗尽。
步骤:
n 声明一个execute()方法,接收一个Runnable实参,
n Executor会将传递给它的execute方法的每个Runnable对象赋予线程池中的某个可用线程。
n 如果没有可用线程,Executor会创建一个新线程,或者等待某个线程成为可用的,再将其赋予Runnable对象。
ExecutorService接口
n 该接口扩展了Executor接口,并声明了许多方法用于管理Executor的声明周期。
n 通过Executor类中的静态方法,可用创建实现ExecutorService接口的对象。
n 程序示例: ExecutorServiceTest
线程的同步
n 同一进程的多个线程共享同一存储空间,带来了访问冲突这个严重的问题。
n 有时两个或多个线程可能会试图同时访问一个资源,在此情况下,数据可能会变得不一致。
n 例如,一个线程可能尝试从一个文件中读取数据,而另一个线程则尝试在同一文件中修改数据,
n 某个时刻只允许一个线程排他性的访问操作共享对象的代码,当独占对象的线程在操作对象时,其他线程会一直等待,直到独占对象的那个线程完成了对对象的操作,其他对象才被允许处理该对象,
n 这种方法可以实现多个线程的协同工作,解决冲突问题。
实现线程同步
n 一种常见办法是使用java内置的监控器。
n 每个对象都具有一个监控器和一个监控锁,
n 监控器保证在任何时刻,它的对象的监控锁是由唯一一个线程持有的。
n 这样监控器和监控锁才能用于实现“互斥”。
n Java中可用使用关键字synchronized为共享资源加锁。
n synchronized可修饰一个代码块或一个方法,使被修饰对象在任一时刻只能有一个线程访问,从而提供了程序的同步执行功能。
n 两种方式实现同步:
n 使用同步方法:通过在方法声明中加入synchronized关键字来声明synchronized方法
n public synchronized void methodA() { }
n 当一个线程调用一个“互斥”方法时,它试图获得该方法锁。如果方法未锁定,则获得使用权,以独占方式运行方法体,运行完释放该方法的锁。如果方法被锁定,则该线程必须等待,直到方法锁被释放时。
n 注意:
n 对方法run( )无法加锁,不可避免冲突;
n 对构造函数不能加锁,否则出现语法错误。
n synchronized方法虽然可以解决同步的问题,但也存在缺陷,
n 如果一个synchronized方法需要执行很长时间,将会大大影响系统的效率。
n Java语言提供了一种解决办法,就是synchronized块。
1. 使用同步块
2. 可以通过synchronized关键字将一个程序块声明为synchronized块。
synchronized(object) { //要互斥的语句 }
n 当一个线程执行这段代码时,它获得特定对象的所有权,即拥有该对象的锁。此时,如果有第二个线程对同一个对象也要执行这段代码时,它也试图获得该对象的所有权,但因该对象已被锁定,则第二个线程必须等待,直到锁被释放为止。
n 第一个线程执行完代码段后,自动释放锁,接下去第二个线程获得锁并可运行。
n 注意:
n 当维护所需要的同步特性时,要尽可能的缩小同步语句占用的时间,这样就能最小化被阻塞线程的等待时间。
n 线程之间的同步是针对线程间共享可变数据的情况,对于线程间共享不可变数据时,应将数据字段声明成final的。