JUC多线程
- 1. volatile 关键字 内存可见性
- 2. 原子变量与CAS算法
-
- 原子变量
- CAS算法
- 3. 同步容器类 ConcurrentHashMap
- 4.CountDownLatch闭锁
- 5.实现Callable接口
- 6.同步锁 Lock
1. volatile 关键字 内存可见性
- 内存可见性问题:当多线程操作共享数据时,彼此不可见。(解决方法1:使用synchronize 同步 锁让每一次读取数据都从内存中去读 保证了数据的及时的更新,但是效率还是比较低;2:使用volatile关键字)
- volatile作用:当多个线程操作共享数据时,可以保证内存中的数据是可见的。(相教于synchronized 是一种较为轻量级的同步策略)
- volatile与synchronized不同点:1. volatile不具备“互斥性”(互斥性当前资源有且只能一个线程访问;2. volatile 不能保证变量的“原子性”)
2. 原子变量与CAS算法
原子变量
i++
的原子性问题:i++ 的操作实际包含了三个步骤“读 该 写”
- 原子变量:在jdk1.5以后提供了
包含了常用的原子变量AtomicXXX(原子变量:volatile的特性,它里面封装的变量有volatile修饰,故而内存可见性;CAS算法 compare-and-swap保证了数据的原子性。)java.util.concurrent.atomic
CAS算法
- CAS算法 是硬件对于并发操作共享数据的共享支持
- 包含了三个操作数 内存值 V 预估值 A 更新值 B
- 特点:当且仅当V==A时,才V=B;否则,不做任何操作。
- 每一次都先从内存值值取数据,(预估值与内存值进行比较,如果相同 才将更新的值 赋值非内存值,否则不做任何操作)
3. 同步容器类 ConcurrentHashMap
- 在java5.0以后 在
包下提供了许多种并发的容器来改进同步容器的性能java.util.concurrent
-
:是线程安全的 锁的分段机制 默认有16个级别的ConcurrentHashMap
段segment
每一个段都有一个独立的锁,支持同时多个线程同时访问,效率比hashtable高(增加了一个线程安全的hash表,分段锁代替了hashtable的独占锁,进而提高了性能)concurrentLevel
4.CountDownLatch闭锁
- 在java 5.0在java.util.concurrent包中,提供了多种并发容器类来改进同步容器的性能
- CountDownLatch一个同步的辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待
- 闭锁 可以延迟线程的进度知道到达终止状态,可以确保某些活动到其他的活动都完成才继续执行
计算10个线程的操作时间:
/**
* 闭锁:先完成某些运算时,只有其他所有线程的运算全部完成时,当前的原酸才能继续执行
*
*/
public class TestCountDownLatch {
public static void main(String[] args) {
// 目的:计算10个线程的总时间
// 错误示范1:下面的方式没有办法计算时间,一个主线程 10个分线程,只有等10个分线程都执行完了才计算他们的时间差,才能达到目的
/*
* final CountDownLatch latch=new CountDownLatch(5); LacthDemo lacthDemo=new
* LacthDemo(latch); //开启多线程 计算10个线程的时间 long start=System.currentTimeMillis();
* for(int i=0;i<10;i++) { new Thread(lacthDemo).start();//开启10个线程 } long
* end=System.currentTimeMillis(); System.out.println("消耗的时间:"+(end-start));
*/
final CountDownLatch latch = new CountDownLatch(10);// 底层有一个count来控制,每此都要减少1,当为0的时候,就继续执行
LacthDemo lacthDemo = new LacthDemo(latch);
// 开启多线程 计算10个线程的时间
long start = System.currentTimeMillis();
for (int i = 0; i < 10; i++) {
new Thread(lacthDemo).start();// 开启10个线程
}
try {
latch.await();// 要它等待,等他为0就继续执行
} catch (InterruptedException e) {// 用于中断的
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("消耗的时间:" + (end - start));
}
}
class LacthDemo implements Runnable {
private CountDownLatch latch;
public LacthDemo(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
// 但是多线程可能存在线程安全问题,故而加一个synchronize
synchronized (this) {
try {
for (int i = 0; i < 5000; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
} finally {
latch.countDown();// 买次执行完就减1,当为0的时候,就继续执行,且必须执行
}
}
}
}
5.实现Callable接口
创建线程的有4种方式:
1. 继承Thread类,并复写run方法,创建该类对象,调用start方法开启线程。此方式没有返回值。
2. 实现Runnable接口,复写run方法,创建Thread类对象,将Runnable子类对象传递给Thread类对象。调用start方法开启线程。此方法2较之方法1好,将线程对象和线程任务对象分离开。降低了耦合性,利于维护。此方式没有返回值。
3. 创建FutureTask对象,创建Callable子类对象,复写call(相当于run)方法,将其传递给FutureTask对象(相当于一个Runnable)。 创建Thread类对象,将FutureTask对象传递给Thread对象。调用start方法开启线程。这种方式可以获得线程执行完之后的返回值。该方法使用Runnable功能更加强大的一个子类.这个子类是具有返回值类型的任务方法。
4. 线程池
Callable创建线程:
1.相较于Runnable:Callable可以有返回值,并且可以抛出异常
2.执行Callable的方式,需要FutureTask实现类的支持,可以用于接收运算的结果,FutureTask是Future的实现类
//创建线程
public class TestCallable {
public static void main(String[] args) {
ThreadDemo treadDemo=new ThreadDemo();
//因为有返回值,执行Callable的方式,需要FutureTask实现类的支持,可以用于接收运算的结果
FutureTask<Integer> res=new FutureTask<>(treadDemo);
new Thread(res).start();//启动线程
try {
//接受线程运算后的结果
Integer sum=res.get();//当在线程的运行过程中,并不会执行,当new Thread(res).start()运行完成后才会执行
//故而:FutureTask也用于闭锁的操作
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//class TreadDemo implements Runnable {
// @Override
// public void run() {
// // TODO Auto-generated method stub
// }
//}
/*
* 1.相较于Runnable:Callable可以有返回值,并且可以抛出异常
* 2.执行Callable的方式,需要FutureTask实现类的支持,可以用于接收运算的结果,FutureTask是Future的实现类
*
*/
class ThreadDemo implements Callable<Integer>{
@Override
public Integer call() throws Exception {//重写call()方法
int sum=0;
for (int i = 0; i<Integer.MAX_VALUE; i++) {
System.out.println(i);
sum+=i;
}
return sum;
}
}
- 所在的包:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
6.同步锁 Lock
- 用于解决多线程安全的方式
- 同步代码块 关键字 synchronized(隐示锁)
- 同步方法 关键字synchronized (隐示锁)
- jdk1.5以后 同步锁Lock 是显示锁,(需要通过
方式进行上锁,通过lock()
来释放锁)更加灵活,但是要注意调用unlock()释放锁,建lock实例,因为lock是接口,所以要使用来的实现类unlock()
ReentrantLock
public class TestLock {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket, "1号窗口").start();
new Thread(ticket, "2号窗口").start();
new Thread(ticket, "3号窗口").start();
}
}
class Ticket implements Runnable {
private int tick = 100;
private Lock lock = new ReentrantLock();// 创建lock实例,因为lock是接口,所以要使用来的实现类ReentrantLock
@Override
public void run() {
while (true) {
lock.lock();// 上锁
try {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
if (tick > 0) {
System.out.println(Thread.currentThread().getName() + ": 完成售票,余票为" + --tick);
}
} finally {
lock.unlock();// 释放锁 必须有最好写到finally
}
}
}
}
- 类所在的包:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
- 利用wait(),notify()等待唤醒机制解决生产消费者问题
//生产者 消费者案例
//生产者 消费者案例
public class TestProductAndConsume2 {
public static void main(String[] args) {
clerk clerk=new clerk();
Productor productor=new Productor(clerk);
Consumer consumer=new Consumer(clerk);
//如果生产者消费者不使用等待唤醒机制 可能就会一段时间内一直消费 或生产 没有进行生产消费的过程
new Thread(productor,"生产者A").start();
new Thread(productor,"生产者C").start();
new Thread(consumer,"消费者B").start();
new Thread(consumer,"消费者D").start();
}
}
//店员
class clerk{
private int product=0;
//进货
public synchronized void get() {
//假设最多可以存放30个商品
while(product>=30) {//解决虚假唤醒 wait应该总是使用在while中
System.out.println("产品满。。。");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+":"+ ++product);//进货
this.notifyAll();
}
//出售
public synchronized void sale() {
while(product<=0) {
System.out.println("缺货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+":"+ --product);//进货
this.notifyAll();
}
}
//生产者
class Productor implements Runnable {//生产者可能有多个
private clerk clerk;
public Productor(clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
for(int i=0;i<100;i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
//消费者
class Consumer implements Runnable{
private clerk clerk;
public Consumer(clerk clerk) {
this.clerk=clerk;
}
@Override
public void run() {
for(int i=0;i<100;i++) {
clerk.sale();
}
}
}
- 通过lock来完成等待唤醒机制,生产者消费者模型
-
:可以控制通信,与Condition
对应的分别是wait,notify,notifyAll
具体代码:await,signal,singalAll
//生产者 消费者案例
public class TestProductAndConsumeLock {
public static void main(String[] args) {
clerk clerk = new clerk();
Productor productor = new Productor(clerk);
Consumer consumer = new Consumer(clerk);
// 如果生产者消费者不使用等待唤醒机制 可能就会一段时间内一直消费 或生产 没有进行生产消费的过程
new Thread(productor, "生产者A").start();
new Thread(productor, "生产者C").start();
new Thread(consumer, "消费者B").start();
new Thread(consumer, "消费者D").start();
}
}
//店员
class clerk {
private int product = 0;
// 使用同步锁来解决
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();// 通过Condition来进行通信
// 进货
public void get() {
lock.lock();
try {
// 假设最多可以存放30个商品
while (product >= 30) {// 解决虚假唤醒
System.out.println("产品满。。。");
// try {
// this.wait();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
try {
condition.await();// 等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ":" + ++product);// 进货
condition.signalAll();// 唤醒
} finally {
lock.unlock();
}
}
// 出售
public void sale() {
lock.lock();
try {
while (product <= 0) {
System.out.println("缺货。。。");
// try {
// this.wait();lock也有自己的等待唤醒
//
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ":" + --product);// 进货
condition.signalAll();
} finally {
lock.unlock();
}
}
}
//生产者
class Productor implements Runnable {// 生产者可能有多个
private clerk clerk;
public Productor(clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.get();
}
}
}
//消费者
class Consumer implements Runnable {
private clerk clerk;
public Consumer(clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
clerk.sale();
}
}
}
线程按序交替
1.A B C交替打印 2.假设A B C线程将自己的ID打印出来个10次,然后按照一定的顺序显示 3.A--打印5次 B打印15次 C--打印20次 并且按序交替
实现代码:
//A B C交替打印
//假设A B C线程将自己的ID打印出来个10次,然后按照一定的顺序显示
//A--打印5次 B打印15次 C--打印20次 并且按序交替
public class TestABCAlternate {
public static void main(String[] args) {
AlternateDemo alternateDemo=new AlternateDemo();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
alternateDemo.loopA(i);
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <=20; i++) {
alternateDemo.loopB(i);
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
alternateDemo.loopC(i);
System.out.println("------------------");
}
}
},"C").start();
}
}
class AlternateDemo{
private int number=1;//表示当前正在执行线程的标记
private Lock lock=new ReentrantLock();//要实现按序交替 需要有锁
//还需要线程之间进行通信
private Condition conditionA=lock.newCondition();
private Condition conditionB=lock.newCondition();
private Condition conditionC=lock.newCondition();
public void loopA(int totallLoop) {//打印的第几轮
lock.lock();
try {
//1.判断线程是否应该是1,1为A打印 2--B,3--C
if(number!=1) {
//如果不该A打印 则自己就进行等待的操作
try {
conditionA.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2. number==1 打印
for (int i = 1; i <=5; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totallLoop);
}
//3.唤醒
number=2;
conditionB.signal();//唤醒2打印
} finally {
lock.unlock();
}
}
public void loopB(int totallLoop) {//打印的第几轮
lock.lock();
try {
//1.判断线程是否应该是1,1为A打印 2--B,3--C
if(number!=2) {
//如果不该A打印 则自己就进行等待的操作
try {
conditionB.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2. number==1 打印
for (int i = 1; i <=15; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totallLoop);
}
//3.唤醒
number=3;
conditionC.signal();//唤醒2打印
} finally {
lock.unlock();
}
}
public void loopC(int totallLoop) {//打印的第几轮
lock.lock();
try {
//1.判断线程是否应该是1,1为A打印 2--B,3--C
if(number!=3) {
//如果不该A打印 则自己就进行等待的操作
try {
conditionC.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//2. number==1 打印
for (int i = 1; i <=20; i++) {
System.out.println(Thread.currentThread().getName()+"\t"+i+"\t"+totallLoop);
}
//3.唤醒
number=1;
conditionA.signal();//唤醒2打印
} finally {
lock.unlock();
}
}
}