Java多线程随笔(3):线程同步

本文详细介绍了线程同步的重要性,通过多人买票和取款的线程不安全案例展示了并发问题。同步方法如`synchronized`关键字用于解决这些问题,避免数据竞争。同时,死锁的概念和示例揭示了多线程间的资源争夺可能导致的僵局。最后,Java从JDK5.0引入的Lock接口,特别是ReentrantLock,提供了更灵活的锁机制,以提高并发程序的安全性和效率。

线程同步

多个线程访问同一个对象,并且某些线程还想修改这个对象,这个时候就需要线程同步;

线程同步就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池,形成队列,等待前面线程使用完毕,下一个线程再使用;

形成条件:队列+锁(synchronized)

 

线程不安全案例

多人买票是非线程安全:

//多人买票是非线程安全
public class BuyTickets{
    public static void main(String[] args) {
        BuyTicketsThread ticketApp = new BuyTicketsThread();

        new Thread(ticketApp,"张三").start();
        new Thread(ticketApp,"李四").start();
        new Thread(ticketApp,"王五").start();
    }

}

class BuyTicketsThread implements Runnable{
    int ticketNum = 10;
    @Override
    public void run() {
        while(true){
            buy();
            if(ticketNum<=0)
                break;
        }
    }
    void buy(){
        if(ticketNum <= 0)
            return;
        System.out.println(Thread.currentThread().getName()+"获得"+ticketNum);
        ticketNum--;
    }
}
多人同时在一个账户上取钱为非线程安全:
//多人同时在一个账户上取钱为非线程安全
public class WithdrawMoney {
    public static void main(String[] args) {
        Account account = new Account(100, "XX银行卡");

        WithdrawMoneyThread zhangsan = new WithdrawMoneyThread(account, 1, "张三");
        WithdrawMoneyThread wife = new WithdrawMoneyThread(account, 100, "张三的媳妇儿");

        zhangsan.start();
        wife.start();
    }
}

class Account{
    int balance;//余额
    String name;//取款人姓名

    public Account(int balance, String name) {
        this.balance = balance;
        this.name = name;
    }
}

class WithdrawMoneyThread extends Thread{
    Account account; //账户
    int drawing; //要取的钱
    int cash; //手中的钱

    public WithdrawMoneyThread(Account account, int drawing, String name) {
        super(name);
        this.account = account;
        this.drawing = drawing;
    }

    @Override
    public void run() {

        if(account.balance < drawing){
            System.out.println(this.getName()+"取钱时钱不够了,取不了");
            return;
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //余额 = 余额 - 要取的钱
        account.balance = account.balance - drawing;
        //手中的钱 = 手中的钱 + 要取的钱
        cash = cash + drawing;

        System.out.println("余额为"+account.balance);
        System.out.println(this.getName() + "手中的钱为" + cash);
    }
}
线程不安全集合举例:ArrayList
import java.util.ArrayList;
import java.util.List;

//线程不安全集合举例:ArrayList
public class UnsafeSet {
    public static void main(String[] args) throws InterruptedException {
        List<Integer> list = new ArrayList<Integer>();
        for (int i = 0; i < 10000; i++) {
            int finalI = i;
            new Thread(()->{
                list.add(finalI);
            }).start();
        }
        Thread.sleep(3000);
        System.out.println(list.size());
    }
}

同步方法及同步块

同步方法:synchronized方法控制对象的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁。

同步块:synchronized(Obj){ },Obj成为“同步监视器”;

  • Obj一般为共享资源对象;
  • 同步方法中无需指定Obj,因为Obj就是this,这个对象本身;

 上面三个线程不安全的例子可以通过同步方法或者同步块来解决;

死锁

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或多个线程都在等待对方释放资源,都停止执行的情形;

某一同步块同时拥有两个以上对象的锁时,就可能发生死锁问题。

举例:

//多个人共用一套化妆品时可能出现死锁
public class DeadLock {
    public static void main(String[] args) {
        MakeUp m1 = new MakeUp(0,"女1");
        MakeUp m2 = new MakeUp(1,"女2");
        new Thread(m1,"1").start();
        new Thread(m2,"2").start();
    }
}

//口红
class Lipstick{

}

//镜子
class Mirror{

}

class MakeUp implements Runnable {
    //静态变量,保证只有一份,这样才会产生死锁
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice;//选择
    String name;//名字

    public MakeUp(int choice, String name) {
        this.choice = choice;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            makeUp();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void makeUp() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) {
                System.out.println(this.name + "获得口红锁");
                Thread.sleep(1000);
                synchronized (mirror) {
                    System.out.println(this.name + "获得镜子锁");
                }
            }
        } else {
            synchronized (mirror) {
                System.out.println(this.name + "获得镜子锁");
                Thread.sleep(1000);
                synchronized (lipstick) {
                    System.out.println(this.name + "获得口红锁");
                }
            }
        }
    }
}

 Lock锁

从JDK5.0开始,Java提供了显式的同步锁java.util.concurrent.locks.Lock接口;

以Lock比较常用的实现类ReentrantLock举例:

import java.util.concurrent.locks.ReentrantLock;

public class TestLock implements Runnable {

    int ticket = 1000;

    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true){
            lock.lock();
            try {
                if(ticket > 0){
                    System.out.println(Thread.currentThread().getName()+ticket--);
                }else
                    break;
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        TestLock testLock = new TestLock();

        new Thread(testLock, "张三").start();
        new Thread(testLock, "李四").start();
        new Thread(testLock, "王五").start();
    }
}

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值