线程安全解决-锁-并发包

线程安全解决-锁-并发包

为了解决多线程安全问题,java官方提供了锁的概念,还有一些并发包,下面让我们来认识一下吧。

AtomicInteger类实现原理
上一章我们说到了AtomicInteger类它的底层采用乐观锁的概念那么什么是乐观锁,就是在线程运行之前,先把自己线程栈中的变量副本与静态区中的变量副本进行比较,如果一样就不需要获取值了,如果不一样就需要到静态区中在获取一次变量副本,从而保证了数据的原子性。下面用一张图来展示乐观锁的大致原理。
在这里插入图片描述

下面让我们看一下对于多线程并发,所产生的的问题,我们以一个例子来说明:
模仿一个卖票系统,有四个人(四个线程)来买票,100张票卖完为止。

public class Ticket extends Thread {
    private int ticket = 100;

    @Override
    public void run(){
        while (ticket > 0){
            System.out.println(Thread.currentThread().getName() + "***" + ticket);
            ticket--;
        }
    }
}


public class Test {
    public static void main(String[] args) {
        Ticket t = new Ticket();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
    }
}

测试结果:
在这里插入图片描述
我们可以看到有些数据已经产生了错乱,两个人买到了相同的票,还有的票直接就消失了,买不到,
这就是线程在高并发下所产生的的问题。

synchronized同步代码块

synchronized的实现机制是(悲观锁),对于synchronized代码块每次只能进去一个线程,对数据进行操作,操作完成之后,及时更新静态区中的变量值,下一次其他线程在运行前就会再次去静态区中获取值,这样保证了数据的安全。

格式:synchronized(任意对象) {可能出现线程安全的代码}
作用:加上同步代码块之后,就不会出现上面发生的问题了。

public class Ticket extends Thread {
    private int ticket = 100;

	//作为锁对象传入到synchronized代码块参数中
    Object obj = new Object();

    @Override
    public void run(){
    	//多个线程的锁对象必须都是同一个对象
        synchronized (obj) {
            while (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "***" + ticket);
                ticket--;
            }
        }
    }
}


public class Test {
    public static void main(String[] args) {
    	//创建Thread类对象
        Ticket t = new Ticket();

		//把创建的Thread对象传入Thread类并且开启线程
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
    }
}

测试结果:
在这里插入图片描述
我们可以发现加上同步代码块以后,已经解决了数据错乱的问题。

synchronized修饰方法

public class Ticket extends Thread {
    private int ticket = 100;

    @Override
    public synchronized void run(){
        while (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "***" + ticket);
            ticket--;
        }
    }
}


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

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

测试结果:
在这里插入图片描述

synchronized修饰静态方法

public class Ticket extends Thread {
    static int ticket = 100;

    public static synchronized void show(){
        while (ticket > 0) {
            System.out.println(Thread.currentThread().getName() + "***" + ticket);
            ticket--;
        }
    }

    @Override
    public void run(){
        show();
    }
}


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

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

在这里插入图片描述

Lock锁

它与synchronized的作用是一样的,它是jdk1.5以后出现的,它是一个接口,具体使用看如下代码。

public class Ticket extends Thread {
    private int ticket = 100;

    //它是一个接口,要new它的子类
    Lock lock = new ReentrantLock();

    @Override
    public void run(){
        lock.lock();    //获取锁
        try {
            while (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + "***" + ticket);
                ticket--;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();  //释放锁 无论是否出现异常都要释放锁
        }
    }
}

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

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

测试结果:
Thread-1***100
Thread-1***99
Thread-1***98
Thread-1***97
Thread-1***96
Thread-1***95
Thread-1***94
Thread-1***93

synchronized与Lock的方法(等待唤醒机制)

在Object类中我们可以看到又这么三个方法,可以让synchronized来使用
wait() 让当前线程等待
notify() 唤醒当前线程
notifyAll() 唤醒所有线程

Locks接口中也有一个实现类 Condition 提供了一些方法。
await() 让当前线程等待
signal() 唤醒当前线程
signalAll() 唤醒所有线程

Lock中有一个方法可以获取 Condition 对象
newCondition() 获取Condition对象

下面以一个生产者消费者的例子来使用一下:

包子类:

/*
 * JDK1.5以后出现了 Lock锁
 * Lock lock = new ReentrantLock();获取锁对象
 * -----lock.lock() 开启锁      lock.unlock() 释放锁
 * 以前用 synchronized 同步的方法只能拥有一个锁对象
 * 现在可以使用 lock.newCondition() 来获取多个锁对象 进行对不同线程的业务操作
 * -----await()等待        signal()释放       signalAll()释放所有
 */
public class BZ {
    private String name;
    private int count = 1;
    private boolean flag = false;   //存储商品状态 如果false就让生产者运行  true就让消费者运行

    private Lock lock = new ReentrantLock();    // 创建lock锁  lock锁可以创建多个锁对象(Condition)根据不同的需求对不同的线程进行操作
    private Condition condition_p = lock.newCondition();   //获取消费者锁对象
    private Condition condition_c = lock.newCondition();    //获取生产者锁对象

    public void set(String name){
        lock.lock();                    //锁
        try{
            while (flag){               //循环判断是否符合条件 防止溜掉一些在await方法后被唤醒的线程
                condition_c.await();    //如果有商品 就让生产者线程等待
            }
            this.name = name + count++; //设置商品名称 并且加上编号
            System.out.println(Thread.currentThread().getName() + "--生产者做--" + this.name);
            flag = true;                //改变商品状态
            condition_p.signalAll();    //唤醒所有消费者线程
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();              //无论啥情况 都释放锁
        }
    }

    public void get(){
        lock.lock();                    //锁
        try {
            while (!flag) {             //循环判断是否符合条件 防止溜掉一些在await方法后被唤醒的线程
                condition_p.await();    //如果没有商品 就让消费者线程等待
            }
            System.out.println(Thread.currentThread().getName() + "--消费者吃---------" + this.name);
            flag = false;               //改变商品状态
            condition_c.signalAll();    //唤醒生产者线程
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            lock.unlock();               //无论啥情况 都释放锁
        }
    }
}

消费者线程:使用了包子类中的get方法

/**
 * 消费者
 */
public class Producer implements Runnable {

    private BZ bz;
    Producer(BZ bz){    //创建构造方法   让创建该对象的时候必须传入一个BZ类
        this.bz = bz;
    }

    @Override
    public void run() {
        while (true){
            bz.get();
        }
    }
}

生产者线程:使用了包子类中的set方法

/**
 * 生产者
 */
public class Consumer implements Runnable {

    private BZ bz;
    Consumer(BZ bz){     //创建构造方法   让创建该对象的时候必须传入一个BZ类
        this.bz = bz;
    }

    @Override
    public void run() {
        while (true){
            bz.set("包子");
        }
    }
}

测试类:

public class Demo {
    public static void main(String[] args) {
        BZ bz = new BZ();   //创建包子对象

        Consumer c = new Consumer(bz);  //创建生产者对象
        Producer p = new Producer(bz);  //创建消费者对象

        Thread t1 = new Thread(c);  //构建生产者线程
        Thread t2 = new Thread(c);  //构建生产者线程
        Thread t3 = new Thread(p);  //构建消费者线程
        Thread t4 = new Thread(p);  //构建消费者线程

        //开启线程
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}


运行结果:
Thread-0--生产者做--包子1
Thread-2--消费者吃---------包子1
Thread-0--生产者做--包子2
Thread-2--消费者吃---------包子2
Thread-0--生产者做--包子3
Thread-2--消费者吃---------包子3
Thread-0--生产者做--包子4
Thread-2--消费者吃---------包子4
Thread-0--生产者做--包子5
Thread-2--消费者吃---------包子5
Thread-0--生产者做--包子6
Thread-2--消费者吃---------包子6
Thread-0--生产者做--包子7

并发包:

我们知道在集合中ArrayList、HashSet、HashMap等集合线程都是不安全的,那么在多线程的情况下,我们该用那些集合呐:

CopyOnWriteArrayList (与ArrayList一样,但是它是线程安全的)
CopyOnWriteArraySet (与HashSet一样,但是它是线程安全的)
ConcurrentHashMap (与HashMap一样,但是它是线程安全的)
Hashtable(与HashMap一样,但是效率低、被ConcurrentHashMap代替了)

创建方式:其他的就用法与ArrayList集合一样,就不在一一演示了。Set与Map也是。

CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<> ();

下面介绍几个并发包需要了解的类
CountDownLatch: 就是在创建CountDownLatch对象的时候给它传入一个值,该值是int类型,意思就是当该对象的int值为0时,被该对象await的方法就会被解除冻结状态。

方法:
await() 让当前线程等待
countDown() 相当于cuont–

案例:

需求:请使用CountDownLatch编写一个程序,实现以下效果:
	线程A打印:”开始计算”
	线程B:计算1--100所有数的累加和,并打印结果。
	线程A打印:”计算完毕”

//线程 A
public class CountThreadA extends Thread {
    private CountDownLatch count;

    public CountThreadA(String name, CountDownLatch count) {
        super(name);
        this.count = count;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始计算");
        try {
            count.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "计算完毕");
    }
}

//线程 B
public class CountThreadB extends Thread {
    private CountDownLatch count;

    public CountThreadB(String name, CountDownLatch count) {
        super(name);
        this.count = count;
    }

    @Override
    public void run() {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            sum += i;
        }
        System.out.println(Thread.currentThread().getName() + "结果为: " + sum);
        count.countDown();
    }
}

//测试类
public class Work4 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch count = new CountDownLatch(1);

        new CountThreadA("线程A",count).start();

        Thread.sleep(100); //防止线程B先执行

        new CountThreadB("线程B",count).start();

    }
}

结果:
线程A开始计算
线程B结果为: 5050
线程A计算完毕

CyclicBarrier: 被该对象await的数量达到指定数量时,会执行指定的线程

构造:new CyclicBarrier(个数, Runnable{});
方法:await() 让该线程等待

案例:

public class MyThread extends Thread {
    private CyclicBarrier barrier;

    public MyThread(CyclicBarrier barrier) {
        this.barrier = barrier;
    }

    @Override
    public void run() {
        System.out.println("我执行完毕!进入等待状态!");
        try {
            barrier.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (BrokenBarrierException e) {
            e.printStackTrace();
        }
    }
}


public class Demo {
    public static void main(String[] args) {
        //当有一个线程被该对象.await时触发该线程。
        CyclicBarrier barrier = new CyclicBarrier(1, new Runnable() {
            @Override
            public void run() {
                System.out.println("已经让一个线程等待了!");
            }
        });

        new MyThread(barrier).start();
    }
}

结果:
我执行完毕!进入等待状态!
已经让一个线程等待了!

(等待员工到齐开会案例!)

Semaphore: 创建对象时传入指定的int值,表示一次最多有几个线程并发执行,
acquire(); 表示获取执行资格
release(); 表示释放执行资格

请使用Semaphore编写一个程序,实现以下效果:
有10名游客要参观展览室,而“展览室”同时只允许最多“三个游客”参观,每个游客参观时间2秒。


public class SemaThread extends Thread {

    private Semaphore semaphore;

    public SemaThread(Semaphore semaphore) {
        this.semaphore = semaphore;
    }

    @Override
    public void run() {
        try {
            semaphore.acquire(); //获取执行资格
            System.out.println(Thread.currentThread().getName() + "正在参观");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + "完毕");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            semaphore.release(); //释放执行资格
        }
    }
}


public class Work6 {
    public static void main(String[] args) {
    	//传入3代表每次最多只能有3个线程并发执行
        Semaphore s = new Semaphore(3);

        for (int i = 0; i < 10; i++) {
            new SemaThread(s).start();
        }

    }
}

Exchanger:
可以让两个线程之间交换数据
exchange(V x);参数是传递给对方线程的,接收的返回值是对方线程传过来的数据。

请使用Exchanger编写一个程序,实现两个线程的信息交互:
	线程A给线程B:一条娱乐新闻
	线程B给线程A:一条体育新闻


public class ExchangerThreadA extends Thread {
    private Exchanger<String> exchanger;

    public ExchangerThreadA(Exchanger<String> exchanger,String name) {
        super(name);
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
        try {
            String e = exchanger.exchange(" 一条娱乐新闻");
            System.out.println(Thread.currentThread().getName() + e);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


public class ExchangerThreadB extends Thread {
    private Exchanger<String> exchanger;

    public ExchangerThreadB(Exchanger<String> exchanger,String name) {
        super(name);
        this.exchanger = exchanger;
    }

    @Override
    public void run() {
        try {
            String e = exchanger.exchange(" 一条体育新闻");
            System.out.println(Thread.currentThread().getName() + e);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}



public class Work7 {
    public static void main(String[] args) {
        Exchanger<String> exchanger = new Exchanger();

        new ExchangerThreadA(exchanger,"线程A").start();
        new ExchangerThreadB(exchanger,"线程B").start();
    }
}

结果:
线程A 一条体育新闻 
线程B 一条娱乐新闻

发布了33 篇原创文章 · 获赞 35 · 访问量 1583

猜你喜欢

转载自blog.csdn.net/weixin_45216092/article/details/105033830