线程安全的解决方法、线程之间的通信(生产者和消费者的经典例题)

目录

解决线程安全的方式一:同步代码块

解决线程安全的方式二:同步方法

总结:

使用同步机制将单例模式中的懒汉式改为线程安全的

解决线程安全的方式三:Lock锁

线程通信的例子:使用两个线程打印1-100。线程1,线程2交替打印

线程通信的应用:经典例题:生产者和消费者的问题


 

一、解决线程安全的方式一:同步代码块

synchronized(同步监视器){
     //需要被同步的代码
 }
说明:1.操作共享数据的代码即为需要被同步的代码
      2.共享数据:多个线程共同操作的变量。
      3.同步监视器:俗称:锁。任何一个类的对象都可以充当锁。
          要求:多个线程必须公用同一把锁

注意:在实现Runnable接口的方式中,我们可以考虑用this来充当同步监视器
     在继承Thread类的方式中,慎用this来充当同步监视器,可以考虑用当前类来充当同步监视器
class WIns implements Runnable{
    private int tacket = 100;
//    Object obj = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (this){//此时的this就是唯一的对象wIns
                if(tacket > 0){

//                   sleep()是为了更大概率出现线程安全问题
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+"卖出的票号是:" + tacket );
                    tacket--;
                }
            }
        }
    }
}

public class WindowsTest1 {
    public static void main(String[] args) {
        WIns wIns = new WIns();

        Thread t1 = new Thread(wIns);
        Thread t2 = new Thread(wIns);

        t1.setName("窗口一");
        t2.setName("窗口二");

        t1.start();
        t2.start();
    }
}
class Window2 extends Thread {

    private static int ticket = 100;//需要加static使得三个线程共用一个数据,但是存在线程安全问题,待解决!!!
    static Object obj = new Object();

    public void run() {
        while (true) {
            synchronized (this) {//类也是对象,真正的面向对象
                if (ticket > 0) {
                    System.out.println(getName() + "卖出的票,票号为:" + ticket);
                    ticket--;
                }
            }
        }
    }
}


public class WindowsTest2 {
    public static void main(String[] args) {

        Window2 w1 = new Window2();
        Window2 w2 = new Window2();
        Window2 w3 = new Window2();

        w1.setName("窗口1:");
        w2.setName("窗口2:");
        w3.setName("窗口3:");

        w1.start();
        w2.start();
        w3.start();
    }
}

 

二、解决线程安全的方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将这个方法声明为同步的
同步方法的 好处:解决了线程安全问题
          坏处:操作同步的代码时,只能一个来操作,相当于单线
/**
 * 使用同步方法来解决实现Runnable接口的线程安全问题
 *
 * @author haiyu
 * @create 2022/1/1-20:36
 */
class Windows5 implements Runnable {
    private int tacket = 100;

    //    Object obj = new Object();
    @Override
    public void run() {
        while (true) {
           show();
        }
    }
    private synchronized void show(){//默认的同步监视器:this
        if (tacket > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出的票号是:" + tacket);
            tacket--;
        }
    }
}


public class WindowsTest3 {
    public static void main(String[] args) {
        Windows5 windows3 = new Windows5();

        Thread t1 = new Thread(windows3);
        Thread t2 = new Thread(windows3);

        t1.setName("窗口一");
        t2.setName("窗口二");

        t1.start();
        t2.start();
    }
}
class Window4 extends Thread {

    private static int ticket = 100;//需要加static使得三个线程共用一个数据,但是存在线程安全问题,待解决!!!

    public void run() {
        while (true) {
                show();
            }
        }
//    private synchronized void show(){ 同步监视器为:this 当前类的对象:w1,w2,w3 有三个。这是不对的
    private static synchronized void show(){//同步监视器:当前类本身:Windows4.class
        if (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "卖出的票,票号为:" + ticket);
            ticket--;
        }
    }
}
public class WindowsTest4 {
    public static void main(String[] args) {

        Window4 w1 = new Window4();
        Window4 w2 = new Window4();
        Window4 w3 = new Window4();

        w1.setName("窗口1:");
        w2.setName("窗口2:");
        w3.setName("窗口3:");

        w1.start();
        w2.start();
        w3.start();
    }
}

总结:

  1.同步方法仍然涉及到同步监视器,只是不用显式的去声明 

  2.非静态的同步方法,同步监视器是:this

  3.静态的同步方法,同步监视器是:当前类本身

三、使用同步机制将单例模式中的懒汉式改为线程安全的

public class BankTest {
    public static void main(String[] args) {
    Bank bank = Bank.getBank();

    }
}

class Bank {
    private Bank() {
    }

    private static Bank bank = null;

    public static Bank getBank() {//或者直接public static synchronized Bank getBank()
//        synchronized (Bank.class) {//但是这样效率不好
            if (bank == null) {
                bank = new Bank();
            }
            return bank;
//        }
        //这样就好多了,后面来的就直接可以return了,不用判断
        if(bank == null){
            synchronized (Bank.class){
                bank = new Bank();
            }
        }
        return bank;
    }
}

四、解决线程安全的方式三:Lock锁

面试题:synchronized 和  lock的异同
      相同:二者都可以解决线程问题
      不同:synchronized机制在执行完相应的同步代码块以后,自动的释放同步监视器
           lock需要手动的lock() 和 unlock()
class WIn implements Runnable {
    private int ticket = 100;
//1.实例化一个ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {

            try {
//               2. 调用lock方法
                lock.lock();

                if (ticket > 0) {

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

                    System.out.println(Thread.currentThread().getName() + "卖出的票号为:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }finally{
//                3.调用解锁的方法
                lock.unlock();
            }

        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        WIn w1 = new WIn();

        Thread t1 = new Thread(w1);
        Thread t2 = new Thread(w1);

        t1.start();
        t2.start();
    }
}
死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁

 2.说明:
 1)出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
 2)我们使用同步时,要避免出现死锁。

 解决方法
 >专门的算法、原则
 >尽量减少同步资源的定义>尽量避免嵌套同步

五、线程通信的例子:使用两个线程打印1-100。线程1,线程2交替打印

涉及到的三个方法:
 wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
 notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait, 就唤醒优先级高的那个线程。
 notifyALl():一旦执行此方法,就会唤醒所有被wait的线程。

 说明:
 1.wait(),notify(),notifyALl()三个方法必须使用在同步代码块或同步方法中。
 2.wait(),notify(),notifyALL()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。
 否则,会出现ILLegalMonitorstateException异常
 3.wait(),notify(),notifyALL()三个方法是定义在java.lang.object类中。

 面试题:sleep()和wait()的异同点
 1.相同点:一旦执行方法,都会让调用的线程进入阻塞状态
 2.不同点:
     ①两个方法声明的位置不同,Thread中声明的是sleep()方法,Object中声明的是wait()方法
     ②调用的要求不同:sleep()可以在任何情况下调用,wait()必须在同步代码块中或者在同步方法中
     ③关于是否释放同步监视器:如果两个方法都声明在同步代码块或同步方法中,slee()不会释放锁,wait()可以释放锁
class Number implements Runnable {

    private int num = 0;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                notify();
                if (num < 100) {
                    num++;

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

                    System.out.println(Thread.currentThread().getName() + "打印了" + num);

                    try {
                        wait();//作用:①释放锁 ②使调用wait()的线程进入阻塞状态
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                } else {
                    break;
                }
            }
        }
    }
}

public class communicationTest {
    public static void main(String[] args) {
        Number n = new Number();

        Thread t1 = new Thread(n);
        Thread t2 = new Thread(n);

        t1.setName("打印机1");
        t2.setName("打印机2");

        t1.start();
        t2.start();
    }
}

六、线程通信的应用:经典例题:生产者和消费者的问题

生产者(Productor)将产品交给店员(CLerk),而消费者(Customer)从店员处取走产品,店员一次
只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,
如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,
如果店中有产品了再通知消费者来取走产品。
lass Clerk {

    private int number = 0;

    public int getNumber() {
        return number;
    }

    public synchronized void shengchan() {
        if (number < 20) {
            number++;
            System.out.println("产品数量为:" + getNumber());

            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            notify();
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void xiaofei() {
        if (number > 0) {
            number--;
            System.out.println("产品数量为:" + getNumber());

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

            notify();
        } else {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Productor extends Thread {
    private Clerk clerk;

    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true) {
            clerk.shengchan();
        }
    }
}

class Cosumer extends Thread {
    private Clerk clerk;

    public Cosumer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while (true) {
            clerk.xiaofei();
        }
    }
}

public class ProductTest {
    public static void main(String[] args) {
        Clerk c = new Clerk();

        Productor p1 = new Productor(c);
        Cosumer c1 = new Cosumer(c);
        Cosumer c2 = new Cosumer(c);

        p1.setName("提供者");
        c1.setName("客户1");
        c2.setName("客户2");

        p1.start();
        c1.start();
        c2.start();
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java塑造中...

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值