java 多线程程序_java 多线程和并行程序设计

多线程使得程序中的多个任务可以同时执行

在一个程序中允许同时运行多个任务。在许多程序设计语言中,多线程都是通过调用依赖系统的过程或函数来实现的

a111e3137271e7038654fafcbb7ddcd3.png

542e4330694760e5ce71a4e071c5d631.png

c298a6ee18aaaab7016aa521a6fe6610.png

5b0862400a541865cc64ca249be5afef.png

2b3fd7a2a3a8b9132bb71041167857a3.png

为什么需要多线程?多个线程如何在单处理器系统中同时运行?

多线程可以使您的程序更具响应性和交互性,并提高性能。在许多情况下需要多线程,例如动画和客户端/服务器计算。因为大多数时候CPU处于空闲状态 - 例如,CPU在用户输入数据时什么都不做 - 多个线程在单处理器系统中共享CPU时间是切实可行的。

什么是可运行的对象?什么是线程?

Runnable的一个实例是一个可运行的对象。线程是用于执行可运行任务的可运行对象的包装对象。

1、创建任务和线程

一个任务类必须实现Runnable接口。任务必须从线程运行。

1ceb824e60012e3fc7bd95dc7b28cea4.png

一旦定义了一个TaskClass,就可以用它的构造方法创建一个任务。

例子:

TaskClass task = new TaskClass();

任务必须在线程中执行。使用下面的语句创建任务的线程

Thread thread = new Thread(task);

然后调用start()方法告诉Java虚拟机该线程准备运行

thread.start()

例子:

7262df50fe2e4ae181fcdc5571a11d7e.png

public classTaskThreadDemo {public static voidmain(String [] args) {

Runnable printA= new PrintChar('a', 100);

Runnable printB= new PrintChar('b', 100);

Runnable printNum= new PrintNum(100);

Thread thread1= newThread(printA);

Thread thread2= newThread(printB);

Thread thread3= newThread(printNum);

thread1.start();

thread2.start();

thread3.start();

}

}class PrintChar implementsRunnable{private charcharToPrint;private inttimes;public PrintChar(char c,intt) {

charToPrint=c;

times=t;

}

@Overridepublic voidrun() {for(int i=0; i

System.out.print(charToPrint);

}

}

}class PrintNum implementsRunnable{private intlastNumber;public PrintNum(intn) {

lastNumber=n;

}

@Overridepublic voidrun() {for(int i=1; i<=lastNumber; i++) {

System.out.print(" " +i);

}

}

}

2、Thread类

ceb4a16d8a036becac35c8f31b2a1aa2.png

8596a0bf4d6665728387c8961221660a.png

58650c90554ebcb595835ad4584e16a0.png

7bba797243e4e205f638f8e897481b82.png

b0f3316b890dc10e735438a74759199e.png

bd7181104684706e002ea20c74eb8ec2.png

4061f822521c9202932b17de55ce7542.png

07cf90ee8b9fd4aa11ef72e971d3ea49.png

6119ffd7b140140a6438ba0c92d6bae3.png

3、线程池

之前运用实现Runnable接口来定义一个任务类,以及如何创建一个线程来运行一个任务

40caf009f526c6559a8c054564bdbcfc.png

该方法对大量的任务而言是不够高效的,为每个任务开始一个新线程可能会限制吞吐量并且造成性能降低。

线程池是管理并发执行任务个数的理想方法。

Java提供Executor接口来执行线程池中的任务,提供ExecutorService接口来管理和控制任务。

Executorservice是Executor的子接口

9896f8efc29dba91b3cfe5259245666b.png

为了创建一个Executor对象,可以使用Executor类中的静态方法

7322b8742a128e809a3e2df45bf02fc1.png

9a1143e85efcbcae5f406ae1262224d7.png

例子:

public classTaskThreadDemo {public static voidmain(String [] args) {

ExecutorService executor= Executors.newFixedThreadPool(3);//ExecutorService executor = Executors.newCachedThreadPool();

executor.execute(new PrintChar('a', 100));

executor.execute(new PrintChar('b', 100));

executor.execute(new PrintNum(100));

executor.shutdown();

}

}

4、线程同步

例子:

public classAccountWithoutSync {private static Account account = newAccount();public static voidmain(String[] args) {

ExecutorService executorService=Executors.newCachedThreadPool();for (int i = 0; i < 100; i++) {

executorService.execute(newAddAPennyTask());

}

executorService.shutdown();//等待 全部任务 完成

while(!executorService.isTerminated()) {

}

System.out.println(account.getBalance());

}private static class AddAPennyTask implementsRunnable{

@Overridepublic voidrun() {//TODO Auto-generated method stub

account.deposit(1);

}

}public static classAccount {private int balance = 0;public intgetBalance() {returnbalance;

}public void deposit(intamount) {int newBalance = balance +amount;try{

Thread.sleep(5);

}catch(InterruptedException e) {//TODO: handle exception

}

balance= balance +newBalance;

}

}

}

0e4985824fc730cb90c0ef15622ce5ea.png

这个例子中出错了

原因是:

b274468ba336b233dd3bb31dc8a92941.png

因为线程不安全,所以数据遭到破坏

synchronized关键字

为避免竞争状态,应该防止多个线程同时进入程序的某一特定部分,程序中的这部分称为临界区。

使用关键字synchronized来同步方法,以便一次只有一个线程可以访问这个方法

例子:

60d86b468fa5076cd2a960acc4af75b1.png

9a75b471920ca712261563175f11f80c.png

b697c360b345bb57abacfcde4fba5a9a.png

调用一个对象上的同步实例方法,需要给该对象加锁。而调用一个类上的同步静态方法,需要给该类加锁。

同步语句

当执行方法中某一个代码块时,同步语句不仅可用与this对象加锁,而且可用于对任何对象加锁。这个代码块称为同步块。同步语句的一般形式如下:

3bb74bf843d3f31ee6f7b934d2f8813e.png

利用加锁同步

之前的用的同步实例方法

acac878c3027a3992ccf086ebe59ea4c.png

实际上 在执行之前都隐式地需要一个加在实例上的锁

888d8b6cd9a719fd23eaea6e95343cb0.png

d581d746505250001b5973b933b17199.png

e44708c66570d66ccf49d358306bff6b.png

例子:

部分代码:

public static classAccount {private int balance = 0;private Lock lock = newReentrantLock(); //创建一个锁public intgetBalance() {returnbalance;

}public void deposit(intamount) {

lock.lock();//获取该锁try{int newBalance = balance +amount;

Thread.sleep(5);

balance=newBalance;

}catch(InterruptedException e) {//TODO: handle exception

}finally{

lock.unlock();//释放该锁

}

}

}

线程间协作

544df1567f565413a78b3ef2adf28a88.png

0c856b7d99f56a5ed8a535df0c22ce12.png

例子:

367d84dd1cf483bb6fb8a9b140a7b27c.png

public classThreadCooperation {private static Account account = newAccount();public static voidmain(String [] args) {

ExecutorService executorService= Executors.newFixedThreadPool(2);

executorService.execute(newDepositTask());

executorService.execute(newwithdrawTask());

executorService.shutdown();

System.out.println("Thread 1 \t Thread 2 \t Balance");

System.out.println();

}public static class DepositTask implementsRunnable{

@Overridepublic voidrun() {try{while(true) {

account.deposit((int)(Math.random() * 10) + 1);

Thread.sleep(1000);

}

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}public static class withdrawTask implementsRunnable{

@Overridepublic voidrun() {while(true) {

account.withdraw((int)(Math.random() * 10) + 1);

}

}

}public static classAccount {private static Lock lock = newReentrantLock();private static Condition newDeposit = lock.newCondition(); //线程间的协作

private int balance = 0;public intgetBalance() {returnbalance;

}public void withdraw(intaccountNumber) {

lock.lock();try{while(balance

System.out.println("\t\tWait for a deposit, wait to withdraw :" +accountNumber);

newDeposit.await();

}

balance-=accountNumber;

System.out.println("\t\tWithdraw: " + accountNumber + "\t\t" + "balance: " +getBalance());

}catch(Exception e) {//TODO: handle exception

e.printStackTrace();

}finally{

lock.unlock();

}

}public void deposit(intaccountNumber) {

lock.lock();try{

balance+=accountNumber;

System.out.println("Deposit " + accountNumber + "\t\t\t\t balance: " +getBalance());

newDeposit.signalAll();

}finally{

lock.unlock();

}

}

}

}

547fdccdb9a2d7ed945ca9409664f18a.png

74d7c912d029e87476df456572bcef26.png

b7d7085c6590b4a9c1f20c88f1e56cfa.png

如何在锁上创建条件?什么是await(),signal()和signalAll()方法?

可以使用lock.newCondition()创建锁上的条件。await()方法使当前线程等待,直到发出条件信号。signal()方法唤醒一个等待线程,signalAll()方法唤醒所有等待线程。

消费者/生产者

ab1defb38831ca59f3822a13ba142cd1.png

5778736c52d1e7c4fba25c70a95870a4.png

public classComsumerProducer {private static Buffer buffer = newBuffer();public static voidmain(String [] args) {

ExecutorService excurtor= Executors.newFixedThreadPool(2);

excurtor.execute(newProducerTask());

excurtor.execute(newConsumerTask());

excurtor.shutdown();

}private static class ProducerTask implementsRunnable{

@Overridepublic voidrun() {//TODO Auto-generated method stub

try{int i = 1;while(true) {

System.out.println("Producer writes " +i);

buffer.write(i++);

Thread.sleep((int)(Math.random() * 1000));

}

}catch(InterruptedException e) {//TODO: handle exception

e.printStackTrace();

}

}

}private static class ConsumerTask implementsRunnable{

@Overridepublic voidrun() {//TODO Auto-generated method stub

try{while(true) {

System.out.println("\t\tConsumer reads " +buffer.read());

Thread.sleep((int)(Math.random() * 1000));

}

}catch(InterruptedException e) {//TODO: handle exception

e.printStackTrace();

}

}

}private static classBuffer{private static final int CAPACITY = 3;private LinkedList queue = new LinkedList<>();private static Lock lock = newReentrantLock();private static Condition notFull =lock.newCondition();private static Condition notEmpty =lock.newCondition();public void write(intvalue) {

lock.lock();try{while(queue.size() ==CAPACITY) {

System.out.println("Wait for notNull condition");

notFull.await();

}

queue.offer(value);

notEmpty.signal();

}catch(InterruptedException e) {

e.printStackTrace();

}finally{

lock.unlock();

}

}public intread() {

lock.lock();int value = 0;try{while(queue.isEmpty()) {

System.out.println("\t\tWait for notEmpty condition");

notEmpty.await();

}

value=queue.pop();

notFull.signal();

}catch(InterruptedException e) {//TODO Auto-generated catch block

e.printStackTrace();

}finally{

lock.unlock();returnvalue;

}

}

}

}

667ba36f7e891381a490483c043cf2ef.png

阻塞队列

c35884e6f294ac07c0c1c803bdb7b1b2.png

阻塞队列在试图向一个满队列添加元素或者从空队列中删除元素时会导致线程阻塞。BlockingQueue接口继承了Queue,并且提供同步的put和take方法向队列尾部添加元素,以及从队列头部删除元素

a70ac9f134b83df210bff0ec7d70f7b2.png

java支持三个具体的阻塞队列ArrayBlockingQueue、LinkedBlockingQueue和PriorityBlockingQueue

ceb8dc68bfada6f18ef3969ffb4fa4e1.png

5ad69c3596d75176079a5055faeb48a0.png

6f8b9bdf31e2dc7efd79ea4f49587acc.png

4161fab555ee59b1e834af8b8da44aa2.png

例子:

用阻塞队列做的消费者/生产者

public classConsumerProducerUsingBlockingQueue {private static ArrayBlockingQueue buffer = new ArrayBlockingQueue(2);public static voidmain(String [] args) {

ExecutorService excutor= Executors.newFixedThreadPool(2);

excutor.execute(newProducerTask());

excutor.execute(newConsumerTask());

excutor.shutdown();

}private static class ProducerTask implementsRunnable{

@Overridepublic voidrun() {//TODO Auto-generated method stub

try{int i = 1;while(true) {

System.out.println("Producer writes " +i);

buffer.put(i++);

Thread.sleep((int)(Math.random() * 1000));

}

}catch(InterruptedException e) {//TODO: handle exception

e.printStackTrace();

}

}

}private static class ConsumerTask implementsRunnable{

@Overridepublic voidrun() {//TODO Auto-generated method stub

try{while(true) {

System.out.println("\t\tConsumer reads " +buffer.take());

Thread.sleep((int)(Math.random() * 1000));

}

}catch(InterruptedException e) {//TODO: handle exception

e.printStackTrace();

}

}

}

}

18ddfea10aa4f4578da41ec574d97c39.png

信号量

e445eb4d7082012e252db7cee0a17fb8.png

17b73e373dfd3dac536842c51ca33871.png

0a0ad75b86ed937abcd3c88ba671e6d6.png

例子:

7cbc0859e8264555b94c129e83a044f1.png

锁和信号量有什么相似之处和不同之处?

锁和信号量都可用于限制对共享资源的访问。在资源上使用锁可确保只有一个线程可以访问它。在资源上使用信号量允许一个或多个指定数量的线程访问资源。

如何创建允许三个并发线程的信号量?你如何获得信号量?你如何发布信号量?

使用新的信号量(numberOfPermits)来创建信号量。调用aquire()获取信号量并调用release()来释放信号量。

避免死锁

ea86563e740f1de10958a8ed4dbfc031.png

f31c13e9f0b6236825f7b03083e09959.png

2eb47b360f77b995f36ea29c49f0ebe3.png

什么是死锁?你怎么能避免死锁?

在两个或多个线程获取多个对象上的锁并且每个对象都锁定一个对象并且正在等待另一个对象上的锁定的情况下发生死锁。资源排序技术可用于避免死锁。

同步合集

c6e99d7cf5f8ca0abf9d0db83e8de1eb.png

5edc5ca89959439a7b62bfff1c7f148e.png

并行编程

774a6b6666e9082b4ee39a50d1181a79.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值