Java多线程-2

1. 线程的同步机制

要解决的问题:多个线程同时操作同一个资源(并发)
同时抢一张票,为什么会变成负数???
每个线程都在自己的工作内存进行交互,内存交互不当造成数据不一致,当2个线程同时看见剩一张票,都将这个1拿到自己的工作内存中,进行购买。第一买完就是0,第二个买完就是-1。
为什么会同时抢到一张票???
没有排队造成几个线程同时拿到同一张票

形成条件:队列+锁,解决线程不安全的问题,称为线程同步synchronized

线程同步的问题:
1. 损失性能问题
2. 优先级低的先拿到锁,会引起性能倒置的问题

2. 同步方法及同步块

同步方法:public synchronized void method(int args){}
synchronized方法控制对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞。方法一旦执行,就独占锁,直到方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

同步块:synchronized(Obj){}
不需要锁整个方法,浪费资源,只锁变动的资源就可以了

Obj称为同步监视器:
Obj可以是任何对象,推荐使用共享资源作为同步监视器
同步方法中无需只指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class

同步监视器的执行过程:
1. 第一个线程访问,锁定同步监视器,执行其中代码
2. 第二个线程访问,发现同步监视器被锁定,无法访问
3. 第一个线程访问完毕,解锁同步监视器
4. 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

注:
在使用多线程的时候,ArrayList是线程不安全的,用JUC下的包CopyOnWriteArrayList是线程安全的

3. 死锁

多个线程互相抱着对方的资源,形成僵持的现象
产生死锁
在这里插入图片描述
解决死锁
在这里插入图片描述

产生死锁的必要条件

  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不变
  3. 不剥夺条件:进程已获得的资源,在未使用之前,不能强行剥夺
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

4. Lock

跟synchonized作用一样,Lock就是通过显示定义同步锁对象来实现同步。在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。
在这里插入图片描述
建议lock()写入try中,unlock()写入finally中。


synchronized与lock的对比:

  1. Lock是显示锁,需要手动开启和关闭,synchronized是隐式锁,出了作用域自动关闭
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. 使用Lock锁JVM将花费更小的时间调度线程,性能更好,并有更好的扩展性
  4. 优先使用顺序:
    Lock -> 同步代码块 -> 同步方法

5. 线程协作-生产者消费者

在这里插入图片描述
这是一个线程同步问题, 生产者和消费者共享同一资源,并且生产者和消费者之间相互依赖,互为条件。
synchronized只能实现线程的同步,不能实现线程的通信。java提供几种方法解决线程之间的通信问题。
wait() 线程等待,直到线程通知,与sleep不同,会释放锁
wait(long timeout) 指定等待毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上,所有调用wait方法线程,有限级别高的线程优先调度

以上方法只能在同步方法或者同步代码块中使用

5.1 管程法

将数据放在缓冲区

public class TestPC(
	public static void main(String[] args){
		SynContainer container = new SynContainer();
		new Productor(container).start();
		new Consumer(container).start();
	}
}

//生产者
class Productor extends Thread{
	SynContainer container;
	Productor(SynContainer container){
		this.container = container;
	}
	@Override
	public void run(){
		for(int i = 0; i < 100; i++){
			container.push(new Chicken(i));
			System.out.println("生产出第" + i + "只鸡");
		}
	}
}

//消费者
class Consumer extends Thread{
	SynContainer container;
	Consumer (SynContainer container){
		this.container = container;
	}
	@Override
	public void run(){
		for(int i = 0; i < 100; i++){
			System.out.println("消费了第" + container.pop().id + "只鸡");
		}
	}
}

//产品
class Chichen{
	int id ;
	Chichen(int id){
		this.id = id;
	}
}

//缓冲区
class SynContainer{
	//设置容器大小
	Chicken[] chickens = new Chicken[10];
	//容器计数器
	int count = 0;
	
	//生产者放入产品
	public synchronized void push(Chicken chicken){
		//如果容器满了就暂停生产,等待消费者消费
		if(count == chickens.length){
			//这里有个try...catch,此处省略
			this.wait();
		}
		//如果容器没满,生产者丢入产品
		chickens[count] = chicken;
		count++;
		//通知消费者消费
		this.notifyAll();
	}
	//消费者消费产品
	public synchronized Chicken pop(){
		//如果容器空了,等待生产者生产
		if(count == 0){
			this.wait();
		}
		//如果容器没空,消费产品
		count--;
		Chicken chicken = chickens[count];
		//通知生产者生产
		this.notifyAll();
	}
	
}

5.2 信号灯法

通过标志位
代码中没有container的类,在产品的类中通过flag标志位判断什么时候wait什么时候notify

6. 线程池

思路:提前创建好多个线程,放入池子中,使用时直接获取,使用完放回。不用频繁的创建和销毁,提升性能。

好处:
提高响应速度,降低资源消耗
便于线程管理:核心池大小、最大线程数、线程没有任务时最多保持多长时间会终止

Executors 工具类,线程池的工厂类,用于创建并返回不同类型的线程池
ExecutorService 真正的线程池接口,常见子类ThreadPoolExecutor

void execute(Runnable command) 执行任务/命令,一般用来执行Runnable
Future submit(Callable task) 执行任务,一般用来执行Callable
void shutdown() 关闭线程池

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您的问题是关于Java多线程的存钱取钱问题。这是一个经典的多线程同步问题,可以通过使用synchronized关键字来实现线程安全。下面是一个简单的示例代码: ```java public class BankAccount { private int balance; public BankAccount(int balance) { this.balance = balance; } public synchronized void deposit(int amount) { balance += amount; System.out.println("Deposit successful. Balance: " + balance); } public synchronized void withdraw(int amount) { if (balance < amount) { System.out.println("Withdraw failed. Insufficient balance."); return; } balance -= amount; System.out.println("Withdraw successful. Balance: " + balance); } } public class Main { public static void main(String[] args) { BankAccount account = new BankAccount(1000); // Create two threads to simulate deposit and withdraw Thread depositThread = new Thread(() -> { for (int i = 0; i < 5; i++) { account.deposit(100); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread withdrawThread = new Thread(() -> { for (int i = 0; i < 5; i++) { account.withdraw(200); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); depositThread.start(); withdrawThread.start(); } } ``` 在这个示例代码中,我们创建了一个银行账户类BankAccount,并在其中实现了deposit和withdraw方法,并使用synchronized关键字来保证线程安全。 在main方法中,我们创建了两个线程来模拟存款和取款操作,每个线程执行5次操作。我们使用Thread.sleep方法来模拟每个操作之间的间隔,以便更好地观察多线程操作的结果。 当多个线程同时访问BankAccount对象的deposit和withdraw方法时,synchronized关键字可以确保每个方法只能被一个线程访问,从而避免了竞争条件和数据不一致的问题。 希望这个示例代码能够回答您的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值