保证线程安全——synchronized同步代码块、同步方法、Lock互斥锁

在使用线程时,经常会出现多个线程同时抢夺一个共享资源,结果导致得不到预期的结果

为了防止此类现象发生,也就引出了线程同步的概念。

 

线程同步:规定的代码块中,两个或多个线程之间要按顺序执行(不能同时执行)

线程安全:两个或两个以上的线程在执行任务过程中,操作共享资源仍然得到正确结果

 

作个例子:

两个售票窗卖同一类票,100张票

写一个自定义类实现Runnable接口

public class MyRunnable implements Runnable {
    private int tickets = 100;


    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                try {
                    Thread.sleep(20);            //睡眠20ms只是为了让效果更明显
                   System.out.println(Thread.currentThread().getName() + " 卖出一张票,剩余 " + --tickets);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } else {
                System.out.println("卖完了");
                break;
            }
        }
    }
}

主方法中开启两个线程

MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr, "A窗口");
Thread t2 = new Thread(mr, "B窗口");
t1.start();
t2.start();

结果:

保证线程安全——synchronized同步代码块、同步方法、Lock互斥锁_第1张图片保证线程安全——synchronized同步代码块、同步方法、Lock互斥锁_第2张图片

不仅出现了剩余负数张票,还有一张票被售卖了两次的情况!!

原因是两个线程刚好同时进入方法中,拿到的票数一样,这是我们不想看到的

这就是两个线程共同抢夺一个共享资源,导致线程不安全的结果

 

 

要解决这样的线程不安全问题就得给两个线程的共享资源上锁

使得一个线程进入后,另一个线程得等它执行完才可以进入

 


synchronized同步代码块:

格式:

synchronized(锁对象){

//操作共享资源的代码

}

锁对象的数据类型可以是任意类型,可是必须是所有线程都共享的

继续使用上面举的例子,加上synchronized锁,看看效果

public void run() {
    while (true) {
        synchronized (this) {
            if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + " 卖出一张票,剩余 " + --tickets);
            } else {
                System.out.println("卖完了");
                break;
            }
        }
    }
}

主方法中,使用同一个Runnable实现类对象target创建A窗口和B窗口线程

MyRunnable target = new MyRunnable();
Thread t1 = new Thread(target, "A窗口");              //两个线程都是用同一个target创建对象
Thread t2 = new Thread(target, "B窗口");              //两个线程都是用同一个target创建对象
t1.start();
t2.start();

试验多次,都再无出现负数票和一张票被卖了两次的情况

因为在此类中,两个线程之间共享的资源很明显,是创建线程时的target

则在上面synchronized(this),this就表示是target

 


若使用的是继承Thread类创建的线程对象,则就不能使用this作为锁对象

且不像实现Runnable那样,创建对象后,因为创建线程时指定了同一个target目标参数

所以成员变量在两个线程中仍能共享

 

继承Thread类的方法,创建线程时,没有指定target目标

唯一相同的地方就是使用同一个MyThread类创建的对象然后开启线程

所以将共享的tickets成员变量变为静态,保存在类中

使得所有创建该类对象的进程都会共享此成员变量

MyThread t1 = new MyThread();                //两个线程都使用MyThread创建对象
MyThread t2 = new MyThread();                //两个线程都使用MyThread创建对象
t1.setName("A窗口");
t2.setName("B窗口");
t1.start();
t2.start();

锁对象就是创建对象时,共用的MyThread.class

public class MyThread extends Thread {
    private static int tickets = 100;              //加入静态,将tickets成员变量保存在类中

    @Override
    public void run() {
        while (true){
            synchronized (MyThread.class){
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + " 卖出一张票,剩余 " + --tickets);
                } else {
                    System.out.println("卖完了");
                    break;
                }
            }
        }
    }
}

 

 

 

 

synchronized同步方法:

与synchronized同步代码块相似的线程同步方法

 

格式:

修饰符   synchronized   返回值类型   方法名   (参数列表) { }

 

实现Runnable接口,并使用同步方法来保护线程安全:

不仅在run()方法中对tickets>0进行判断,在同步方法test()也对tickets>0进行判断

是因为,同步锁在方法中,若tickets只剩1张

两个线程同时进入了run方法,没作判断的同步方法执行完一个线程还会执行另一个线程就又会出现负数票

所以同步方法还要作一次条件判断

public class MyRunnable implements Runnable {
    private int tickets = 100;


    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                test();
            } else {
                System.out.println("卖完了..");
                break;
            }
        }
    }

    public synchronized void test() {
        if (tickets > 0) {
            System.out.println(Thread.currentThread().getName() + " 卖出一张票,剩余 " + --tickets);
        }
    }
}

 

 

继承Thread类的线程使用同步方法:

切记:继承Thread类创建线程,使用同步方法必须是静态的!!

public class MyThread extends Thread {
    private static int tickets = 100;

    @Override
    public void run() {
        while (true) {
            if (tickets > 0) {
                test();
            } else {
                System.out.println("卖完了..");
                break;
            }
        }
    }

    public static synchronized void test() {        //静态同步方法
        if (tickets > 0) {
            System.out.println(Thread.currentThread().getName() + " 卖出一张票,剩余 " + --tickets);
        }
    }
}

 


Lock接口:

常用实现类:ReentrantLock  互斥锁

常用方法:

void  lock() 获取锁
void  unlock() 释放锁

由互斥锁顾名思义:

将共享的资源锁起来,然后等待一个线程执行完毕后再释放锁,让另一个线程进入

实现Runnable的类创建线程方法使用的是同一个target

所以直接创建Lock对象,即可给所有线程共享且唯一

public class MyRunnable implements Runnable {
    private int tickets = 100;
    Lock lockObj = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lockObj.lock();
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + " 卖出了一张票,剩余 " + --tickets);
                lockObj.unlock();
            } else {
                System.out.println("卖完了..");
                lockObj.unlock();
                break;
            }
        }
    }
}

 

 

继承Thread类创建线程方法使用的是同一个类

所以创建的Lock对象必须是静态的,才能供所有线程对象使用,且唯一

public class MyThread extends Thread {
    private static int tickets = 100;
    static Lock lockObj = new ReentrantLock();            //静态互斥锁对象

    @Override
    public void run() {
        while (true) {
            lockObj.lock();
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + " 卖出了一张票,剩余 " + --tickets);
                lockObj.unlock();
            } else {
                System.out.println("卖完了..");
                lockObj.unlock();
                break;
            }
        }
    }
}

 

你可能感兴趣的:(保证线程安全——synchronized同步代码块、同步方法、Lock互斥锁)