多线程总结——开篇

Java多线程一直是面试中的必考点,满打满算学习Java也有一年的时间了。从开始入门的C语言到现在的JavaEE,过程中学了非常多的东西,但是很多基础由于开始理解的不够透彻。最近想放慢一些脚步,将自己基础扎实后再去攻坚三大框架,所以总结内容基本上都是JavaSE,数据结构算法之类的内容。


线程与进程

这两个概念很容易理解,我们可以认为线程为进程的子集,一个进程可以包含多个线程,这些线程独立工作,执行着不同的任务。在内存方面,每个进程拥有自己的内存空间,而所有线程则拥有同一片内存空间。

Java中多线程内存模型

我们在聊多线程具体内容前,有必要总结一下内存模型。由于Java程序运行与虚拟机之上,所以我们可以通过计算机的类比来理解。当一条线程运行时,其过程应该是从内存获取共享变量副本,将该副本存于CPU内的高速缓存(cache)之中 。CPU执行完成后再将数据写回主存中。

关于Java内存模型中的一些规则。

  • 线程内的代码能够按先后顺序执行,这被称为程序次序规则。
  • 对于同一个锁,一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前,也叫做管程锁定规则。
  • 前一个对volatile的写操作在后一个volatile的读操作之前,也叫volatile变量规则。
  • 一个线程内的任何操作必需在这个线程的start()调用之后,也叫作线程启动规则。
  • 一个线程的所有操作都会在线程终止之前,线程终止规则。
  • 一个对象的终结操作必需在这个对象构造完成之后,也叫对象终结规则。
  • 可传递性

该图即为线程的内存模型,正因为该模型,出现了缓存一致性问题,关于这点,不赘述。在Volatile关键词的总结中有详细说明。


Java中多线程

Java中已经为我们提供了类和接口来供我们实现多线程处理。我们实现一个多线程可以通过继承Thread类和继承Runnable接口。而在大多数情况下,我们都通过继承Runnable接口来实现Run方法。

public class ThreadTest implements Runnable{
        //复写方法
	@Override
	public void run() {
                //在这里写需要执行的多线程代码
		// TODO Auto-generated method stub
		doSomething();	
	}
	
	public static void main(String[] args) {
                //创建本类对象
		ThreadTest t = new ThreadTest();
		//在这里,新建一个线程,并将实例的对象传参
		Thread t1 = new Thread(t);
                //启动线程
		t1.start();
	}
}

上述内容就是创建一个多线程的过程。


下面我们来看一个对于线程的各个方法,首先先看一下一个线程运行时的状态。

我们知道,CPU每个Core在同一时间只能运行同一条线程,那么有必要了解下线程的状态。

  • 初始化状态          当线程被创建,并未进行对CPU执行权的竞争
  • 就绪状态              当线程被创建,并调用start方法启动线程,此时,该线程开始竞争CPU执行权,一旦获取将会执行
  • 运行状态              获取到CPU执行权后运行状态
  • 阻塞状态              因为某种原因放弃了CPU执行权,等待某个条件后重新进入Runnable状态去竞争执行权
  • 销毁线程              线程生命周期结束,被销毁
public static void main(String[] args) {
		ThreadTest t = new ThreadTest();
		//此时为新建状态
		Thread t1 = new Thread(t);
		//进入准备状态,开始竞争执行权
		t1.start();
		//此时线程进入阻塞状态,等休眠定时结束后,重新竞争执行权
		t1.sleep(1000);	
	}

虽然在代码中并没有体现出运行状态,但是,实际上Run方法已经在start内部被执行,所以我们可以将start方法看着同时就绪并执行,但是这两步并不一定同时发生,调用方法同时该线程就参与了CPU执行权的竞争,但是何时执行不能确定。当同一时间有多个线程都在就绪状态时,这些线程都有概率获取到执行权,而概率实际上是有所不同的,这就涉及到线程优先级的问题。这方面内容留待之后的两篇文章中进行详细的总结。


wait / notify方法

wait方法能够让线程进入阻塞状态,而notify方法能够让唤醒进入阻塞状态的线程。而这两个方法一般都会配合synchronized 关键字使用,一般会写入到synchronized 代码块中。因为在synchronized 代码块中,执行wait的当前线程必然已经获得了锁,此时调用wait方法会放弃CPU的执行权,进入阻塞状态,知道另一个线程使用notify唤醒后再去竞争CPU执行权。

而notify/notifyAll方法在执行的一瞬间,能够唤醒所有的线程。但需要注意的是,这时并不一定会立即释放锁,具体何时释放,要看代码块的编写方式。所以我们尽量在使用notify后将当前线程退出临界条件。

而notify方法和notifyAll方法区别在于,前者调用后只会唤醒多个阻塞线程中的一个线程,至于唤醒哪一个,这将有CPU进行管理,而notifyAll方法将唤醒所有线程。

我们通过一个非常著名的模型来理解这两个方法

生产者——消费者模型

和我们生活中的场景完全相同,生产者负责生产商品,消费者负责购买商品。当库存不足时,消费者将无法进行购买,而库存满后,生产者将停止生产。我们用编程思维来理解这个问题,我们将库存视为共享变量,生产者和消费者分别看作两条不同的线程,这两条线程将对该变量进行增减操作,当消费者连续购买直到库存为零时,这时消费者需要调用wait方法,停止购买,同时调用notify,来启动生产者的线程,开始生产商品,而生产者生产到一定的产量后,库存满,调用wait方法停止生产,并调用notify方法来告知消费者购买。

下面是该模型的代码实现。

import java.io.IOException;

public class WaitAndNotify
{
	public static void main(String[] args) throws IOException
	{
		Person person = new Person();
		new Thread(new Consumer("消费者一", person)).start();
		new Thread(new Consumer("消费者二", person)).start();
		new Thread(new Consumer("消费者三", person)).start();

		new Thread(new Producer("生产者一", person)).start();
		new Thread(new Producer("生产者一", person)).start();
		new Thread(new Producer("生产者一", person)).start();

	}
}

class Producer implements Runnable
{
        //将生产者消费者都归位person类
	private Person person;
	private String producerName;

	public Producer(String producerName, Person person)
	{
		this.producerName = producerName;
		this.person = person;
	}

	@Override
	public void run()
	{       
                //将所有内容封装到run方法中
		while (true)
		{
			try
			{
				person.produce();
			} catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}

}

class Consumer implements Runnable
{

	private Person person;
	private String consumerName;

	public Consumer(String consumerName, Person person)
	{
		this.consumerName = consumerName;
		this.person = person;
	}

	@Override
	public void run()
	{
		try
		{
			person.consume();
		} catch (InterruptedException e)
		{
			e.printStackTrace();
		}

	}

}

class Person
{       
        //库存初始
	private int foodNum = 0;
        //监视器对象
	private Object synObj = new Object();
        //最大库存
	private final int MAX_NUM = 5;

	public void produce() throws InterruptedException
	{
		synchronized (synObj)
		{       //当到达库存时,停止生产
			while (foodNum == 5)
			{
				System.out.println("box is full,size = " + foodNum);
				synObj.wait();
			}
			foodNum++;
			System.out.println("produce success foodNum = " + foodNum);
                        //唤醒消费者线程
			synObj.notifyAll();
		}

	}    
         
        //同理
	public void consume() throws InterruptedException
	{
		synchronized (synObj)
		{
			while (foodNum == 0)
			{
				System.out.println("box is empty,size = " + foodNum);
				synObj.wait();
			}
			foodNum--;
			System.out.println("consume success foodNum = " + foodNum);
			synObj.notifyAll();
		}

	}
}

猜你喜欢

转载自blog.csdn.net/weixin_41582192/article/details/81738059
今日推荐