死锁
概念:
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁;
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞 的状态,无法继续;
解决方法:
- 专门的算法
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
多线程综合案例分析
/*
需求:有3个储户分别向一个账户存3000,分三次存,每次存1000,每次存完打印结果;
分析: 多线程:3个储户
共享数据:操作一个账户
存在安全问题,进行解决
*/
一:创建一个账户类
class Account{
//1.定义余额
private double balance;
//2.创建含参构造 用来给balance赋值
public Account(double balance){
this.balance=balance;
}
//3.创建一个存钱方法 amt为存入的金额
/*问题:如果甲存完钱 出现堵塞没有打印 已进行操作就会出现错误;所以需要添加锁;
方法一:public synchronized void add(double amt){}
方法二: synchronized (Account.class) {}
方法三:lock() unlock()
*/
//4.创建ReentrantLock对象 后面调用方法
ReentrantLock lock=new ReentrantLock();
public void add(double amt){
lock.lock();//加锁
if (amt > 0) {
balance += amt;
System.out.println(Thread.currentThread().getName() + "存钱成功:余额为:" + balance);
}
lock.unlock();//解锁
}
}
二:创建多线程
class Customer extends Thread{
//1.获取账户 三个线程共享此账户;
private Account acc;
//2.共享一个数据 将账户赋给用户;
public Customer(Account acc){
this.acc=acc;
}
//3.重写run方法
@Override
public void run() {
try {
sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i <3 ; i++) {
//4.调用账户的存钱方法
acc.add(1000);
}
}
输出结果:
public class AccountTest {
public static void main(String[] args) {
//创建一个对象 三个用户共享一个账户;
Account acc=new Account(0);
//三个线程 三个用户进行操作
Customer c1 = new Customer(acc);
Customer c2 = new Customer(acc);
Customer c3 = new Customer(acc);
//给线程改名
c1.setName("甲");
c2.setName("已");
c3.setName("丙");
c1.start();
c2.start();
c3.start();
}
}
甲存钱成功:余额为:1000.0
丙存钱成功:余额为:2000.0
甲存钱成功:余额为:3000.0
甲存钱成功:余额为:4000.0
已存钱成功:余额为:5000.0
已存钱成功:余额为:6000.0
已存钱成功:余额为:7000.0
丙存钱成功:余额为:8000.0
丙存钱成功:余额为:9000.0
多线程通信
三个方法:
- wait():一旦执行此方法,线程进入阻塞状态,但是会释放锁 其他线程可以进入;
- notify():一旦执行此方法,会唤醒一个被wait阻塞的线程,如果多个线程被wait阻塞,那么唤醒优先级高的那个;
- notifyAll():一旦执行此方法,唤醒所有被wait阻塞的线程;
说明
- 此三个方法必须使用在同步代码块和同步方法中,lock不适用;
- 此三个方法的调用者必须是同步代码块或者同步方法中的同步监视器(对象),否则会出现异常;
- 此三个方法定义在Object类中,因为同步监视器可以是任意类型对象,所以放在Object类中可以保证每个对象都能进行调用;
sleep()和wait()异同
相同
- 都可以让线程进行阻塞状态;
不同
- 1.2个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
- 2.调用的要求不同:sleep()可以在任意场景调用,wait()必须使用在同步方法或者同步代码块中;
- 3.是否会释放锁:如果2个方法都放在同步方法或者代码块中,sleep()不会释放锁(其他线程无法进入),wait()会释放锁(其他线程可以进入);
生产者和消费者模式
- 循环可以一直进行
/*
测试:本类测试生产者和消费者通信问题
1.问题描述:产品 生产者 消费者
当生产的产品大于20时 开始消费
当消费的产品剩余0 开始生产
2.创建 产品类 生产者 消费者
会产生线程安全问题 因为存在共享数据
需要对生产方法和消费方法进行加锁 这样可以保证同步 一次只执行一个方法;
*/
1.创建产品类
class Clerk{
//1.1初始产品为1
private int clerkCount=0;
//1.2生产产品
public synchronized void produceClerk(){
if (clerkCount<20){
clerkCount++;
System.out.println(Thread.currentThread().getName()+"开始生产"+clerkCount);
//当生产者生产了一个产品就可以唤醒消费者;
notify();
}else {
//当生产的产品数量大于20时 进行阻塞
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//1.3消费产品
public synchronized void consumeClerk(){
if (clerkCount>0){
System.out.println(Thread.currentThread().getName()+"开始消费"+clerkCount);
clerkCount--;
//当消费者消费完一个产品就可以唤醒生产者
notify();
}else {
//当消费的产品剩余0时进行阻塞
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.定义生产者 继承Thread
class Producer extends Thread{
//2.1 定义产品属性
private Clerk clerk;
//2.2创建含参构造
public Producer(Clerk clerk){
this.clerk=clerk;
}
//2.3 重写run方法
@Override
public void run() {
System.out.println(getName()+"开始生产产品");
while (true){
clerk.produceClerk();
}
}
}
3.定义消费者 继承Thread
class Consumer extends Thread{
//3.1 定义产品属性
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk=clerk;
}
//3.3重写run方法
@Override
public void run() {
System.out.println(getName()+"开始消费产品");
while (true){
clerk.consumeClerk();
}
}
}
public class ProduceTest {
public static void main(String[] args) {
//实例化产品
Clerk clerk = new Clerk();
//实例化生产者
Producer producer = new Producer(clerk);
producer.setName("生产者1号");
producer.start();
//实例化消费者
Consumer consumer = new Consumer(clerk);
consumer.setName("消费者1号");
consumer.start();
}