java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > JAVA多线程

JAVA多线程编程实例详解

作者:doubly_yi

这篇文章主要介绍了JAVA多线程编程,结合实例形式总结分析了多线程、锁、线程池等相关原理及使用技巧,需要的朋友可以参考下

本文实例讲述了JAVA多线程编程。分享给大家供大家参考,具体如下:

并发性和并行性的区别:
并行性:在同一时刻,有多条指令在多个处理器上同时执行(多个CPU)
并发性:在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得宏观上具有多个进程同时执行的效果(单核)。

public class Deom_1 extends Thread{

  public void run(){
      super.run();
      System.out.println("MyThread01");
    }


  public static void main(String[] args){
    Deom_1 demo=new Deom_1();
    demo.setName("Demo_1");
    demo.start();
    System.out.println("当前线程的名字:"+Thread.currentThread().getName()+" 运行结束");
  }
}

多次调用start会抛出java.lang.IllegalThreadStateException异常
如果只调用run(),不调用start()方法,就相当于调用了一个普通的函数,实际上还是在同一个线程中运行的run()方法。

//通过实现Runnable接口来创建线程类
public class SecondThread implements Runnable
{
  private int i ;
  //run方法同样是线程执行体
  public void run()
  {
    for ( ; i < 20 ; i++ )
    {
      //当线程类实现Runnable接口时,
      //如果想获取当前线程,只能用Thread.currentThread()方法。
      System.out.println(Thread.currentThread().getName() + " " + i);
    }
  }

  public static void main(String[] args) 
  {
    for (int i = 0; i < 50; i++)
    {
      System.out.println(Thread.currentThread().getName() + " " + i);
      if (i == 20)
      {
        SecondThread st = new SecondThread();
        //通过new Thread(target , name)方法创建新线程
        new Thread(st , "新线程1").start();
        new Thread(st , "新线程2").start();
      }
    }
  }
}

进入阻塞状态的线程在获得执行机会后重新进入就绪状态,而不是运行状态

当主线程结束的时候,其他线程不受影响。一旦子线程启动它就拥有和主线程一样的地位。

调用Thread类的setDaemon(true)方法可以将指定线程设置为后台线程。该方法一定要在启动线程之前设置,否则会发生异常。同时提供isDaemon()方法判断是否是后台线程。主线程一般默认为前台线程前台线程创建的子线程默认是前台,后台线程创建的子线程默认是后台。

PriorityTest low = new PriorityTest("低级");
low.start();
System.out.println("创建之初的优先级:" + low.getPriority());
//设置该线程为最低优先级
low.setPriority(Thread.MIN_PRIORITY);
PriorityTest high = new PriorityTest("高级");
high.start();
System.out.println("创建之初的优先级:" + high.getPriority());
//设置该线程为最高优先级
high.setPriority(Thread.MAX_PRIORITY);

每个线程的默认优先级都与创建它的父线程具有相同的线程,在默认的情况下,main线程具有普通优先级。

class SelfPrivateNum {
  private int num = 0;

  public void addI(String username) {
    try {

      if (username.equals("a")) {
        num = 100;
        System.out.println("a set over!");
        Thread.sleep(2000);
      } else {
        num = 200;
        System.out.println("b set over!");
      }
      System.out.println(username + " num=" + num);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

}

class ThreadAA extends Thread {

  private SelfPrivateNum numRef;

  public ThreadAA(SelfPrivateNum numRef) {
    super();
    this.numRef = numRef;
  }

  @Override
  public void run() {
    super.run();
    numRef.addI("a");
  }

}

class ThreadBB extends Thread {

  private SelfPrivateNum numRef;

  public ThreadBB(SelfPrivateNum numRef) {
    super();
    this.numRef = numRef;
  }

  @Override
  public void run() {
    super.run();
    numRef.addI("b");
  }

}

public class RunUnsafe {

  public static void main(String[] args) {

    SelfPrivateNum numRef = new SelfPrivateNum();

    ThreadAA athread = new ThreadAA(numRef);
    athread.start();

    ThreadBB bthread = new ThreadBB(numRef);
    bthread.start();

  }

}
class SelfPrivateNum2 {
  private int num = 0;

  public synchronized void addI(String username) {
    try {

      if (username.equals("a")) {
        num = 100;
        System.out.println("a set over!");
        Thread.sleep(2000);
      } else {
        num = 200;
        System.out.println("b set over!");
      }
      System.out.println(username + " num=" + num);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

}

关键字synchronized取的是锁都是对象锁,而不是代码或者是方法当作锁。当多个线程访问的是同一个对象的同步方法的时候是排队的,而当多个线程访问多个对象的同步方法的时候运行的顺序是异步的。

为了避免数据出现交叉的情况,使用synchronized关键字来进行同步。虽然在赋值时进行了同步,但是可能在取值的时候出现脏读(dirtyRead)的现象。发生脏读的情况是在读取实例变量时。出现脏读是应为getValue方法不是同步方法,解决方法可以定义为同步方法。

//线程开始执行同步代码块之前,必须先获得对同步监控器的锁定
synchronized (Obj){
  ………
  //此处的代码就是同步代码块  
}

通常使用可能被并发访问的共享资源充当同步监视器。

Class A{
   private final ReentrantLock lock= new ReentrantLock ();
   //需要保证线程安全的方法
   public void m(){
    //加锁
    lock.lock();
    try{  
      ……………
    }
    finally{
     lock.unlock();
    }
  }
}   

同步方法的比较
1、同步方法和同步代码块使用与竞争资源相关的、隐式的同步监视器,并且强制要求加锁和释放锁要出现在同一个块结构中,而且当获取多个锁的时候,他们必须按照相反的顺序依次释放。
2、Lock方法不仅可以使用与非结构快中,还可以试图中断锁和再次加锁的功能。被Lock加锁的代码中还可以嵌套调用。
3、资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍。

如果需要进行多个线程之间共享资源,以达到线程之间的通信功能,就使用同步机制;如果仅仅需要隔离多个线程之间的共享冲突,可以使Threadlocal。

方法wait()的作用是使当前执行代码线程进行等待,是Object类的方法,该方法用来将当前线程置于“阻塞队列”中,并在wait()所在的代码处停止执行,直到接到通知被唤醒。

在调用wait()之前,线程必须持有该对象的对象级别锁,只能在同步方法或者是同步块中调用此方法。在执行wait方法后,当前线程释放锁。进入和其他线程竞争重新获得锁。如果调用wait方法没有持有锁,则会抛出异常。

方法notify()用来通知那些可能等待该对象的对象锁的其他线程,如果有多个线程等待,则由线程规划器随便选择一个呈wait状态的线程。

方法nofity()也是在同步方法或者同步块中调用,调用前同样要获得对象的对象级别所,否则抛出异常。在执行notify方法后,当前线程不会马上释放该对象锁,要等到执行notify方法的线程将程序执行完才能会释放锁。

方法notifyAll()方法可以使正在等待队列中等待同一共享资源的”全部”线程从等待状态退出,进入可运行状态。

public class Test3 {
  //main方法中有三个等待线程,一个唤醒线程,一个唤醒线程只能唤醒一个等待线程,程序出现阻塞
  public static void main(String[] args) throws InterruptedException {

    Object lock = new Object();

    ThreadA a = new ThreadA(lock);
    new ThreadA(lock).start();
    new ThreadA(lock).start();
    new ThreadA(lock).start();

    Thread.sleep(1000);

    NotifyThread notifyThread = new NotifyThread(lock);
    notifyThread.start();

  }

}

class ThreadA extends Thread {
  private Object lock;

  public ThreadA(Object lock) {
    super();
    this.lock = lock;
  }

  @Override
  public void run() {
    Service service = new Service();
    service.testMethod(lock);
  }

}

class NotifyThread extends Thread {
  private Object lock;

  public NotifyThread(Object lock) {
    super();
    this.lock = lock;
  }

  @Override
  public void run() {
    synchronized (lock) {
      //notify()只能唤醒一个线程,nofiyAll()唤醒所有的等待线程
      lock.notify();
//     lock.notifyAll();
    }
  }

}

class Service {

  public void testMethod(Object lock) {
    try {
      synchronized (lock) {
        System.out.println("begin wait() ThreadName="
            + Thread.currentThread().getName());
        lock.wait();
        System.out.println(" end wait() ThreadName="
            + Thread.currentThread().getName());
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

}

在没有使用synchronized关键字保证同步,而采用Lock的程序。Java提供了condition类来保持协调。Lock替代synchronized,Condition替代同步监视器功能。
await: 类似于wait。
signal:唤醒在此Lock对象上等待的单个线程,选择是任意的。
signalAll:唤醒在此Lock对象上等待的所有线程,选择是任意的。

public class Account
{
  //显示定义Lock对象
  private final Lock lock = new ReentrantLock();
  //获得指定Lock对象对应的条件变量
  private final Condition cond = lock.newCondition(); 

  private String accountNo;
  private double balance;

  //标识账户中是否已经存款的旗标
  private boolean flag = false;

  public Account(){}

  public Account(String accountNo , double balance)
  {
    this.accountNo = accountNo;
    this.balance = balance;
  }

  public void setAccountNo(String accountNo)
  {
    this.accountNo = accountNo;
  }
  public String getAccountNo()
  {
     return this.accountNo;
  }

  public double getBalance()
  {
     return this.balance;
  }
  public void draw(double drawAmount)
  {
    //加锁
    lock.lock();
    try
    {
      //如果账户中还没有存入存款,该线程等待
      while(!flag)
      {
        cond.await();
      }
        //执行取钱操作
        System.out.println(Thread.currentThread().getName() + 
          " 取钱:" + drawAmount);
        balance -= drawAmount;
        System.out.println("账户余额为:" + balance);
        //将标识是否成功存入存款的旗标设为false
        flag = false;
        //唤醒该Lock对象对应的其他线程
        cond.signalAll();
    }
    catch (InterruptedException ex)
    {
      ex.printStackTrace();
    }
    //使用finally块来确保释放锁
    finally
    {
      lock.unlock();
    }
  }
  public void deposit(double depositAmount)
  {
    lock.lock();
    try
    {
      //如果账户中已经存入了存款,该线程等待
      while(flag)
      {
        cond.await();        
      }

        //执行存款操作
        System.out.println(Thread.currentThread().getName() + 
          " 存款:" + depositAmount);
        balance += depositAmount;
        System.out.println("账户余额为:" + balance);
        //将标识是否成功存入存款的旗标设为true
        flag = true;
        //唤醒该Lock对象对应的其他线程
        cond.signalAll();
    }
    catch (InterruptedException ex)
    {
      ex.printStackTrace();
    }
    //使用finally块来确保释放锁
    finally
    {
      lock.unlock();
    }
  }

  public int hashCode()
  {
    return accountNo.hashCode();
  }
  public boolean equals(Object obj)
  {
    if (obj != null && obj.getClass() == Account.class)
    {
      Account target = (Account)obj;
      return target.getAccountNo().equals(accountNo);
    }
    return false;
  }
}

使用notify()/notifyAll()方法进行通知时,被通知的线程却是JVM随机选择的。当notifyAll()通知所有WAITING线程,没有选择权,会出现相当大的效率问题。但是ReentrantLock结合Condition类可以”选择性通知”。Condition可以实现唤醒部分指定线程,这样有助于程序运行的效率。

从JDK1.5之后,Java提供了Callable接口,实际上就是Runnable接口的增强版。提供call方法作为线程执行体,但是功能更加强大。
Callable接口不是Runnable接口,不能直接作为Thread的target,有返回值得call方法也不能直接运行。这里需要一个包装器Future。

import java.util.concurrent.*;

class RtnThread implements Callable<Integer>
{
  //实现call方法,作为线程执行体
  public Integer call()
  {
    int sum = 0;
    int i=0;
    for ( ; i < 10 ; i++ )
    {
      System.out.println(Thread.currentThread().getName()+ " 的循环变量i的值:" + i); 
      sum+=i;
    }
    //call()方法可以有返回值

    return sum;
  }
} 

public class CallableTest
{
  public static void main(String[] args) 
  {
    //创建Callable对象
    RtnThread rt = new RtnThread();
    //使用FutureTask来包装Callable对象
    FutureTask<Integer> task = new FutureTask<Integer>(rt);
    for (int i = 0 ; i < 10 ; i++)
    {
      System.out.println(Thread.currentThread().getName()
        + " 的循环变量i的值:" + i);
      if (i == 5)
      {
        //实质还是以Callable对象来创建、并启动线程
        new Thread(task , "有返回值的线程").start();
      }
    }
    try
    {
      //获取线程返回值
      System.out.println("子线程的返回值:" + task.get());          
    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
  }
}
import java.util.concurrent.*;

//实现Runnable接口来定义一个简单的
class TestThread implements Runnable
{
  public void run()
  {
    for (int i = 0; i < 10 ; i++ )
    {
      System.out.println(Thread.currentThread().getName()
        + "的i值为:" + i);
    }
  }
}

public class ThreadPoolTest
{
  public static void main(String[] args) 
  {
    //创建一个具有固定线程数(6)的线程池
    ExecutorService pool = Executors.newFixedThreadPool(6);
    //向线程池中提交3个线程
    pool.execute(new TestThread());
    Future f1=pool.submit(new TestThread());
    Future f2=pool.submit(new TestThread());

    Future<Integer> f3=pool.submit(new RtnThread());

    try
    {
      if(f1.get()==null&&f2.get()==null&&f3.get()!=null){
        System.out.println("执行完毕!");
        System.out.println(f3.get());

      }
      //获取线程返回值

    }
    catch (Exception ex)
    {
      ex.printStackTrace();
    }
    //关闭线程池
    pool.shutdown();
  }
}

更多java相关内容感兴趣的读者可查看本站专题:《Java进程与线程操作技巧总结》、《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总

希望本文所述对大家java程序设计有所帮助。

您可能感兴趣的文章:
阅读全文