Java多线程学习06

Java多线程学习06

21/04/07


线程同步

  • 并发:同一个对象被多个线程同时操作
  • 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步。线程同步其实是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前边线程使用完毕,下一个线程再使用。

队列和锁

  • 由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入了 锁机制 synchronized ,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
    • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

同步方法

  • 由于我们可以通过private关键字来保证数据对象只被方法访问,所以我们指需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种方法:

    synchronized方法和synchronized块

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

    缺陷:若将一个大的方法申明为要syncronized将会影响效率
    

购票:

package com.syn;

//不安全购票
public class UnsafeBuyTickets {
    public static void main(String[] args) {
        BuyTickets station = new BuyTickets();
        new Thread(station,"小红").start();
        new Thread(station,"小明").start();
        new Thread(station,"小王").start();
    }
}

class BuyTickets implements Runnable{

    private int ticketNums = 10;
    boolean flag = true;//外部停止方式
    @Override
    public void run() {
        //买票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //synchronized 同步方法
    private synchronized void buy() throws InterruptedException {
        //判断是否有票
        if(ticketNums<=0){
            flag = false;
            return;
        }else {
            //买票
            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票");

        }
        //延时
        Thread.sleep(100);
    }
}
输出结果:
小红拿到了第10张票
小红拿到了第9张票
小红拿到了第8张票
小红拿到了第7张票
小红拿到了第6张票
小红拿到了第5张票
小强拿到了第4张票
小强拿到了第3张票
小强拿到了第2张票
小强拿到了第1张票

进程完成,退出码 0

取钱:

package com.syn;
/*
 * 问题:两个线程同时访问临界资源时,出现的问题
 * 实例问题:妻子、丈夫携主、副卡在同一个银行账户中取钱(两个线程访问临界资源对象)
 *
 * 妻子正在读卡...
 * 验证成功!
 * 丈夫正在读卡...
 * 验证成功!
 * 妻子取款成功!卡内余额为:0.0元
 * 丈夫取款成功!卡内余额为:-200.0元
 *
 * 解决:为临界资源的原子操作加同步代码块(锁)synchronized
 * 1.为取款方法加锁,因为取款就是原子操作,从插卡验证,到取款成功的一系列步骤,不可缺少或打乱
 * 2.在取款方法内部加同步代码块,锁住的this即是当前账户的实例对象
 * 3.为所有取出钞票的那一步原子操作加锁,包括妻子和丈夫
 *
 * 思路:如果丈夫先拿到锁,他就会先取钱,后台将取出钱后的余额修改数据,而妻子再取钱的时候,余额不足
 *      抢到锁之后妻子在等锁,即是阻塞状态
 *
 * 注意:锁是随机的被线程抢到的!
 *
 * //什么场景下加锁?什么场景下不加锁?
 * //写(增、删、改) 操作---> 加锁!
 * //读操作 不加锁
 */
public class TestSynchronized {
    public static void main(String[] args) {
        //临界资源,只有一张银行卡
        //临界资源对象只有一把锁
        Account account = new Account("0001","123456",2000);
        Thread husband = new Thread(new Husband(account),"丈夫");
        Thread wife = new Thread(new Wife(account),"妻子");
        wife.start();
        husband.start();
    }
}

class Account {   //银行账户
    String cardNo;
    String passWord;
    double balance;

    public Account(String cardNo,String passWord,double balance){
        this.cardNo = cardNo;
        this.passWord = passWord;
        this.balance = balance;
    }

    //取款(原子操作,从插卡验证,到取款成功的一系列步骤,不可缺少或打乱)
    public synchronized void withdrawal(String cardNo,String passWord,double money){
        synchronized (this){
            System.out.println(Thread.currentThread().getName()+"正在读卡...");
            //(原子操作,从插卡验证,到取款成功的一系列步骤,不可缺少或打乱)
            if (this.cardNo.equals(cardNo)&&this.passWord.equals(passWord)){
                System.out.println("验证成功!");
                if (money<=this.balance){
                    //模拟现实世界,正在数钞
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    this.balance -= money;//取出
                    System.out.println(Thread.currentThread().getName()+"取款成功,卡内余额:"+this.balance+"元");
                }else {
                    System.out.println(Thread.currentThread().getName()+"卡内余额不足!余额:"+balance+"元");
                }
            }else {
                System.out.println("卡号或密码不正确!");
            }
        }
    }
}
class Husband implements Runnable{
    Account account;
    public Husband(Account account){
        this.account = account;
    }
    //线程任务
    @Override
    public void run() {
        synchronized (account){
            this.account.withdrawal("0001","123456",500);//原子操作
        }
    }
}

class Wife implements Runnable{
    Account account;
    public Wife(Account account){
        this.account = account;
    }
    //线程任务
    @Override
    public void run() {
        synchronized (account){
            this.account.withdrawal("0001","123456",2000);//原子操作
        }
    }
}
输出结果:
妻子正在读卡...
验证成功!
妻子取款成功,卡内余额:0.0元
丈夫正在读卡...
验证成功!
丈夫卡内余额不足!余额:0.0元

进程完成,退出码 0

JUC:

package com.syn;

import java.util.concurrent.CopyOnWriteArrayList;

public class TestJUC {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}
输出结果:
10000

进程完成,退出码 0

死锁问题:

  • 多个线程各自占有一些资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一同步块同时拥有”两个以上的锁“时,就可能发生”死锁“问题。
package com.syn;

//死锁:多个线程互相抱着对方需要的资源,然后形成僵持。
public class DeadLock {
    public static void main(String[] args) {
        Makeup g1 = new Makeup(0,"灰姑娘");
        Makeup g2 = new Makeup(1,"白雪公主");
        g1.start();
        g2.start();
    }
}

class Lipstick{
}

class Mirror{
}

class Makeup extends Thread{

    //需要的资源只有一份资源,用static来保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice ;
    String girlName;

    Makeup(int choice,String girlName){
        this.choice = choice;
        this.girlName = girlName;
    }
    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //化妆,互相持有对方的锁
    private  void makeup() throws InterruptedException {
        if (choice==0){
            synchronized (lipstick){
                System.out.println(this.girlName+"获得了口红的锁");
                Thread.sleep(1000);
                synchronized (mirror){
                    System.out.println(this.girlName+"获得了镜子的锁");
                }
            }
        }else {
            synchronized (mirror){
                System.out.println(this.girlName+"获得了镜子的锁");
                Thread.sleep(2000);
                synchronized (lipstick){
                    System.out.println(this.girlName+"获得了口红的锁");
                }
            }

        }
    }
}
输出结果:
灰姑娘获得了口红的锁
白雪公主获得了镜子的锁		
    		//程序卡住,两个线程僵持住

更改为:

    private  void makeup() throws InterruptedException {
        if (choice==0){
            synchronized (lipstick){
                System.out.println(this.girlName+"获得了口红的锁");
                Thread.sleep(1000);
                }synchronized (mirror){
                    System.out.println(this.girlName+"获得了镜子的锁");
            }
        }else {
            synchronized (mirror){
                System.out.println(this.girlName+"获得了镜子的锁");
                Thread.sleep(2000);
                }synchronized (lipstick){
                System.out.println(this.girlName+"获得了口红的锁");
            }

        }
    }
输出结果:
灰姑娘获得了口红的锁
白雪公主获得了镜子的锁
白雪公主获得了口红的锁
灰姑娘获得了镜子的锁

进程完成,退出码 0

  • 产生死锁的四个必要条件:

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

    上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生

Lock锁

package com.syn;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestLock {
    public static void main(String[] args) {
        Test test = new Test();

        new Thread(test).start();
        new Thread(test).start();
        new Thread(test).start();
    }
}

class Test implements Runnable{

    int ticketNums = 10;
    //定义一个Lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                lock.lock();    //加锁
                if (ticketNums<=0){
                    break;
                }else {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                }

            }finally {
                lock.unlock();      //解锁

            }
        }
    }
}
输出结果:
10
9
8
7
6
5
4
3
2
1

进程完成,退出码 0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值