JAVA基础总结(十)之并发编程

进程

什么是进程。操作系统中运行的一个应用程序
例如qq,微信。操作系统都会运行一个进程
每个进程都是独立的,每个进程都运行在其专有且保护的内存空间里面
在windows可以通过任务管理器查看

线程

什么是线程.一个进程想要执行任务。就必须要有线程。
一个进程的所有任务都在线程中执行
一个线程的任务执行是串行的
例如下载一个3个文件。是一个一个下载

多线程

什么是多线程。一个进程可以开启多线程。所有线程可以同时执行不同的任务。叫多线程
例如同时下载多个文件

多线程的原理

在同一时间, cpu的一个核心只能处理一个线程
多线程并发执行,其实是cpu在多个线程中快速切换
如果cpu调度线程的时间足够快,就造成了多线程并发执行的假象
多核cpu才是真正的多个线程并发执行

多线程的优缺点

  • 优点
    • 能适当提高程序的执行效率
    • 能适当提高资源的利用率
  • 缺点
    • 开启线程需要占用一定的内存空间,如果开启大量的线程。会占用大量的内存空间
    • 线程越多,CPU在调度线程上的开销越大
    • 程序的设计更复杂

开启线程方法

第一个方法

/// 创建一个线程
		Thread thread = new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				// to doing 
			}
		});
// 开启
thread.start();

第二个方法

// 创建一个类。然后继承后重写run方法
public class MyThread extend Thread{
  
  @Override  
  public void run{
}

	Thread thread = new MyThread();
}

多线程的内存布局

  • PC寄存器
    每一个线程都有自己的PC寄存器.
    即开启的每一个线程他们的执行进度不一样,都有属于自己的寄存器
  • JAVA虚拟机栈
    每一个线程都有自己的JAVA虚拟机栈空间
    因为每一个线程都会执行,执行完成后就会销毁空间。如果他们共用一个则会出问题

  • 多线程共享堆
  • 方法区
    多个线程共享方法去
  • 本地方法栈
    每一个线程都有自己的本地方法栈

线程的状态

可以通过getState()获取线程的状态。一共有6个状态

  • NEW 尚未启动
  • RUNABLE 正在JVM运行
  • BLOCKED 正在等待监视器锁
  • WAITING 正在等待另外一个线程
  • TIMED_WAITING 定时等待状态
  • TERMINATED 已经执行完毕

线程的状态切换

在这里插入图片描述
线程可以通过slee[方法进入到WAITING状态。等待另外一个线程状态
在暂停期间,如果使用interrupt中断方法则会抛出异常
isAlive 查明线程是否或者

线程安全问题

多个线程访问同一块资源,比如访问同一个对象,同一个文件
当多线程访问同一块资源的时候,很容易引发数据错乱,称为线程安全问题
当多个线程共享同一块资源的时候,并且有一个线程正在写的操作则会引发安全问题
例如:

	public static void main(String[] args) throws ParseException {
		
		Station station = new Station();
		for(int i = 0; i < 4; i++){
			Thread thread = new Thread(station);
			thread.setName(i + "");
			thread.start();
		}
	}
	
package com.bona;
/**
* 
*
* @author Zyuan
* @version 创建时间:2020年5月12日 下午11:03:17
*/
public class Station implements Runnable{
	
	private int tickets = 100;
	
	private boolean saleTicket(){
		if(tickets < 1){
			return false;
		}
		tickets --;
		String threadName = Thread.currentThread().getName();
		System.out.println("线程"+threadName+"卖了"+tickets+"张");
		return tickets > 0;
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while (saleTicket()) {
			
		}
	}
	
}

线程安全解决方案

  • 线程同步
    使用同步语句使用 synchronized
    在上面方法上使用synchronized
	private boolean saleTicket(){
       synchronized (this) {
	   		if(tickets < 1){
				return false;
			}
			tickets --;
			String threadName = Thread.currentThread().getName();
			System.out.println("线程"+threadName+"卖了"+tickets+"张");
			return tickets > 0;
	    }
	}

在同一时间只有一个人在卖票.
原理在于每一个对象都有一个与他对应的内部锁与监听器锁。
虽然我们有4条线程执行,但是都有一个先后顺序,第一条到达的线程会获取对象的内部锁。则加锁了。同一时间,只有一个线程拥有对象的内部锁。所以谁先来谁则先执行。等先来的执行完成后。剩下的再次抢到则开始执行。其他开始等待。当其他开始等待则进入BLOCKED状态

  • 同步方法
    在方法前面加 synchronized
    构建方法不能使用synchronized
	private synchronized boolean saleTicket(){
		if(tickets < 1){
			return false;
		}
		tickets --;
		String threadName = Thread.currentThread().getName();
		System.out.println("线程"+threadName+"卖了"+tickets+"张");
		return tickets > 0;
	}

虽然使用了线程同步后,虽然解决线程安全后,但是降低了性能效率。

几个常用的细节

ArrayList 非线程安全数组
Vector 有线程安全

StringBuilder 非线程安全
StringBuffer 线程安全

HashMap 非线程安全
HashTable 线程安全

死锁

两个线程相互永远堵塞,相互等待的叫死锁

线程之间的通信与示例

假如有两条线程,一条叫生产者,一条叫消费者。消费者需要等待生产者生产东西。生产者生产完成后需要主动通知消费着。
可以使用Object.wait,Object.notify,Object.notifyAll.
如果想要在线程A调用上面的方法。那么线程A必须只有Object的内部锁。
// // 在 使用wait过程中.Object.wait 必须要 notify与wait的Object一致。
// wait();
// // 随机通知一个等待他的线程
// notify();
// // 通知所有等待他的线程
// notifyAll();

例如:
生产者

package com.bona;
/**
* 
* 生产者
*/
public class Producer implements Runnable{
	
	private Drop drop;
	
	public Producer(Drop drop){
		this.drop = drop;
	}

	@Override
	public void run() {
		String foots[] = {"食物1", "食物2", "食物3", "食物4"};
		for (int i = 0; i < foots.length; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.err.println("生产了食物"+foots[i]);
			drop.add(foots[i]);
		}
	   drop.add(null);
	}

}

消费者

package com.bona;
/**
* 
* 消费者
*/
public class Consumer implements Runnable{

	private Drop drop;
	
	public Consumer(Drop drop){
		this.drop = drop;
	}
	
	@Override
	public void run() {
		String foot = null;
		while ((foot = drop.get()) != null) {
		  System.out.println("收到食物"+foot);
		    try {
			  Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
         drop.add(null);
	}

}

package com.bona;
/**
* 
*
* @author Zyuan
* @version 创建时间:2020年5月16日 下午10:16:18
*/

public class Drop {
	
	private String foot;
	// 等待生产食物,如果生产了则为false,需要去通知,没有生产就等待
	private boolean empty = true;

	public synchronized String get() {
		
		// 等待食物获取
		while (empty) {
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
        empty = true;
        notifyAll();
		return foot;	
	}

	public synchronized void add(String foot) {
		while (!empty) {
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		empty = false;
		this.foot = foot;
		// 通知所有等待的线程。告诉他们食物生产完成了
		notifyAll();
	}
	
//	private Boolean empty = true;

}

最后执行:

	public static void main(String[] args) throws ParseException {

		Drop drop = new Drop();
		(new Thread(new Producer(drop))).start();
		(new Thread(new Consumer(drop))).start();
		
		
	}
	

在这里插入图片描述

可重入锁

具有跟同步语句,同步方法一样的功能。但是功能更加强大.
ReentrantLock 方法
例如使用方法如上面卖票例子:

package com.bona;

import java.util.concurrent.locks.ReentrantLock;

/**
* 
*
* @author Zyuan
* @version 创建时间:2020年5月12日 下午11:03:17
*/
public class Station implements Runnable{
	
	private int tickets = 100;
	
	private ReentrantLock lock = new ReentrantLock();
	
	private boolean saleTicket(){
		try {
			lock.lock();
			if(tickets < 1){
				return false;
			}
			tickets --;
			String threadName = Thread.currentThread().getName();
			System.out.println("线程"+threadName+"卖了"+tickets+"张");
			return tickets > 0;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while (saleTicket()) {
			
		}
	}
	
}

线程池

线程对象占用大量的内存,在大型应用中,频繁的创建会产生大量的内存
使用线程池可以减少线程的创建,销毁带来的开销
线程池由工作线程组成:

  • 线程执行完一个任务后生命周期就结束了(Thread)
  • 工作线程可以执行多个任务。任务没来就等待。来了就执行。先吧任务添加到队列中,再从队列中取出任务提交到池中
		// 线程池对象
		ExecutorService executorService = Executors.newFixedThreadPool(5);
		executorService.execute(()->{
			System.out.println(Thread.currentThread());
		});
		executorService.execute(()->{
			System.out.println(Thread.currentThread());
		});
		executorService.execute(()->{
			System.out.println(Thread.currentThread());
		});
		executorService.execute(()->{
			System.out.println(Thread.currentThread());
		});
		executorService.execute(()->{
			System.out.println(Thread.currentThread());
		});
		
		// 不用了需要关掉线程池
		executorService.shutdown();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值