Volatile关键字
1.volatile解决可见性
public class MyThread extends Thread{
public static volatile int a = 0;
@Override
public void run() {
System.out.println("Thread-0线程开始执行了,先睡眠2秒钟");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread-0线程睡醒了,改变a的值为1");
a=1;
System.out.println("Thread-0线程的线程任务执行完毕!");
}
}
public class Demo01Visible {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
System.out.println("main线程,会继续执行main方法中的代码");
while (true){
if(MyThread.a==1){
System.out.println("main线程,判断变量a的值为1,结束死循环!");
break;
}
}
}
}
 2.volatile解决有序性  3.volatile不能解决原子性
原子性的解决
概述: 所谓的原子性是指在一次操作或者多次操作中,要么所有的操作全部都得到了执行并且不会受到任何因素的干扰而中断,要么所有的操作都不执行,
多个操作是一个不可以分割的整体(原子)。
比如:从张三的账户给李四的账户转1000元,这个动作将包含两个基本的操作:从张三的账户扣除1000元,给李四的账户增加1000元。这两个操作必须符合原子性的要求,
要么都成功要么都失败
原子类
原子类概述: java从JDK1.5开始提供了java.util.concurrent.atomic包(简称Atomic包),这个包中的原子操作类提供了一种用法简单,性能高效,线程安全地更新一个变量的方式。
1). java.util.concurrent.atomic.AtomicInteger:对int变量进行原子操作的类。
2). java.util.concurrent.atomic.AtomicLong:对long变量进行原子操作的类。
3). java.util.concurrent.atomic.AtomicBoolean:对boolean变量进行原子操作的类。
这些类可以保证对“某种类型的变量”原子操作,多线程、高并发的环境下,就可以保证对变量访问的有序性,从而保证最终的结果是正确的。
AtomicInteger原子型Integer,可以实现原子更新操作
构造方法:
public AtomicInteger(): 初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
成员方法:
int get(): 获取值
int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值
2.AtomicInteger类的基本使用
public class Demo01AtomicInteger {
public static void main(String[] args) {
AtomicInteger ai = new AtomicInteger(10);
int i = ai.get();
System.out.println(i);
int i2 = ai.getAndIncrement();
System.out.println("i2:"+i2);
System.out.println(ai);
int i3 = ai.incrementAndGet();
System.out.println("i3:"+i3);
System.out.println(ai);
int i4 = ai.addAndGet(100);
System.out.println("i4:"+i4);
System.out.println(ai);
int i5 = ai.getAndSet(88);
System.out.println("i5:"+i5);
System.out.println(ai);
}
}
AtomicInteger解决变量的可见性、有序性、原子性(重点)
public class MyThread extends Thread{
public static AtomicInteger money = new AtomicInteger(0);
@Override
public void run() {
System.out.println("Thread-0线程开始改变money的值!");
for (int i = 0; i < 10000; i++) {
money.incrementAndGet();
}
System.out.println("Thread-0线程执行完毕!");
}
}
public class Demo01Thread {
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread();
mt.start();
System.out.println("main继续往下执行");
System.out.println("main线程开始改变money的值!");
for (int i = 0; i < 10000; i++) {
MyThread.money.incrementAndGet();
}
System.out.println("main线程暂停1秒,让Thread-0执行完毕!");
Thread.sleep(1000);
System.out.println("最终money为:"+ MyThread.money);
}
}
3.AtomicInteger的原理_CAS机制(乐观锁)

第二章 线程安全
AtomicInteger可以对“int类型的变量”做原子操作。但如果需要将“很多行代码”一起作为“原子性”执行——一个线程进入后,必须将所有代码行执行完毕,其它线程才能进入,可以使用synchronized关键字——重量级的同步关键字。
AtomicInteger:只能解决一个变量的原子性
synchronized:可以解决一段代码的原子性 
2.线程安全问题的代码实现
public class RunnableImpl implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!");
ticket--;
}
}
}
}
public class Demo01PayTticket {
public static void main(String[] args) {
RunnableImpl r = new RunnableImpl();
Thread t0 = new Thread(r);
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t0.start();
t1.start();
t2.start();
}
}

4.解决线程安全问题的第一种方式使用同步代码块(重点)
public class RunnableImpl implements Runnable {
private int ticket = 100;
String obj = "abc";
@Override
public void run() {
while (true){
synchronized (obj){
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!");
ticket--;
}
}
}
}
}
public class Demo01PayTticket {
public static void main(String[] args) {
RunnableImpl r = new RunnableImpl();
Thread t0 = new Thread(r);
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t0.start();
t1.start();
t2.start();
}
}
5.同步原理

6.解决线程安全问题的第二种方式——同步方法
public class RunnableImpl implements Runnable {
private static int ticket = 100;
@Override
public void run() {
System.out.println("this:"+this);
while (true){
payTicketStatic();
}
}
public static void payTicketStatic(){
synchronized (RunnableImpl.class){
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!");
ticket--;
}
}
}
public void payTicket() {
synchronized (this){
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!");
ticket--;
}
}
}
public synchronized void payTicket() {
if(ticket>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!");
ticket--;
}
}
}
public class Demo01PayTticket {
public static void main(String[] args) {
RunnableImpl r = new RunnableImpl();
System.out.println("r:"+r);
Thread t0 = new Thread(r);
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t0.start();
t1.start();
t2.start();
}
}
7.解决线程安全的第三种方式——Lock锁
public class RunnableImpl implements Runnable {
private int ticket = 100;
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
if(ticket>0){
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
}
8.CAS和Synchronized
CAS和Synchronized都可以保证多线程环境下共享数据的安全性。那么他们两者有什么区别?
Synchronized是从悲观的角度出发:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁
(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。因此Synchronized我们也将其称之为悲观锁。jdk中的ReentrantLock也是一种悲观锁。
CAS是从乐观的角度出发:
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。
CAS这种机制我们也可以将其称之为乐观锁。
并发包
在JDK的并发包java.util.concurrent里提供了几个非常有用的并发容器和并发工具类。供我们在多线程开发中进行使用。这些集合和工具类都可以保证高并发的线程安全问题.
1.并发List集合CopyOnWriteArrayList(重点)
1).java.util.concurrent.CopyOnWriteArrayList(类):它是一个“线程安全”的ArrayList,而java.utils.ArrayList不是线程安全的。 2).如果是多个线程,并发访问同一个ArrayList,我们要使用:CopyOnWriteArrayList
public class MyThread extends Thread{
public static CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
list.add(i);
}
System.out.println("Thread-0线程往集合中添加元素结束!");
}
}
public class Demo01CopyOnWriteArrayList {
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread();
mt.start();
System.out.println("main线程继续执行");
for (int i = 0; i < 1000; i++) {
MyThread.list.add(i);
}
System.out.println("main线程休息1秒");
Thread.sleep(1000);
System.out.println("最终集合的长度为:"+MyThread.list.size());
}
}
ArrayList集合的并发问题:
main线程会继续执行,往集合中添加元素
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 73
Thread-0线程开始执行了...往集合中添加元素!
Thread-0线程结束执行了!
at java.util.ArrayList.add(ArrayList.java:463)
at com.llz.demo12CopyOnWriteArrayList.Demo01.main(Demo01.java:12)
main线程往集合中添加元素
main线程休息1秒钟,让Thread-0线程先执行完毕
Thread-0线程往集合中添加元素结束!
最终集合的长度为:1998
main线程会继续执行,往集合中添加元素
main线程添加数据结束,等待1秒钟,等待Thread-0线程执行完毕
Thread-0线程开始执行了...往集合中添加元素!
Thread-0线程结束执行了!
ArrayList集合的总长度为:2000
2.并发Set集合CopyOnWriteArraySet(重点)
public class MyThread extends Thread{
public static CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet<>();
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
set.add(i);
}
System.out.println("Thread-0线程执行完毕!");
}
}
public class Demo01CopyOnWriteArraySet {
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread();
mt.start();
System.out.println("main线程继续执行");
for (int i = 1000; i < 2000; i++) {
MyThread.set.add(i);
}
System.out.println("main线程休息1秒");
Thread.sleep(1000);
System.out.println("最终集合的长度为:"+ MyThread.set.size());
}
}
HashSet集合存在并发问题:
main线程会继续执行,往集合中添加元素
Thread-0线程开始执行了...往集合中添加元素!
Thread-0线程结束执行了!
main线程添加数据结束,等待1秒钟,等待Thread-0线程执行完毕
HashSet集合的总长度为:1996
main线程会继续执行,往集合中添加元素
Thread-0线程开始执行了...往集合中添加元素!
Thread-0线程结束执行了!
main线程添加数据结束,等待1秒钟,等待Thread-0线程执行完毕
HashSet集合的总长度为:1999
3.并发Map集合ConcurrentHashMap(重点)
public class MyThread extends Thread{
public static Hashtable<Integer,Integer> map = new Hashtable<>();
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
map.put(i,i);
}
System.out.println("Thread-0线程执行结束!");
}
}
public class Demo01ConcurrentHashMap {
public static void main(String[] args) throws InterruptedException {
MyThread mt = new MyThread();
mt.start();
System.out.println("main线程继续执行");
for (int i = 1000; i < 2000; i++) {
MyThread.map.put(i,i);
}
System.out.println("main线程休息1秒");
Thread.sleep(1000);
System.out.println("最终集合的长度为:"+ MyThread.map.size());
}
}
HashMap集合存在并发问题:
main线程会继续执行,往集合中添加元素
Thread-0线程开始执行了...往集合中添加元素!
main线程添加数据结束,等待1秒钟,等待Thread-0线程执行完毕
Thread-0线程结束执行了!
HashMap集合的总长度为:1992
比较ConcurrentHashMap和Hashtable的效率
Java类库中,从1.0版本也提供一个线程安全的Map:Hashtable Hashtable和ConcurrentHashMap有什么区别: Hashtable采用的synchronized——悲观锁,效率更低。 ConcurrentHashMap:采用的CAS 机制——乐观锁,效率更高。
Hashtable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下Hashtable的效率非常低下。因为当一个线程访问Hashtable的同步方法,其他线程也访问Hashtable的同步方法时,会进入阻塞状态。如线程1使用put进行元素添加,线程2不但不能使用put方法添加元素,也不能使用get方法来获取元素,所以竞争越激烈效率越低。  
4.多线程协作CountDownLatch
CountDownLatch允许一个或多个线程等待其他线程完成操作。
例如:线程1要执行打印:A和C,线程2要执行打印:B,但线程1在打印A后,要线程2打印B之后才能打印C,所以:线程1在打印A后,必须等待线程2打印完B之后才能继续执行。
CountDownLatch构造方法:
public CountDownLatch(int count)
CountDownLatch重要方法:
public void await() throws InterruptedException
public void countDown()
示例
public class MyThreadAC extends Thread {
private CountDownLatch countDownLatch;
public MyThreadAC(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println("A");
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("C");
}
}
public class MyThreadB extends Thread {
private CountDownLatch countDownLatch;
public MyThreadB(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println("B");
countDownLatch.countDown();
}
}
public class Demo01Thread {
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(1);
new MyThreadAC(cdl).start();
Thread.sleep(1000);
new MyThreadB(cdl).start();
}
}
执行结果: 会保证按:A B C的顺序打印。
说明: CountDownLatch中count down是倒数的意思,latch则是门闩的含义。整体含义可以理解为倒数的门栓,似乎有一点“三二一,芝麻开门”的感觉。
CountDownLatch是通过一个计数器来实现的,每当一个线程完成了自己的任务后,可以调用countDown()方法让计数器-1,当计数器到达0时,调用CountDownLatch。
await()方法的线程阻塞状态解除,继续执行。
多线程协作CyclicBarrier
概述
CyclicBarrier的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。
例如:公司召集5名员工开会,等5名员工都到了,会议开始。
我们创建5个员工线程,1个开会线程,几乎同时启动,使用CyclicBarrier保证5名员工线程全部执行后,再执行开会线程。
CyclicBarrier构造方法:
public CyclicBarrier(int parties, Runnable barrierAction)
CyclicBarrier重要方法:
public int await()
示例
public class PersonThread extends Thread {
private CyclicBarrier cb;
public PersonThread(CyclicBarrier cb) {
this.cb = cb;
}
@Override
public void run() {
try {
Thread.sleep((int) (Math.random() * 1000));
System.out.println(Thread.currentThread().getName()+"...来到了会场!");
cb.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public class MeetingThread extends Thread{
@Override
public void run() {
System.out.println("人齐了,开始开会了!");
}
}
public class Demo {
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(5,new MeetingThread());
PersonThread p1 = new PersonThread(cb);
PersonThread p2 = new PersonThread(cb);
PersonThread p3 = new PersonThread(cb);
PersonThread p4 = new PersonThread(cb);
PersonThread p5 = new PersonThread(cb);
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
}
}
执行结果:
Thread-3...来到了会场!
Thread-2...来到了会场!
Thread-5...来到了会场!
Thread-1...来到了会场!
Thread-4...来到了会场!
人齐了,开始开会了!
使用场景
使用场景:CyclicBarrier可以用于多线程计算数据,最后合并计算结果的场景。 需求:使用两个线程读取2个文件中的数据,当两个文件中的数据都读取完毕以后,进行数据的汇总操作。
6.并发数量控制SemaphoreSemaphore的主要作用是控制线程的并发数量。
synchronized可以起到"锁"的作用,但某个时间段内,只能有一个线程允许执行。
Semaphore可以设置同时允许几个线程执行。
Semaphore字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。
Semaphore构造方法:
public Semaphore(int permits) permits 表示许可线程的数量
public Semaphore(int permits, boolean fair) fair 表示公平性,如果这个设为 true 的话,下次执行的线程会是等待最久的线程
Semaphore重要方法
public void acquire() 表示获取许可 lock
public void release() 表示释放许可 unlock
示例:同时允许两个线程执行
public class ClassRoom {
private Semaphore semaphore = new Semaphore(2);
public void info() throws InterruptedException {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"..来到了教室!");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName()+"..离开了教室!");
semaphore.release();
}
}
public class StudentThread extends Thread{
private ClassRoom classRoom;
public StudentThread(ClassRoom classRoom) {
this.classRoom = classRoom;
}
@Override
public void run() {
try {
classRoom.info();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Demo {
public static void main(String[] args) {
ClassRoom classRoom = new ClassRoom();
for (int i = 0; i < 5; i++) {
new StudentThread(classRoom).start();
}
}
}
执行结果:
Thread-1..来到了教室!
Thread-0..来到了教室!
Thread-1..离开了教室!
Thread-0..离开了教室!
Thread-2..来到了教室!
Thread-3..来到了教室!
Thread-3..离开了教室!
Thread-2..离开了教室!
Thread-4..来到了教室!
Thread-4..离开了教室!
7.线程信息交互Exchanger
概述
Exchanger(交换者)是一个用于线程间协作的工具类。Exchanger用于进行线程间的数据交换。
这两个线程通过exchange方法交换数据,如果第一个线程先执行exchange()方法,它会一直等待第二个线程也执行exchange方法,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。
Exchanger构造方法:
public Exchanger()
Exchanger重要方法:
public V exchange(V x) 参数传递给对方的数据,返回值接收对方返回的数据
示例一:exchange方法的阻塞特性
1).制作线程A,并能够接收一个Exchanger对象:
public class ThreadA extends Thread {
private Exchanger<String> exchanger;
public ThreadA(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
System.out.println("线程A开始执行");
System.out.println("线程A给线程B100元,并从线程B得到一个电影票");
String result = null;
try {
result = exchanger.exchange("100元");
System.out.println("线程A得到的东西:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2).制作线程B
public class ThreadB extends Thread {
private Exchanger<String> exchanger;
public ThreadB(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
System.out.println("线程B开始执行");
System.out.println("线程B给线程A一张电影票,并从线程A得到100元钱");
String result = null;
try {
result = exchanger.exchange("电影票");
System.out.println("线程B得到的东西:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试类
public class Demo {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new ThreadA(exchanger).start();
new ThreadB(exchanger).start();
}
}
执行结果:
线程A开始执行
线程A给线程B100元,并从线程B得到一个电影票
线程B开始执行
线程B给线程A一张电影票,并从线程A得到100元钱
线程A得到的东西:电影票
线程B得到的东西:100元
示例2:exchanger方法的超时
public class ThreadA extends Thread {
private Exchanger<String> exchanger;
public ThreadA(Exchanger<String> exchanger) {
this.exchanger = exchanger;
}
@Override
public void run() {
System.out.println("线程A开始执行");
System.out.println("线程A给线程B100元,并从线程B得到一个电影票");
String result = null;
try {
result = exchanger.exchange("100元",5, TimeUnit.SECONDS);
System.out.println("线程A得到的东西:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (TimeoutException e) {
System.out.println("5秒钟没等到线程B的值,线程A结束!");
}
}
}
public class Demo {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
new ThreadA(exchanger).start();
}
}
结果:
线程A开始执行
线程A给线程B100元,并从线程B得到一个电影票
5秒钟没等到线程B的值,线程A结束!
使用场景
使用场景:可以做数据校对工作
需求:比如我们需要将纸制银行流水通过人工的方式录入成电子银行流水。为了避免错误,采用AB岗两人进行录入,录入到两个文件中,系统需要加载这两个文件,
并对两个文件数据进行校对,看看是否录入一致,
|