JAVA核心技术:多线程和并发编程

知识点

多进程和多线程简介

多进程概念

OS都讲过的

  • 当前的操作系统都是多任务OS,每个独立执行的任务就是一个进程。
  • OS将时间划分为多个时间片(时间很短),每个时间片内将CPU分配给某一个任务,时间片结束,CPU将自动回收,再分配给另外任务。从外部看,所有任务是同时在执行。但是在CPU上,任务是按照串行依次运行(单核CPU)。如果是多核,多个进程任务可以并行。但是单个核上,多进程只能串行执行。
  • 多进程的优点
    1. 可以同时运行多个任务
    2. 程序因IO堵塞时,可以释放CPU,让CPU为其他程序服务
    3. 当系统有多个CPU时,可以为多个程序同时服务
      • 我们的CPU不再提高频率,而是提高核数
      • 2005年Herb Sutter的文章 The free lunch is over,指明多核和并行程序才是提高程序性能的唯一办法
  • 多进程的缺点
    1. 太笨重,不好管理
    2. 太笨重,不好切换

多线程概念

  • 一个程序可以包括多个子任务,可串/并行,每个子任务可以称为一个线程
  • 如果一个子任务阻塞,程序可以将CPU调度另外一个子任务进行工作。这样CPU还是保留在本程序中,而不是被调度到别的程序(进程)去。这样,提高本程序所获得CPU时间和利用率。

多进程和多线程对比

多进程 vs 多线程

  • 线程共享数据
  • 线程通讯更高效
  • 线程更轻量级,更容易切换
  • 多个线程更容易管理

Java多线程实现

Java 多线程创建

  1. java.lang.Thread:线程继承Thread类,实现run方法
public class Thread1 extends Thread{
	public void run()
	{
		System.out.println("hello");
	}
}
  1. java.lang.Runnable接口:线程实现Runnable接口,实现run方法
public class Thread2 implements Runnable{
	public void run()
	{
		System.out.println("hello");
	}
}

Java的四个主要接口:
Clonable,用于对象克隆
Comparable,用于对象比较
Serializable,用于对象序列化
Runnable,用于对象线程化

Java 多线程启动

Thread方式:

  1. 可以提供过继承Thread类来创建线程。
  2. 通过start方法来启动线程的run方法。
public class Thread1 extends Thread{
	public void run()
	{
		System.out.println("hello");
	}
	public static void main(String[] a)
	{
		new Thread1().start();
	}
}

Runnable方法:

  1. 可以通过实现Runnable接口来创建线程
  2. 实现Runnable的对象必须包装在Thread类里面,才可以启动;不能直接对Runnable的对象进行start方法。
  3. 通过start方法来启动线程的run方法
public class Thread2 implements Runnable{
	public void run()
	{
		System.out.println("hello");
	}
	public static void main(String[] a)
	{
		new Thread(new Thread2()).start();
	}
}

第一条规则:

  1. 调用run方法来启动run方法,将会是串行运行。
  2. 调用start方法来启动run方法,将会是并行运行。
public class ThreadDemo0
{
	public static void main(String args[]) throws Exception
	{
		//new TestThread0().run(); //串行
		new TestThread0().start();  //并行
		while(true)
		{
			System.out.println("main thread is running");
			Thread.sleep(10);
		}
	}
}
 class TestThread0  	
{
	public void run() 
	{
		while(true)
		{
			System.out.println(" TestThread1 is running");
			try {
				Thread.sleep(1000); //1000毫秒
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
} 

第二条规则:

  1. main线程可能早于子线程结束。
  2. main线程和子线程都结束了,整个程序才算终止。
public class ThreadDemo2
{
	public static void main(String args[]) throws InterruptedException
	{
		new TestThread2().start();
//		while(true)
//		{
//			System.out.println("main thread is running");
//			Thread.sleep(1000);
//		}
	}
}
 class TestThread2 extends Thread
{
	public void run() 
	{
		while(true)
		{
			System.out.println("TestThread2" + 
			" is running");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
} 

第三条规则:

  1. 实现Runnable的对象必须包装在Thread类里面,才可以启动。
  2. 不能直接对Runnable的对象进行start方法。
public class ThreadDemo3
{
	public static void main(String args[])
	{
		//new TestThread3().start();
		//Runnable对象必须放在一个Thread类中才能运行
		TestThread3 tt= new TestThread3();//创建TestThread类的一个实例
		Thread t= new Thread(tt);//创建一个Thread类的实例
		t.start();//使线程进入Runnable状态
		while(true)
		{
			System.out.println("main thread is running");
			try {
				Thread.sleep(1000); //1000毫秒
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
class TestThread3 implements Runnable //extends Thread
{
	//线程的代码段,当执行start()时,线程从此出开始执行
	public void run()
	{
		while(true)
		{
			System.out.println(Thread.currentThread().getName() +
			" is running");
			try {
				Thread.sleep(1000); //1000毫秒
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

第四条规则:

  1. 一个线程对象不能多次start,多次start将报异常。
  2. 多个线程对象都start后,哪一个先执行,完全由JVM/操作系统来主导,程序员无法指定。
public class ThreadDemo4
{
	public static void main(String [] args)
	{
		TestThread4 t=new TestThread4();
		t.start();
		//t.start();
		//t.start();
		//t.start();
		TestThread4 t1=new TestThread4();
		t1.start();		
	}
}

class TestThread4 extends Thread  
{
	public void run()
	{
		while(true)
		{
			System.out.println(Thread.currentThread().getName() +
			" is running");
			try {
				Thread.sleep(1000); //1000毫秒
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

Java 多线程实现对比

  • Thread占据了父类的名额,不如Runnable方便
  • Thread 类实现Runnable
  • Runnable启动时需要Thread类的支持
  • Runnable 更容易实现多线程中资源共享
  • 结论:建议实现Runnable接口来完成多线程

Java多线程信息共享

  • 线程类
    • 通过继承Thread或实现Runnable
    • 通过start方法,调用run方法, run方法工作
    • 线程run结束后,线程退出
  • 粗粒度:子线程与子线程之间、和main线程之间缺乏交流
  • 细粒度:线程之间有信息交流通讯
    • 通过共享变量达到信息共享
    • JDK原生库暂不支持发送消息 (类似MPI并行库直接发送消息)

static变量

同一个Runnable类的成员变量来达到共享。

示例代码(线程卖盘)

public class ThreadDemo0
{
	public static void main(String [] args)
	{
		new TestThread0().start();
		new TestThread0().start();
		new TestThread0().start();
		new TestThread0().start();
	}
}
class TestThread0 extends Thread  
{
	//private int tickets=100;           //每个线程卖100张,没有共享
	private static int tickets=100;  //static变量是共享的,所有的线程共享
	public void run()
	{
		while(true)
		{
			if(tickets>0)
			{
				System.out.println(Thread.currentThread().getName() +
				" is selling ticket " + tickets);
				tickets = tickets - 1;
			}
			else
			{
				break;
			}
		}
	}
}

部分运行结果如下:
在这里插入图片描述

普通成员变量

示例代码

public class ThreadDemo1
{
	public static void main(String [] args)
	{
		TestThread1 t=new TestThread1();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
		new Thread(t).start();
	}
}
class TestThread1 implements Runnable
{
	private int tickets=100;
	public void run()
	{
		while(true)
		{
			if(tickets>0)
			{
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				tickets--;
				System.out.println(Thread.currentThread().getName() +" is selling ticket " + tickets);
			}
			else
			{
				break;
			}
				
		}
	}
}

TestThread1只被创建一次,就是t。
而new Thread(t)并没有创建TestThread1对象,而是把t包装成线程对象,然后启动。
第7行到第10行代码使用的是同一个TestThread1的对象t。

部分运行结果如下:
在这里插入图片描述

存在问题

  1. 工作缓存副本
    多线程内存模型
    某线程修改了自己工作缓存中的值,其他线程并不知晓,继续用自己的工作缓存中的值,但该值不能反映最新的变量值,大家都是用的前一刻变量值。

  2. 关键步骤(临界区)缺乏加锁限制
    一次只允许一个线程对某一变量进行修改操作。

volatile关键字

采用volatile 关键字修饰变量,保证不同线程对共享变量操作时的可见性。

示例代码

public class ThreadDemo2
{
	public static void main(String args[]) throws Exception 
	{
		TestThread2 t = new TestThread2();
		t.start();
		Thread.sleep(2000);
		t.flag = false;
		System.out.println("main thread is exiting");
	}
}

class TestThread2 extends Thread
{
	//boolean flag = true;   //子线程不会停止
	volatile boolean flag = true;  //用volatile修饰的变量可以及时在各线程里面通知
	public void run() 
	{
		int i=0;
		while(flag)
		{
			i++;			
		}
		System.out.println("test thread3 is exiting");
	}	
}

运行结果如下:

在这里插入图片描述

关键步骤加锁

  • 关键步骤加锁限制
    • 互斥:某一个线程运行一个代码段(关键区),其他线程不能同时运行这个代码段
    • 同步:多个线程的运行,必须按照某一种规定的先后顺序来运行
    • 互斥是同步的一种特例
  • 互斥的关键字是synchronized
    • synchronized代码块/函数,只能一个线程进入
    • synchronized加大性能负担,但是使用简便

示例代码

public class ThreadDemo3 {
	public static void main(String[] args) {
		TestThread3 t = new TestThread3();
		new Thread(t, "Thread-0").start();
		new Thread(t, "Thread-1").start();
		new Thread(t, "Thread-2").start();
		new Thread(t, "Thread-3").start();
	}
}

class TestThread3 implements Runnable {
	private volatile int tickets = 100; // 多个 线程在共享的
	String str = new String("");

	public void run() {
		while (true) {
			synchronized(str){   //同步代码块
				sale();
			}
			try {
				Thread.sleep(100);
			} catch (Exception e) {
				System.out.println(e.getMessage());
			}
			if (tickets <= 0) {
				break;
			}
		}

	}

	public synchronized void sale() { // 同步函数
		if (tickets > 0) {
			System.out.println(Thread.currentThread().getName() + " is saling ticket " + tickets--);
		}
	}
}

部分运行结果如下:

在这里插入图片描述

Java多线程管理

线程状态

  • NEW 刚创建(new)
  • RUNNABLE 就绪态(start)
  • RUNNING 运行中(run)
  • BLOCK 阻塞(sleep)
  • TERMINATED 结束
    在这里插入图片描述
  • Thread的部分API已经废弃
    • 暂停和恢复 suspend/resume
    • 消亡 stop/destroy
  • 线程阻塞和唤醒
    • sleep,时间一到,自己会醒来
    • wait/notify/notifyAll,等待,需要别人来唤醒(不被唤醒则一直等待)
    • join,等待另外一个线程结束
    • interrupt,向另外一个线程发送中断信号,该线程收到信号,会触发InterruptedException(可解除阻塞),并进行下一步处理

生产者与消费者问题

  • 生产者不断的往仓库中存放产品,消费者从仓库中消费产品。
  • 其中生产者和消费者都可以有若干个。
  • 仓库规则:容量有限,库满时不能存放,库空时不能取产品 。

主类

package product;
public class ProductTest {
	public static void main(String[] args) throws InterruptedException {
		Storage storage = new Storage();
		
		Thread consumer1 = new Thread(new Consumer(storage));
		consumer1.setName("消费者1");
		Thread consumer2 = new Thread(new Consumer(storage));
		consumer2.setName("消费者2");
		Thread producer1 = new Thread(new Producer(storage));
		producer1.setName("生产者1");
		Thread producer2 = new Thread(new Producer(storage));
		producer2.setName("生产者2");
		
		producer1.start();
		producer2.start();
		Thread.sleep(1000);		
		consumer1.start();
		consumer2.start();		
	}
}

仓库

package product;
/**
 *仓库
 */
class Storage {
	// 仓库容量为10
	private Product[] products = new Product[10];
	private int top = 0;

	// 生产者往仓库中放入产品
	public synchronized void push(Product product) {
		while (top == products.length) {
			try {
				System.out.println("producer wait");
				wait();//仓库已满,等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
        //把产品放入仓库
		products[top++] = product;
		System.out.println(Thread.currentThread().getName() + " 生产了产品"
				+ product);
		System.out.println("producer notifyAll");
		notifyAll();//唤醒等待线程
	}

	// 消费者从仓库中取出产品
	public synchronized Product pop() {
		while (top == 0) {
			try {
				System.out.println("consumer wait");
				wait();//仓库空,等待
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		//从仓库中取产品
		--top;
		Product p = new Product(products[top].getId(), products[top].getName());
		products[top] = null;
		System.out.println(Thread.currentThread().getName() + " 消费了产品" + p);
		System.out.println("comsumer notifyAll");
		notifyAll();//唤醒等待线程
		return p;
	}
}

产品类

package product;

/**
 * 产品类
 */
class Product {
	private int id;// 产品id
	private String name;// 产品名称

	public Product(int id, String name) {
		this.id = id;
		this.name = name;
	}
	public String toString() {
		return "(产品ID:" + id + " 产品名称:" + name + ")";
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
}

生产者

package product;

import java.util.Random;

/**
 * 生产者
 */
class Producer implements Runnable {
	private Storage storage;

	public Producer(Storage storage) {
		this.storage = storage;
	}

	@Override
	public void run() {
		int i = 0;
		Random r = new Random();
		while(i<10)
		{
			i++;
			Product product = new Product(i, "电话" + r.nextInt(100));
			storage.push(product);
		}		
	}
}

消费者

package product;

/**
 * 消费者
 */
class Consumer implements Runnable {
	private Storage storage;

	public Consumer(Storage storage) {
		this.storage = storage;
	}

	public void run() {
		int i = 0;
		while(i<10)
		{
			i++;
			storage.pop();
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
	}
}

该示例中两个生产者分别生产10个产品,两个消费者分别消费10个产品。

部分运行结果如下:

在这里插入图片描述

线程要做自己的主

  • 线程被动地暂停和终止
    • 依靠别的线程来拯救自己 😕😕😕
    • 没有及时释放资源
  • 线程主动暂停和终止
    • 定期监测共享变量
    • 如果需要暂停或者终止,先释放资源,再主动动作 😊😊😊
    • 暂停:Thread.sleep(),休眠
    • 终止:run方法结束,线程终止

示例代码

package interrupt;

public class InterruptTest {

	public static void main(String[] args) throws InterruptedException {
		TestThread1 t1 = new TestThread1();
		TestThread2 t2 = new TestThread2();

		t1.start();
		t2.start();

		// 让线程运行一会儿后中断
		Thread.sleep(2000);
		t1.interrupt();
		t2.flag = false;
		System.out.println("main thread is exiting");
	}

}

class TestThread1 extends Thread {
	public void run() {
		// 判断标志,当本线程被别人interrupt后,JVM会被本线程设置interrupted标记
		while (!interrupted()) {
			System.out.println("test thread1 is running");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
				break;
			}
		}
		System.out.println("test thread1 is exiting");
	}
}

class TestThread2 extends Thread {
	public volatile boolean flag = true;
	public void run() {
		// 判断标志,当本线程被别人interrupt后,JVM会被本线程设置interrupted标记
		while (flag) {
			System.out.println("test thread2 is running");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("test thread2 is exiting");
	}
}

interrupted() 是Thread类的方法,用来测试当前线程是否收到一个INTERRUPT信号。
如果收到,该方法返回true,否则返回false。

运行结果如下:

在这里插入图片描述
两种方式比较:

  1. 用interrupt这个标志来中断异常的话,需要自己去添加异常处理,并且此处的异常可能会让你来不及释放资源。
  2. 定期去监测flag变量,当变量被修改了,就可以很优雅地释放所有资源,然后主动退出。

多线程死锁

  • 每个线程互相持有别人需要的锁(哲学家吃面问题)
  • 预防死锁,对资源进行等级排序

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

示例代码

package deadlock;

import java.util.concurrent.TimeUnit;

public class ThreadDemo5
{
	public static Integer r1 = 1;
	public static Integer r2 = 2;
	public static void main(String args[]) throws InterruptedException
	{
		TestThread51 t1 = new TestThread51();
		t1.start();
		TestThread52 t2 = new TestThread52();
		t2.start();
	}
}

class TestThread51 extends Thread
{
	public void run() 
	{
		//先要r1,再要r2
		synchronized(ThreadDemo5.r1)
		{
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized(ThreadDemo5.r2)
			{
				System.out.println("TestThread51 is running");
			}
		}
	}
} 
class TestThread52 extends Thread
{
	public void run() 
	{
		//先要r2,再要r1
		synchronized(ThreadDemo5.r2)
		{
			try {
				TimeUnit.SECONDS.sleep(3);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized(ThreadDemo5.r1)
			{
				System.out.println("TestThread52 is running");
			}
		}
	}
}

TimeUnit是JDK 5引入的新类
位于java.util.concurrent包中。
它提供了时间单位粒度和一些时间转换、计时和延迟等函数。

该代码段中t1拿到r1,t2拿到r2,但双发下一步需要取得的锁被对方持有从而无法进行下去,由此产生了死锁。
若两个进程都先拿r1,再拿r2则可以避免死锁

守护(后台)线程

  • 普通线程的结束,是run方法运行结束
  • 守护线程的结束,是run方法运行结束,或main函数结束
  • 守护线程永远不要访问资源,如文件或数据库等

示例代码

package daemon;

public class ThreadDemo4
{
	public static void main(String args[]) throws InterruptedException
	{
		TestThread4 t = new TestThread4();
		t.setDaemon(true);
		t.start();
		Thread.sleep(2000);
		System.out.println("main thread is exiting");
	}
}
 class TestThread4 extends Thread
{
	public void run() 
	{
		while(true)
		{
			System.out.println("TestThread4" + 
			" is running");
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

运行结果如下:
在这里插入图片描述

练习

请分成6个线程,计算m到n的值(以1到100000000为例)的总和。要求每个线程计算的数字量之差不超过1.

在这里插入图片描述

class TestThread implements Runnable {

	volatile int[] threadSize = new int[6];// 每个线程的大小
	volatile int[] threadStart = new int[6];// 每个线程的开始数字

	volatile long sum = 0;// 总和
	volatile int threadNum = 0;// 代表当前线程的数字

	public void run() {
		int threadNum = getThreadNum();
		// System.out.println(threadNum);

		int size = threadSize[threadNum];
		int start = threadStart[threadNum];
		long partsum = 0;

		// 计算每个线程部分的总和
		for (int i = start; i < (size + start); i++) {
			partsum = partsum + i;
		}
		// System.out.println("i="+i);
		total(partsum);
		System.out.println("Thread " + threadNum + " partsum = " + partsum);
	}

	private synchronized int getThreadNum() {
		return threadNum++;
	}

	private synchronized void total(long partsum) {
		this.sum += partsum;
	}

}
public class ThreadDemo {
	public static void main(String[] args) {
		int m = 1;
		int n = 100000000;
		int quotient = n / 6; // 商
		int remainder = n % 6; // 余数
//		System.out.println(quotient);
//		System.out.println(remainder);

		TestThread t = new TestThread();

		// 每个线程大小
		for (int i = 0; i < 6; i++) {
			t.threadSize[i] = quotient;
			if(remainder>0) {
				t.threadSize[i]++;
				remainder--;
			}
		}
		// 每个线程开始数字
		t.threadStart[0] = m;
		for (int i = 1; i < 6; i++) {
			t.threadStart[i] = t.threadSize[i - 1] + t.threadStart[i - 1];
		}

		for (int i = 0; i < 6; i++) {
			System.out.println();
			System.out.println("size " + t.threadSize[i]);
			System.out.println("start " + t.threadStart[i]);
		}
		new Thread(t, "Thread0").start();
		new Thread(t, "Thread1").start();
		new Thread(t, "Thread2").start();
		new Thread(t, "Thread3").start();
		new Thread(t, "Thread4").start();
		new Thread(t, "Thread5").start();

		while (true) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			//System.out.println("t.threadNum: "+t.threadNum);
			if (t.threadNum > 5) { // 如果t.seq>5,则说明6个线程已经运行完毕,结束循环
				break;
			}
		}
		// 输出最后的总和
		System.out.println("------------------\n" + "sum=" + t.sum);
	}
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值