java多线程

  1. 创建任务和线程

为任务定义一个类,这个类必须实现runnable接口,runnable接口只包含一个run办法,这个方法告诉系统线程将如何运行。

>>> TaskClass task = new TaskClass(…);

 

创建任务的线程

>>> Thread thread = new Thread(task);

 

start()方法告诉java虚拟机该线程准备运行。线程启动,run()方法执行。

>>> thread.start()


Eg.创建三个任务以及三个运行这些任务的线程:

第一个任务打印字母a 100次。

第二个任务打印字母b 100次。

第三个任务打印1-100的整数。

三个线程将共享cpu,并且在控制台上轮流打印字母和数字。


public class TaskThreadDemo {
  public static void main(String[] args) {
    // Create tasks
    Runnable printA = new PrintChar('a', 100);
    Runnable printB = new PrintChar('b', 100);
    Runnable print100 = new PrintNum(100);

    // Create threads
    Thread thread1 = new Thread(printA);
    Thread thread2 = new Thread(printB);
    Thread thread3 = new Thread(print100);

    // Start threads
    thread1.start();
    thread2.start();
    thread3.start();
  }
}

// The task for printing a specified character in specified times
class PrintChar implements Runnable {
  private char charToPrint; // The character to print
  private int times; // The times to repeat

  /** Construct a task with specified character and number of
   *  times to print the character
   */
  public PrintChar(char c, int t) {
    charToPrint = c;
    times = t;
  }

  /** Override the run() method to tell the system
   *  what the task to perform
   */
  public void run() {
    for (int i = 0; i < times; i++) {
      System.out.print(charToPrint);
    }
  }
}

// The task class for printing number from 1 to n for a given n
class PrintNum implements Runnable {
  private int lastNum;

  /** Construct a task for printing 1, 2, ... i */
  public PrintNum(int n) {
    lastNum = n;
  }

  /** Tell the thread how to run */
  public void run() {
    for (int i = 1; i <= lastNum; i++) {
      System.out.print(" " + i);
    }
  }
}

2. thread

    sleep(long mills)可以将该线程设置为休眠以确保其他xian线程的执行,休眠时间为指定的毫秒数。sleep方法可能抛出一     InterruptedException必检异常,必须放进try-catch中。

yield() 线程让步。为其他线程临时让出cpu时间。表示暂停当前线程,执行其他线程(包括自身线程),由cpu决定

join() 使一个线程等待另一个线程结束,阻塞所在线程,等调用它的线程执行完毕,再向下执行。


  1. 线程池

线程池是管理并发执行任务个数的理想方法。java提供executor接口来执行线程池中的任务,提供ExecutorService接口来管理 和控制任务。ExecutorServiceexecutor的子接口。

如果需要为一个任务创建一个线程,就使用thread类。如果需要为多个任务创建线程,最好使用线程池。



>>> ExecutorService executor =Executors.newFixedThreadPool(3);

创建一个最大线程数为3的线程池执行器。

 

>>> ExecutorService executor =Executors.newFixedThreadPool(1);

可运行的任务将顺次执行,因为在线程池中只有一个线程。

 

>>> ExecutorService executor =Executors.newFixedThreadPool( );

为每个等待的任务创建一个新线程,所以,所有的任务都并发地执行。


import java.util.concurrent.*;

public class ExecutorDemo {
  public static void main(String[] args) {
    // Create a fixed thread pool with maximum three threads
    ExecutorService executor = Executors.newFixedThreadPool(3);

    // Submit runnable tasks to the executor
    executor.execute(new PrintChar('a', 100));
    executor.execute(new PrintChar('b', 100));
    executor.execute(new PrintNum(100));

    // Shut down the executor
    executor.shutdown();
  }
}

  1. 4. 线程同步

如果一个共享资源被多个线程同时访问,可能会遭到破坏。

添加关键字 synchronized可以避免竞争状态的发生。

一个同步方法在执行之前需要加锁。对于实例方法,要给调用该方法的对象加锁。对于静态方法,要给这个类加锁。如果一个线 程调用一个对象上的同步实例方法(静态方法),首先要给该对象(类)加锁,然后执行该方法,最后解锁。在解锁之前,另一 个调用那个对象(类)中方法的线程将被阻塞,直到解锁。

 

调用一个对象的同步实例方法要求给该对象加锁。调用一个类的同步静态方法要求对该类加锁。当执行方法中某一个代码块时, 同步语句不仅可用于对this对象加锁,而且可用于对任何对象加锁。这个代码成为同步块。


synchronized (expr) {

statements;

}

synchronized (account) {

account.deposit(1);

}

expr必须求出对象的引用。同步语句允许设置同步方法中的部分代码,而不必是整个方法。这大大增强了程序的并发能力。

任何同步的实例方法都可以转换为同步语句。

A.

public synchronized void xMethod() {

//method body

}

B.

public void xMethod() {

synchronized (this) {

//method body

}

}



另一种方法是利用加锁同步


package chapter32;

import java.util.concurrent.*;
import java.util.concurrent.locks.*;

public class AccountWithSyncUsingLock {
  private static Account account = new Account();

  public static void main(String[] args) {
    ExecutorService executor = Executors.newCachedThreadPool();

    // Create and launch 100 threads
    for (int i = 0; i < 100; i++) {
      executor.execute(new AddAPennyTask());
    }

    executor.shutdown();

    // Wait until all tasks are finished
    while (!executor.isTerminated()) {
    }

    System.out.println("What is balance ? " + account.getBalance());
  }

  // A thread for adding a penny to the account
  public static class AddAPennyTask implements Runnable {
    public void run() {
      account.deposit(1);
    }
  }

  // An inner class for account
  public static class Account {
    private static Lock lock = new ReentrantLock(); // Create a lock
    private int balance = 0;

    public int getBalance() {
      return balance;
    }

    public void deposit(int amount) {
      lock.lock(); // Acquire the lock

      try {
        int newBalance = balance + amount;

        // This delay is deliberately added to magnify the
        // data-corruption problem and make it easy to see.
        Thread.sleep(5);

        balance = newBalance;
      }
      catch (InterruptedException ex) {
      }
      finally {
        lock.unlock(); // Release the lock
      }
    }
  }
}

33行创建一个锁,41行获取该锁,55行释放该锁。

在对lock()的调用后紧随一个try-catch块并且在finally子句中释放这个锁是一个很好的习惯。



5. 线程协作

有时候线程之间需要相互协作。使用条件便于线程间通信。一个线程可以置顶在某种条件下该做什么。条件是通过调用lock对象 newCondition()方法而创建的对象。一旦创建了条件,就可以使用await()signal()signalAll()方法来实现线程之间的相互 通信。

await() 让当前线程都处于等待状态,直到条件发生。

signal() 唤醒一个等待的线程

signalAll() 唤醒所有等待的线程




假设创建并启动两个任务,一个用来向账户中存款,另一个从同一账户中提款。当提款的数额大于账户的当前余额是,提款线程 必须等待。不管什么时候,只要向账户新存入一笔资金,存储线程必须通知提款线程重新尝试。如果余额仍未达到提款的数额, 提款线程必须继续等待新的存款。

为了同步这些操作,使用一个具有条件的锁newDeposit.如果余额小于取款数额,提款任务将等待newDeposit条件。当存款任务 给账户增加资金时,存款任务缓行等待中的提款任务再次尝试。

lock对象中创建条件。为了使用条件,必须首先获取锁。Await()让线程等待并且自动释放条件上的锁,一旦条件正确,线程重 新获取锁并且继续执行。

import java.util.concurrent.*;
import java.util.concurrent.locks.*;

public class ThreadCooperation {
  private static Account account = new Account();

  public static void main(String[] args) {
    // Create a thread pool with two threads
    ExecutorService executor = Executors.newFixedThreadPool(2);
    executor.execute(new DepositTask());
    executor.execute(new WithdrawTask());
    executor.shutdown();

    System.out.println("Thread 1\t\tThread 2\t\tBalance");
  }

  // A task for adding an amount to the account
  public static class DepositTask implements Runnable {
    public void run() {
      try { // Purposely delay it to let the withdraw method proceed
        while (true) {
          account.deposit((int)(Math.random() * 10) + 1);
          Thread.sleep(1000);
        }
      }
      catch (InterruptedException ex) {
        ex.printStackTrace();
      }
    }
  }

  // A task for subtracting an amount from the account
  public static class WithdrawTask implements Runnable {
    public void run() {
      while (true) {
        account.withdraw((int)(Math.random() * 10) + 1);
      }
    }
  }

  // An inner class for account
  private static class Account {
    // Create a new lock
    private static Lock lock = new ReentrantLock();

    // Create a condition
    private static Condition newDeposit = lock.newCondition();

    private int balance = 0;

    public int getBalance() {
      return balance;
    }

    public void withdraw(int amount) {
      lock.lock(); // Acquire the lock
      try {
        while (balance < amount) {
          System.out.println("\t\t\tWait for a deposit");
          newDeposit.await();
        }
        balance -= amount;
        System.out.println("\t\t\tWithdraw " + amount +
          "\t\t" + getBalance());
      }
      catch (InterruptedException ex) {
        ex.printStackTrace();
      }
      finally {
        lock.unlock(); // Release the lock
      }
    }

    public void deposit(int amount) {
      lock.lock(); // Acquire the lock
      try {
        balance += amount;
        System.out.println("Deposit " + amount +
          "\t\t\t\t\t" + getBalance());

        // Signal thread waiting on the condition
        newDeposit.signalAll();
      }
      finally {
        lock.unlock(); // Release the lock
      }
    }
  }
}

一旦线程调用条件上的await(),线程就进入等待状态,等待恢复的信号。如果忘记对状态调用signal()或者signalAll(),那么线程就 永远等待下去。

条件由lock对象创建,为了调用任意方法(await() signal()signalAll()),首先必须拥有锁。如果没有获取锁就调用这些方 法,会抛出IllegalMonitorStateException异常。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值