栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【Java并发编程】线程安全问题、线程同步、单例模式(懒汉式)改进、细节、几个常用类的细节、死锁(Deadlock)、线程间通讯-生产者消费者模型、ReentrantLock(可重入锁)、线程池

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【Java并发编程】线程安全问题、线程同步、单例模式(懒汉式)改进、细节、几个常用类的细节、死锁(Deadlock)、线程间通讯-生产者消费者模型、ReentrantLock(可重入锁)、线程池

并发编程

文章目录

并发编程

07_线程安全问题

线程安全问题 – 错误示例解决方案 - 线程同步线程同步 - 同步语句线程同步 - 同步方法 08_单例模式(懒汉式)改进、细节几个常用类的细节09_死锁(Deadlock)

死锁示例1死锁示例2 10_线程间通讯

线程间通信 - 生产者消费者模型 11_ReentrantLock(可重入锁)

`lock`、`trylock`ReentrantLock 在卖票示例中的使用ReentrantLock – `tryLock`使用注意 12_线程池(Thread Pool)

07_线程安全问题

多个线程可能会共享(访问)同一个资源

比如访问同一个对象、同一个变量、同一个文件

当多个线程访问同一块资源时,很容易引发数据错乱和数据安全问题,称为线程安全问题。

什么情况下会出现线程安全问题?

多个线程共享同一个资源且至少有一个线程正在进行 写(write) 的操作

例如:存钱取钱过程

卖票过程

线程安全问题 – 错误示例

编写一个站台类:

public class Station implements Runnable {
	private int tickets = 100;
	
	public boolean saleTicket(){
		if(tickets < 1) return false; // 票卖完了,不卖了
		
		tickets--;
		
		String name = Thread.currentThread().getName();
		System.out.println(name + "卖了1张票,还剩" + tickets + "张");
		
		return tickets > 0;
	}
	@Override
	public void run() {
		while(saleTicket()); // 只要能卖票就一只卖
	}
}
public static void main(String[] args)  {
	Station station = new Station();
	for (int i = 1; i <= 4; i++) {
		Thread thread = new Thread(station);
		thread.setName("" + i);
		thread.start();
	}
}

会发现结果不是我们想要的,票数乱七八糟

....
2卖了1张票,还剩47张
2卖了1张票,还剩45张
2卖了1张票,还剩44张
2卖了1张票,还剩43张
2卖了1张票,还剩42张
2卖了1张票,还剩41张
2卖了1张票,还剩40张
2卖了1张票,还剩39张
2卖了1张票,还剩38张
2卖了1张票,还剩37张
2卖了1张票,还剩36张
1卖了1张票,还剩47张
1卖了1张票,还剩34张
1卖了1张票,还剩33张
1卖了1张票,还剩32张
1卖了1张票,还剩31张
1卖了1张票,还剩30张
4卖了1张票,还剩46张
4卖了1张票,还剩28张
4卖了1张票,还剩27张
4卖了1张票,还剩26张
4卖了1张票,还剩25张
4卖了1张票,还剩24张
4卖了1张票,还剩23张
4卖了1张票,还剩22张
4卖了1张票,还剩21张
4卖了1张票,还剩20张
4卖了1张票,还剩19张
4卖了1张票,还剩18张
4卖了1张票,还剩17张
3卖了1张票,还剩47张
3卖了1张票,还剩15张
3卖了1张票,还剩14张
4卖了1张票,还剩16张
4卖了1张票,还剩12张
4卖了1张票,还剩11张
4卖了1张票,还剩10张
4卖了1张票,还剩9张
4卖了1张票,还剩8张
4卖了1张票,还剩7张
1卖了1张票,还剩29张
1卖了1张票,还剩5张
1卖了1张票,还剩4张
1卖了1张票,还剩3张
1卖了1张票,还剩2张
1卖了1张票,还剩1张
1卖了1张票,还剩0张
2卖了1张票,还剩35张
4卖了1张票,还剩6张
3卖了1张票,还剩13张

问题分析:

解决方案 - 线程同步

可以使用线程同步技术来解决线程安全问题

同步语句(Synchronized Statement)同步方法(Synchronized Method) 线程同步 - 同步语句

将上面错误示例的代码修改成如下,则正确了

public boolean saleTicket(){
	synchronized (this) {
		if(tickets < 1) return false;
		tickets--;
		String name = Thread.currentThread().getName();
		System.out.println(name + "卖了1张票,还剩" + tickets + "张");
		return tickets > 0;
	}
}
.....
1卖了1张票,还剩49张
1卖了1张票,还剩48张
1卖了1张票,还剩47张
1卖了1张票,还剩46张
1卖了1张票,还剩45张
1卖了1张票,还剩44张
4卖了1张票,还剩43张
4卖了1张票,还剩42张
4卖了1张票,还剩41张
4卖了1张票,还剩40张
4卖了1张票,还剩39张
3卖了1张票,还剩38张
3卖了1张票,还剩37张
3卖了1张票,还剩36张
3卖了1张票,还剩35张
3卖了1张票,还剩34张
3卖了1张票,还剩33张
3卖了1张票,还剩32张
3卖了1张票,还剩31张
3卖了1张票,还剩30张
3卖了1张票,还剩29张
3卖了1张票,还剩28张
3卖了1张票,还剩27张
3卖了1张票,还剩26张
3卖了1张票,还剩25张
3卖了1张票,还剩24张
3卖了1张票,还剩23张
3卖了1张票,还剩22张
3卖了1张票,还剩21张
3卖了1张票,还剩20张
3卖了1张票,还剩19张
3卖了1张票,还剩18张
2卖了1张票,还剩17张
2卖了1张票,还剩16张
2卖了1张票,还剩15张
2卖了1张票,还剩14张
2卖了1张票,还剩13张
2卖了1张票,还剩12张
2卖了1张票,还剩11张
2卖了1张票,还剩10张
2卖了1张票,还剩9张
2卖了1张票,还剩8张
2卖了1张票,还剩7张
2卖了1张票,还剩6张
2卖了1张票,还剩5张
2卖了1张票,还剩4张
2卖了1张票,还剩3张
2卖了1张票,还剩2张
2卖了1张票,还剩1张
2卖了1张票,还剩0张

synchronized(obj) 的原理:

每个对象都有一个与它相关的内部锁(intrinsic lock)或者叫监视器锁(monitor lock)第一个执行到同步语句的线程可以获得 obj 的内部锁,在执行完同步语句中的代码后释放此锁只要一个线程持有了内部锁,那么其它线程在同一时刻将无法再获得此锁
当它们试图获取此锁时,将会进入BLOCKED状态。

多个线程访问同一个 synchronized(obj) 语句时

obj 必须是同一个对象,才能起到同步的作用。 线程同步 - 同步方法

public synchronized boolean saleTicket(){
		if(tickets < 1) return false;
		tickets--;
		String name = Thread.currentThread().getName();
		System.out.println(name + "卖了1张票,还剩" + tickets + "张");	
		return tickets > 0;
}

synchronized 不能修饰构造方法

同步方法的本质

实例方法:synchronized (this)静态方法:synchronized (Class对象)

同步语句比同步方法更灵活一点

同步语句可以精确控制需要加锁的代码范围

使用了线程同步技术后

虽然解决了线程安全问题,但是降低了程序的执行效率所以在真正有必要的时候,才使用线程同步技术 08_单例模式(懒汉式)改进、细节

public class Rocket {
	private static Rocket instance = null;
	private Rocket() {}
	public static synchronized Rocket getInstance(){
		if(instance == null){
			instance = new Rocket();
		}
		return instance;
	}
}
几个常用类的细节

动态数组:

ArrayList:非线程安全Vector:线程安全

动态字符串:

StringBuilder:非线程安全StringBuffer:线程安全

映射(字典):

HashMap:非线程安全Hashtable:线程安全 09_死锁(Deadlock)

什么是死锁?

两个或者多个线程永远阻塞,相互等待对方的锁 死锁示例1

以下代码会造成死锁:

第一个进程获得了 “1” 的同步锁,又想要获得 “2” 的同步锁第二个进程获得了 “2” 的同步锁,想要获得进程 “1” 的同步锁第一个进程和第二个进程互相等待对方释放,谁也不会主动释放,造成了死锁

public static void main(String[] args)  {
	new Thread(() -> {
		synchronized ("1") { // 进程1获得了 "1" 的同步锁
			System.out.println("1 - 1");
			try{
				Thread.sleep(100);
			} catch (Exception e) {
				e.printStackTrace();
			}
			synchronized ("2") { // 进程1想要获得 "2" 的同步锁
				System.out.println("1 - 2");
			}
		}
	}).start();;
	
	new Thread(() -> {
		synchronized ("2") { // 进程2获得了 "2" 的同步锁
			System.out.println("2 - 1");
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			synchronized ("1") { // 进程2想要获得 "1" 的同步锁
				System.out.println("2 - 2");
			}
		}
	}).start();;
}
死锁示例2
class Person{
	private String name;
	public Person(String name){
		this.name = name;
	}
	public synchronized void hello(Person p){
		System.out.format("[%s] hello to [%s]%n", name, p.name);
		p.smile(this);
	}
	public synchronized void smile(Person p){
		System.out.format("[%s] smile to [%s]%n", name, p.name);
	}
}
public class Deadlock {
	public static void main(String[] args) {
		Person jack = new Person("Jack");
		Person rose = new Person("Rose");
		new Thread(() -> {
			jack.hello(rose);
		}).start();;
		new Thread(() -> {
			rose.hello(jack);
		}).start();;
	}
}
10_线程间通讯

可以使用 Object.wait、Object.notify、Object.notifyAll 方法实现线程之间的通信

若想在线程 A 中成功调用 obj.wait、obj.notify、obj.notifyAll 方法

线程 A 必须要持有 obj 的内部锁

obj.wait :释放 obj 的内部锁,当前线程进入WAITING 或 TIMED_WAITING 状态

obj.notifyAll:唤醒所有因为 obj.wait 进入WAITING 或 TIMED_WAITING 状态的线程

obj.notify:随机唤醒 1 个因为 obj.wait 进入WAITING 或 TIMED_WAITING 状态的线程

线程间通信 - 生产者消费者模型

Drop:食品Consumer`:消费者Producer:生产者main:测试类

public class Drop {
	private String food;
	// empty为true代表:消费者需要等待生产者生产食品
	// empty为false代表:食品生产完毕,生产者要等待消费者消化完食品
	private boolean empty = true;
	
	
	public synchronized String get(){
		while(empty){
			try {
				wait();
			} catch (InterruptedException e) {}
		}
		
		empty = true;
		notifyAll();
		return food;
	}
	
	
	public synchronized void add(String food){
		while(!empty){
			try {
				wait();
			} catch (InterruptedException e) {}
		}
		
		empty = false;
		this.food = food;
		notifyAll();
	}	
}
public class Consumer implements Runnable {
	private Drop drop;
	public Consumer(Drop drop) {
		this.drop = drop;
	}
	
	@Override
	public void run() {
		String food = null;
		
		while((food = drop.get()) != null){
			System.out.format("消费者接收到生产者生产的食物:%s%n", food);
			try {
				Thread.sleep(1000); // 消费者吃食物2秒
			} catch (InterruptedException e) {}
		}
		
	}
}
public class Producer implements Runnable {
	private Drop drop;
	public Producer(Drop drop) {
		this.drop = drop;
	}
	
	@Override
	public void run() {
		String foods[] = {"beef", "bread", "apple", "cookie"};
		
		for (int i = 0; i < foods.length; i++) {
			try {
				Thread.sleep(1000); // 生产者生产食物2秒
			} catch (InterruptedException e) {}
			// 将foods[i]传递给消费者
			drop.add(foods[i]);
		}
		// 告诉消费者:不会再生产任何东西了
		drop.add(null);
	}
}
package com.yu;

public class Main {
	public static void main(String[] args) {
		Drop drop = new Drop();
		(new Thread(new Consumer(drop))).start(); // 开启消费者线程
		(new Thread(new Producer(drop))).start(); // 开启生产者线程	
	}
}
消费者接收到生产者生产的食物:beef
消费者接收到生产者生产的食物:bread
消费者接收到生产者生产的食物:apple
消费者接收到生产者生产的食物:cookie
11_ReentrantLock(可重入锁)

ReentrantLock ,译为“可重入锁”,也被称为“递归锁”

类的全名是:java.util.concurrent.locks.ReentrantLock具有跟同步语句、同步方法(synchronized)一样的一些基本功能,但功能更加强大

什么是可重入(rerntrant)?

同一个线程可以重复获取同一个锁其实 synchronized 也是可重入的

public static void main(String[] args) {
	synchronized ("1") {
		synchronized("1"){
			System.out.println("synchronized是可重入锁");
		}
	}
}

该例获取了两次 “1” 的内部锁,仍然可以执行,在有的语言中是不允许这样,那就不是可重入锁。

lock、trylock

ReentrantLock.lock:获取此锁

如果此锁没有被另一个线程持有,则将锁的持有计数设为 1,并且此方法立即返回如果当前线程已经持有此锁,则将锁的持有计数加 1,并且此方法立即返回如果此锁被另一个线程持有,并且在获得锁之前,此线程将一直处于休眠状态(相当于wait),此时锁的持有计数被设为 1

ReentrantLock.tryLock:仅在锁未被其他线程持有的情况下,才获取此锁

如果此锁没有被另一个线程持有,则将锁的持有计数设为 1,并且此方法立即返回 true如果当前线程已经持有此锁,则将锁的持有计数加 1,并且此方法立即返回 true如果此锁被另一个线程持有,则此方法立即返回 false

ReentrantLock.unlock:尝试释放此锁

如果当前线程持有此锁,则将持有计数减 1如果持有计数现在为 0,则释放此锁如果当前线程没有持有此锁,则抛出 java.lang.IllegalMonitorStateException

ReentrantLock.isLocked:查看此锁是否被任意线程持有

ReentrantLock 在卖票示例中的使用
import java.util.concurrent.locks.ReentrantLock;

public class Station implements Runnable {
	private int tickets = 50;
	// ReentrantLock lock = new ReentrantLock(); // 两个都行
	Lock lock = new ReentrantLock();
	
	
	public boolean saleTicket(){
		lock.lock();
		try{
			if(tickets < 1) return false;
			tickets--;
			
			String name = Thread.currentThread().getName();
			System.out.println(name + "卖了1张票,还剩" + tickets + "张");
			
			return tickets > 0;
		}finally {
			lock.unlock();
		}
	}
	
	@Override
	public void run() {
		while(saleTicket());
	}	
}
ReentrantLock – tryLock使用注意
Lock lock = new ReentrantLock();
new Thread(() -> {
	try {
		lock.lock();
		System.out.println("1");
		Thread.sleep(1000);
	} catch (InterruptedException e) {
		e.printStackTrace();
	} finally {
		lock.unlock();
	}
}).start();
Lock lock = new ReentrantLock();
new Thread(() -> {
	boolean locked = false;
	try{
		locked = lock.tryLock();
		System.out.println("2");
	} finally {
		if(locked)
			lock.unlock();
	}
}).start();
12_线程池(Thread Pool)

线程对象占用大量内存,在大型应用程序中,频繁地创建和销毁线程对象会产生大量内存管理开销。

使用线程池可以最大程度地减少线程创建、销毁所带来的开销。

线程池由 工作线程(Worker Thread) 组成

普通线程:执行完一个任务后,生命周期就结束了。工作线程:可以执行多个任务(任务没来就一直等,任务来了就干活);
先将任务添加到队列(Queue)中,再从队列中取出任务提交到池中。

常用的线程池类型是固定线程池(Fixed Thread Pool)

具有固定数量的正在运行的线程

线程池简单使用:

public static void main(String[] args)  {
	// 创建拥有5条工作线程的固定线程池
	ExecutorService pool = Executors.newFixedThreadPool(5);
	// 执行任务
	pool.execute(() -> {
		// Thread[pool-1-thread-1,5,main]
		System.out.println(Thread.currentThread());
	});
	pool.execute(() -> {
		// Thread[pool-1-thread-2,5,main]
		System.out.println(Thread.currentThread());
	});
	pool.execute(() -> {
		// Thread[pool-1-thread-3,5,main]
		System.out.println(Thread.currentThread());
	});
	// 关闭线程池
	pool.shutdown();
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/768468.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号