并发与多线程

并发

  并发(concurrency)是指CPU在某个时间段内交替处理多任务的能力。每个CPU不可能只顾着执行某个进程,而让其他进程一直等待被执行。所以,CPU把可执行时间均分成若干份,每个进程执行一份或多份时间后,记录当前的工作状态,释放相关资源并进入等待状态,让其他进程抢占CPU等资源。

  在并发环境下,由于程序的封闭性被打破,出现了以下特点:

  1.并发程序之间有相互制约的关系。直接制约体现在一个程序需要另一个程序的计算结果;间接制约体现在多个进程竞争共享资源。

  2.并发程序的执行过程是断断续续的。程序需要保留现场,记忆现场指令及执行点。

  3.当并发数设置合理并且CPU拥有足够的处理能力时,并发会提高程序的运行效率。

  在Java编程中,并发主要与线程有关。

线程

  线程是CPU调度和分派的基本单位,为了更充分地利用CPU资源,一般都会使用多线程进行处理。多线程的作用是提高任务的平均执行速度,但是会导致程序可解性变差,编程难度加大。所以,合适的线程数才能让CPU资源被充分利用。

  每一个线程都有自己的操作栈、程序计数器、局部变量表等资源。同一进程内的所有线程都可以共享该进程的所有资源。

  Java提供了两种形式定义线程类:

  1.实现Runnable接口并重写其中的run()方法。

 1 class Consumer implements Runnable {
 2 
 3     private Store store;
 4 
 5     public Consumer(Store store) {
 6         this.store = store;
 7     }
 8 
 9     @Override
10     public void run() {
11         for (int i = 0; i < 1000; i++) {
12             store.getValue();
13         }
14     }
15 
16 }
Consumer

  2.继承Thread类并重写其中的run()方法。

 1 class Producer extends Thread {
 2 
 3     private Store store;
 4 
 5     public Producer(Store store) {
 6         this.store = store;
 7     }
 8 
 9     @Override
10     public void run() {
11         for (int i = 0; i < 1000; i++) {
12             store.setValue((int) (Math.random() * 100));
13         }
14     }
15 
16 }
Producer

  里氏代换原则对继承的一个约束是子类不重写父类的非抽象方法,而Thread类的run()方法不是一个抽象方法,所以继承Thread类并重写其中的run()方法就不符合里氏代换原则,该方式不推荐使用。相比之下,实现Runnable接口可以使编程更加灵活,对外暴露的细节也比较少,让使用者专注于实现线程的run()方法。

线程状态

  线程的生命周期分为以下5种状态:

新建状态

  新建状态是线程被创建且未启动的状态。也就是说,初始化一个线程对象时,该对象进入新建状态。

  线程对象的初始化分为2种:

  1.如果是继承Thread类的线程类,则该类线程对象可以直接通过new运算进行初始化。

  2.如果是实现Runnable接口的线程类,则该类线程对象通过new运算进行初始化后需要包装为一个Thread对象。

就绪状态

  就绪状态是线程启动后运行之前的状态。即启动了的线程在准备执行run()方法时的状态。

  线程的启动是指线程对象调用Thread的start()方法。

运行状态

  运行状态是线程运行时的状态,即启动了的线程在执行run()方法时的状态。

阻塞状态

  阻塞状态分以下3种情况:

  同步阻塞:缺少资源无法继续运行。抢占到资源后会退出该状态。

  主动阻塞:主动让出CPU执行权,即线程执行Thread的sleep()方法之后的状态。调用sleep()方法时会传入一个long类型的参数,表示睡眠的时间,单位为毫秒,时间结束时会退出该状态。

  等待阻塞:进入睡眠,即线程执行Object的wait()方法之后的状态。其他线程执行Object的notify()方法或notifyAll()方法之后会退出该状态。

终止状态

  终止状态是线程执行结束或因异常退出后的状态。

线程同步

  线程同步机制的主要任务是,对多个相关线程在执行次序上进行协调,使并发执行的每个线程之间能按照一定的时序共享资源,并能很好地相互合作,从而使程序的执行具有可再现性。

  资源的共享分为两种方式:

  互斥共享方式:某些资源例如打印机、磁带机等,一次只能给一个线程使用,当一个线程申请该资源时,如果该资源有其他线程在使用,则该线程需要等待,直到资源被释放之后才能申请。

  同时访问方式:某些资源例如磁盘设备等,一次可以给多个线程“同时”访问,这种“同时”是宏观上的,实际上还是多个线程交替访问。

  临界资源指的是一段时间内只能由一个线程访问的资源,而临界区指的是每个线程中访问临界资源的那部分代码。显然,若能保证每个线程互斥地进入自己的临界区,便可以实现每个线程对临界资源的互斥访问。为此,需要在每个线程进入临界区前需要对访问的临界资源进行检查,如果它是空闲的,则进入临界区;否则等待,直到临界资源空闲。具体流程如下:

  进入区:检查临界资源的状态,如果空闲,则将其状态改为被访问,并进入临界区;如果被访问,则循环等待,直到其状态变为空闲。

  临界区:访问临界资源。

  退出区:将临界资源的状态改为空闲,并释放临界资源。

  Java提供synchronized关键字标识方法或代码块,被标识的方法称为同步方法,被标识的代码块称为同步代码块。每个对象都有一个监视器与之关联。当线程通过该对象执行同步方法或同步代码块时,它首先试图获取监视器,如果获取到监视器,则锁定该对象,防止其他线程通过该对象执行同步方法或同步代码块,执行结束后,解锁该对象并释放监视器;如果获取不到监视器,表示有其他线程通过该对象执行同步方法或同步代码块,则会进入等待。所以,监视器的作用就相当于进入区和退出区的作用。

  例如:定义两种线程——生产者(Producer)和消费者(Consumer),生产者每次会产生一个数,消费者每次会取出一个数。Producer和Consumer线程对象通过同一个Store对象来调用Store的同步方法。

 1 class Store {
 2 
 3     private int value;
 4 
 5     public synchronized int getValue() {
 6         System.out.println("-取出" + value);
 7         return value;
 8     }
 9 
10     public synchronized void setValue(int value) {
11         this.value = value;
12         System.out.println("放入" + value);
13     }
14 
15 }
Store
1 @Test
2 void test() {
3     Store store = new Store();
4     Thread producer = new Producer(store);   // 继承Thread类的线程类对象的初始化
5     Thread consumer = new Thread(new Consumer(store));   // 实现Runnable接口的线程类对象的初始化
6     producer.start();
7     consumer.start();
8 }
test

  部分输出结果:

  

  当Consumer线程对象调用getValue()方法时,会获取监视器,锁定Store对象,直到方法返回后解锁Store对象,释放监视器;当Producer线程对象调用setValue()方法时也是如此。所以在创建Producer和Consumer线程对象时需要传入同一个Store对象。如果传入不同的Store对象,每一个Store对象都有一个监视器,则起不到锁定的效果。

  根据输出结果可以发现:取出多次数后才放入一次数,放入多次数后才取出一次数。要实现放入一个数后取出一个数的效果,则需要添加一个标识量。

  改进:在Store类中添加一个mutex标识量,当mutex为true时,表示Store内存了一个数,等待Consumer来取;为false时,表示Store内没有数,等待Producer生产数。

 1 class Store {
 2 
 3     private int value;
 4     private boolean mutex;   // mutex初始值为false,表示没有数
 5 
 6     public synchronized int getValue() {
 7         while (! mutex) {   // mutex为false时进入等待
 8             try {
 9                 wait();
10             } catch (InterruptedException e) {
11                 e.printStackTrace();
12             }
13         }
14         System.out.println("-取出" + value);
15         mutex = false;   // 取出数后将mutex置为false
16         notify();
17         return value;
18     }
19 
20     public synchronized void setValue(int value) {
21         while (mutex) {   // mutex为true时进入等待
22             try {
23                 wait();
24             } catch (InterruptedException e) {
25                 e.printStackTrace();
26             }
27         }
28         this.value = value;
29         System.out.println("放入" + value);
30         mutex = true;   // 放入数后将mutex置为true
31         notify();
32     }
33 
34 }
Store

  部分输出结果:

  

原文地址:https://www.cnblogs.com/lqkStudy/p/11135153.html