多线程-*同步方法*解决两种线程创建方式的线程安全问题及练习

同步方法解决两种线程创建方式的线程安全问题

接上一节!

线程的安全问题与线程的同步机制

1. 多线程卖票出现的问题:出现了重票和错票


2.什么原因导致的?  线程1操作ticket的过程中,尚未结束的情况下,其他线程也参与进来,对ticket进行操作。


3.如何解决? 必须保证一个线程a在操作ticket的过程中,其他线程必须等待,
            直到线程a操作ticket结束以后,其它线程才可以进来继续操作ticket。(ticket是共享数据)
            比如上厕所几个坑位,坑位就是共享数据,排队进去后,上完,后面的人才能进来,不能一次进去好几个人,这样才安全。


4.Java是如何解决线程的安全问题? 使用线程的同步机制。

方式1:同步代码块

synchronized(同步监视器){
    //需要被同步的代码
}

说明:
> 需要被同步的代码,及为操作共享数据的代码
> 共享数据:即多个线程都需要操作的数据。比如:ticket
> 需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其他线程必须等待。
> 同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码。
              比如一个坑位,有个门锁,你进去把门锁上别人就进不来了,上完厕所把锁打开其他人才能进来。
> 同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器。

注意:在实现Runnable接口的方式中,同步监视器可以考虑使用:this。
      在继承Thread类的方式中,同步监视器要慎用this,可以考虑使用:当前类.class。


方式2:同步方法

说明:
> 如果操作共享数据的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。
> 非静态的同步方法,默认同步监视器是thisthis还是要考虑是不是唯一的。
    静态的同步方法,默认的同步监视器是当前类本身。当前类名.class


5. synchronized的好处:解决了线程的安全问题

   弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能底。

使用 同步方法 解决两种线程创建方式的线程安全问题

解决实现Runnable接口方式出现的线程安全问题:

package thread.demo03_threadsafe.runnablesafe;

//使用同步方法解决实现Runnable接口的线程安全问题。

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

        SaleTicket1 s = new SaleTicket1();

        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);

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

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


class SaleTicket1 implements Runnable{
    int ticket = 100;
    boolean isFlag = true;//之前while(true)没法跳出循环,就加了这一行。

    @Override
    public void run() {

        while (isFlag){

                show();

        }

    }

    //同步方法是非静态的,默认的同步监视器就是this。
    public synchronized void show(){    //此时的同步监视器是:this。此题目中即为s,是唯一的。
        if (ticket > 0){

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

            System.out.println(Thread.currentThread().getName() + "售票,票号为: " + ticket);
            ticket--;
        }else {
            isFlag = false;
        }

    }

}

解决继承Thread方式出现的线程安全问题:

package thread.demo03_threadsafe.threadsafe;

//使用同步方法解决继承Thread类的线程安全问题

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

        SaleTicket2 s1 = new SaleTicket2("窗口1");
        SaleTicket2 s2 = new SaleTicket2("窗口2");
        SaleTicket2 s3 = new SaleTicket2("窗口3");

        s1.start();
        s2.start();
        s3.start();

    }
}


class SaleTicket2 extends Thread{

    public SaleTicket2() {
    }

    public SaleTicket2(String name) {
        super(name);
    }

    static int ticket = 100;//没有static直接300张票,每个窗口都卖1——100
    //一个对象一份的是实例变量。
    //所有对象一份的是静态变量。

    static boolean isFlag = true;

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

                show();

        }
    }

//    public synchronized void show(){    //此时同步监视器:this。此题目中this有好几个,s1,s2,s3,仍然是线程不安全的
    public static synchronized void show(){     //此时同步监视器:当前类本身,即为SaleTicket2.class,是唯一的,所以线程是安全的。
    //具体还要看适不适合加成静态,不要为了改成线程安全就加静态static。
            if (ticket > 0){

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

                System.out.println(Thread.currentThread().getName() + "票号为: " + ticket);
                ticket--;
            }else {
                isFlag = false;
            }
        }

}

Thread类的常用方法和生命周期练习:

题目需求:
新年倒计时

模拟新年倒计时,每隔一秒输出一个数字,依次输出10,9,8......1,最后输出,新年快乐!
package thread.demo02_lifecycle.exer;

//题目:模拟新年倒计时,每隔一秒输出一个数字,依次输出10,9,8......1,最后输出,新年快乐!

//题目没有其它要求,就直接在主线程里面写出来
public class HappyNewYear {
    public static void main(String[] args) {

        for (int i = 10; i >= 0; i--) {

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

            if (i > 0){
            System.out.println("新年倒计时:" +i + "天");
            }else {
                System.out.println("新年快乐!!!");
            }

        }

    }
}

输出:

多线程-*同步方法*解决两种线程创建方式的线程安全问题及练习_第1张图片

Thread类的常用方法和生命周期面试:

package thread.demo02_lifecycle.interview;

/*
关于Thread.sleep()方法的一个面试题:如下的代码中sleep()执行后,到底哪个线程进入阻塞状态了呢?
 */

public class ThreadTest {
    public static void main(String[] args) {
        //创建线程对象
        MyThread t = new MyThread("线程1");
        t.start();

        //调用sleep方法
        try {
            t.sleep(1000 * 5);//t还是在主线程里面调sleep方法,这里可以理解为对象调sleep方法。
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //5秒后这里才会执行。
        System.out.println("Hello,World!");//阻塞的是主线程
    }
}


class MyThread extends Thread{
    public MyThread() {
    }

    public MyThread(String name){

    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

解决线程安全问题的练习:

银行有一个账户。

有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
问题:该程序是否有安全问题,如果有,如何解决?

【提示】
1. 明确哪些代码是多线程运行代码,须写入run()方法。
2. 明确什么是共享数据。
3. 明确多线程运行代码中哪些语句是操作共享数据的。
package thread.demo03_threadsafe.exer;

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

        Account acct = new Account();

        Customer customer1 = new Customer(acct,"储户甲");
        Customer customer2 = new Customer(acct,"储户乙");

        customer1.start();
        customer2.start();

    }
}

class Account{  //账户
    private double balance; //余额

    public synchronized void deposit(double amt){
        if(amt > 0){
            balance += amt;
        }

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

        System.out.println(Thread.currentThread().getName() + "存钱1000元,账户余额为: " + balance);
    }
}

class Customer extends Thread{  //Customer客户
    Account account;

    public Customer(Account acct){
        this.account = acct;
    }

    public Customer(Account acct,String name){
        super(name);
        this.account = acct;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {

            //有了这一段就能看出来交互了,甲存一次,乙存一次。
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            account.deposit(1000);

        }
    }
}

输出:

多线程-*同步方法*解决两种线程创建方式的线程安全问题及练习_第2张图片

你可能感兴趣的:(java学习,java)