线程安全解决-锁-并发包
为了解决多线程安全问题,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 一条娱乐新闻