java多线程-问题

在java的历史版本中,有两种创建多线程程序的方法

1.       通过创建Thread类的子类来实现

2.       通过实现Runable接口的类来实现(推荐)

一、通过Thread类实现多线程

  1. 设计Thread的子类
  2. 根据工作需要重新设计线程的run方法

线程类Thread中提供的run是一个空方法。为此,我们可以继承Thread,然后覆盖(override)其中的run,使得该线程能够完成特定的工作。

  1. 使用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(); //启动线程

    }

}

二、通过实现Runnable接口创建线程

创建线程最简单的方法就是创建一个实现Runnable 接口的类,这个类仅需实现其中一个run()方法。

主要步骤:

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  

 

 

Java有两种创建线程的方法,为什么推荐实现Runnable接口的方式创建线程?

Thread类定义了多种方法可以被派生类重载。对于所有的方法,唯一的必须被重载的是run()方法。这当然是实现Runnable接口所需的同样的方法。

很多Java程序员认为类仅在它们需要被加强或修改时被扩展。因此,如果你不重载Thread的其他方法,最好通过实现Runnable 接口,来创建线程。

二、使用Executor框架管理线程

虽然可以使用Thread类来显示的创建线程,但推荐的做法是使用Executor接口,让它来管理Runnable对象的执行。

通常Executor对象会创建并管理一组执行Runnable对象的线程,这组线程被称为线程池。

采用Executor的优势

Executor对象能够复用已有的线程,从而消除了为每个任务创建新线程的开销,

它能通过优化线程的数量,提高程序性能,保证处理器一直处于忙碌状,而不必创建过多的线程使程序资源耗尽。

 

步骤:

声明一个execute()方法,接收一个Runnable实参,

Executor会将传递给它的execute方法的每个Runnable对象赋予线程池中的某个可用线程。

如果没有可用线程,Executor会创建一个新线程,或者等待某个线程成为可用的,再将其赋予Runnable对象。

 

ExecutorService接口

该接口扩展了Executor接口,并声明了许多方法用于管理Executor的声明周期。

通过Executor类中的静态方法,可用创建实现ExecutorService接口的对象。

程序示例: ExecutorServiceTest

线程的同步

同一进程的多个线程共享同一存储空间,带来了访问冲突这个严重的问题。

有时两个或多个线程可能会试图同时访问一个资源,在此情况下,数据可能会变得不一致。

例如,一个线程可能尝试从一个文件中读取数据,而另一个线程则尝试在同一文件中修改数据,

某个时刻只允许一个线程排他性的访问操作共享对象的代码,当独占对象的线程在操作对象时,其他线程会一直等待,直到独占对象的那个线程完成了对对象的操作,其他对象才被允许处理该对象,

这种方法可以实现多个线程的协同工作,解决冲突问题。

实现线程同步

一种常见办法是使用java内置的监控器。

每个对象都具有一个监控器和一个监控锁,

监控器保证在任何时刻,它的对象的监控锁是由唯一一个线程持有的。

这样监控器和监控锁才能用于实现“互斥”。

Java中可用使用关键字synchronized为共享资源加锁。

synchronized可修饰一个代码块或一个方法,使被修饰对象在任一时刻只能有一个线程访问,从而提供了程序的同步执行功能。

 

两种方式实现同步:

使用同步方法:通过在方法声明中加入synchronized关键字来声明synchronized方法

public synchronized void methodA() {  }

当一个线程调用一个“互斥”方法时,它试图获得该方法锁。如果方法未锁定,则获得使用权,以独占方式运行方法体,运行完释放该方法的锁。如果方法被锁定,则该线程必须等待,直到方法锁被释放时。

 

注意:

对方法run( )无法加锁,不可避免冲突;

对构造函数不能加锁,否则出现语法错误。

synchronized方法虽然可以解决同步的问题,但也存在缺陷,

如果一个synchronized方法需要执行很长时间,将会大大影响系统的效率。

Java语言提供了一种解决办法,就是synchronized块。

 

1.       使用同步块

2.       可以通过synchronized关键字将一个程序块声明为synchronized块。

              synchronized(object) {  //要互斥的语句  }

当一个线程执行这段代码时,它获得特定对象的所有权,即拥有该对象的锁。此时,如果有第二个线程对同一个对象也要执行这段代码时,它也试图获得该对象的所有权,但因该对象已被锁定,则第二个线程必须等待,直到锁被释放为止。

第一个线程执行完代码段后,自动释放锁,接下去第二个线程获得锁并可运行。

 

注意:

当维护所需要的同步特性时,要尽可能的缩小同步语句占用的时间,这样就能最小化被阻塞线程的等待时间。

线程之间的同步是针对线程间共享可变数据的情况,对于线程间共享不可变数据时,应将数据字段声明成final的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值