多线程
一、进程和线程
进程是操作系统资源分配的基本单位。
线程是CPU的基本调度单位
- CPU时间片:操作系统会为每个线程分配执行时间
- 运行数据:
- 堆空间:存储线程需使用的对象,多个线程可以共享堆中的对象
- 栈空间:存储线程需使用的局部变量,每个线程都拥有独立的栈
- 线程逻辑代码
线程特点
- 抢占式执行,效率高,可防止单一线程长时间独占CPU
- 在单核CPU中,宏观上同时执行,微观上顺序执行
进程和线程区别
- 一个线程运行后至少有一个进程
- 一个进程可以包含多个线程,但是至少需要有一个线程
- 进程间不能共享数据段地址,但进程之间可以
二、线程创建
1.继承Thread类
步骤:
- 继承Thread类
- 重写run()方法
- 创建子类对象
- 调用start()方法
-
子类中
- this.getId()
- this.getName()
-
其他
- Thread.currentThread().getId()
- Thread.currentThread().getName()
获取线程id和线程name
-
更改线程名字
- 调用线程对象:setName();
- 使用线程子类的构造方法赋值
public class MyThread extends Thread{
public MyThread(){
}
public MyThread(String threadName){
super(threadName);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//System.out.println(this.getId()+"-->"+this.getName()+"run"+i);
System.out.println(Thread.currentThread().getId()+"-->"+Thread.currentThread().getName()+" run"+i);
}
}
}
//测试类
public class TestThread {
public static void main(String[] args) {
MyThread myThread = new MyThread();
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread("t2");
myThread.setName("t0");
myThread.start();
myThread1.setName("t1");
myThread1.start();
myThread2.start();
for (int i = 0; i < 100; i++) {
System.out.println("main:"+i);
}
}
}
2.实现Runnable接口
- 实现Runnable接口
- 重写run()方法
- 创建实现类对象
- 创建线程对象
- 调用start对象
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
}
}
public class TestRunnable {
public static void main(String[] args) {
//创建可运行线程对象
MyRunnable myRunnable = new MyRunnable();
//创建线程对象
Thread thread = new Thread(myRunnable, "线程");
//启动线程
thread.start();
}
}
常用方法
- public static void sleep(long millis)
- 休眠
- 当前线程主动休眠millis毫秒。释放了CPU,不再争抢CPU
- public static void yield()
- 放弃
- 当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。
- 注:只让给优先级比当前线程高或一样的
- public final void join()
- 加入
- 允许其他线程加入到当前线程中。当前线程会阻塞,直到加入线程执行完毕
- .setPriority(int n)
- 线程优先级为1-10,默认为5,优先级越高,表示获取CPU机会越多
- .interrupt()
- 打断线程,被打断线程抛出InterruptedException
- .setDaemon(true)
- 设置为守护线程
一般使用Runnable接口:1.有资源共享。2.线程操作相同,共享资源类实现Runnable接口。3.线程操作不同,使用操作类分开实现Runnable接口
三、线程安全
1.线程不安全
- 当线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
- 临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性
- 原子操作:不可分割的多步操作,被视为一个整体,其顺序和步骤不可打乱或缺省
2.死锁
- 当第一个线程拥有A对象锁标记,并等待B对象锁标记,同时第二个线程拥有B对象锁标记,并等待A对象锁标记时,产生死锁。
- 一个线程可以同时拥有多个对象的锁标记,当线程阻塞时,不会释放已经拥有的锁标记,由此可能造成死锁。
四、线程同步
补充:并发和并行
- 并发:指应用交替执行不同的任务
- 并行:指应用同时执行不同的任务
- 区别:一个是交替执行,一个是同时执行
1.synchronized作用域
/*
同步代码块
*/
synchronized(锁){//对临界资源加锁 monitor
//代码(原子操作)
}
/*
同步方法
*/
synchronized 返回值类型 方法名(形参列表){//对当前对象(this)加锁
//代码(原子操作)
}
- 非静态方法(加的锁为对象锁)
public class Demo{
public synchronized void printA(){//this
System.out.println("A");
}
//等同-->代码块
public void printB(){
synchronized(this){
System.out.println("B");
}
}
}
- 静态方法(加的锁为类锁)
public synchronized static void printB(){//Demo.class
System.out.println("B");
}
//等同-->代码块
public static void printC(){
synchronized (Demo.class) {
System.out.println("C");
}
}
只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中
线程退出同步方法时,会释放相应的互斥锁标记
如果时静态方法,锁时【类名.class】
- 代码块(对象锁与类锁均可)
public void printA(String name,int i){
synchronized (this){
System.out.println(name+"--->"+i);
}
}
public static void printC(){
synchronized (Demo.class) {
System.out.println("C");
}
}
每个对象都有一个互斥锁标记,用来分配给线程的
只有拥有对象互斥锁标记的线程,才能进入对该对象加锁的同步代码块
线程退出同步代码块时,会释放相应的互斥锁标记
//四个窗口共卖100张票
public class TicketDemo {
public static void main(String[] args) {
Runnable runnable=new Runnable() {
private int ticket=100;
//private Object lock=new Object();
@Override
public void run() {
while(true){
synchronized (this){
if(ticket<=0){
break;
}
System.out.println(Thread.currentThread().getName()+"卖了第"+ticket+"张票");
ticket--;
}
}
}
};
new Thread(runnable,"窗口1").start();
new Thread(runnable,"窗口2").start();
new Thread(runnable,"窗口3").start();
new Thread(runnable,"窗口4").start();
}
}
2.Lock
常用方法
- void lock()
- 获取锁,如锁被占用,则等待
- boolean tryLock()
- 尝试获取锁。成功返回true,失败返回false,不阻塞
- void unlock()
- 释放锁。在finally中释放锁,不然容易造成线程死锁
实现类
- ReentrantLock --重入锁
- 与synchronized一样实现重入锁
//lock锁实现卖票
public class Demo02 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
private int ticket = 1000;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
try {
if (ticket < 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "卖了第" + ticket + "号票");
ticket--;
} finally {
lock.unlock();
}
}
}
};
new Thread(runnable, "窗口一").start();
new Thread(runnable, "窗口二").start();
new Thread(runnable, "窗口三").start();
new Thread(runnable, "窗口四").start();
}
}
- ReenTrantReadWriteLock --读写锁
- 一种支持一写多读的同步锁,读写分离,可分别分配读锁,写锁。
- 支持多次分配读锁,使多个读操作可以并发执行
public class ReadWriterDemo {
//创建读写锁
private ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//变量
private int value;
public int getValue() throws InterruptedException {
//上锁
readWriteLock.readLock().lock();
try {
Thread.sleep(1000);
return value;
} finally {
readWriteLock.readLock().unlock();
}
}
public void setValue(int value) throws InterruptedException {
//上锁
readWriteLock.writeLock().lock();
try {
Thread.sleep(1000);
this.value=value;
} finally {
readWriteLock.writeLock().unlock();
}
}
}
//测试代码
public class TestReadWriteLock {
public static void main(String[] args) {
ReadWriterDemo readWriterDemo=new ReadWriterDemo();
ExecutorService es = Executors.newFixedThreadPool(20);
Runnable read=new Runnable() {
@Override
public void run() {
try {
int r= readWriterDemo.getValue();
System.out.println(r);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Runnable write=new Runnable() {
@Override
public void run() {
try {
int i = new Random().nextInt(100);
readWriterDemo.setValue(i);
System.out.println(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//提交任务
long start=System.currentTimeMillis();
for(int i=0;i<2;i++){
es.submit(write);
}
for(int i=0;i<18;i++){
es.submit(read);
}
es.shutdown();
//等待20个线程执行完毕,才继续执行
while(!es.isTerminated()){}
long end=System.currentTimeMillis();
System.out.println("用时:"+(end-start));
}
}
synchronized和Lock区别
类别 | synchronized | Lock |
---|---|---|
存在层次 | Java关键字,在JVM层面上 | 是一个类或接口 |
释放锁 | 1.获取锁的线程执行同步到代码,释放锁 2.线程执行发生异常,JVM会让线程释放锁 | 在finally中必须释放锁,不然容易造成线程死锁 |
锁的获取 | 假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一致等待 | Lock可以尝试获得锁,线程可以不用一直等待 |
锁状态 | 无法判断 | 可以判断 |
锁类型 | 可重入,不可中断,非公平 | 可重入,可中断,可公平或非公平 |
深入解析
正在准备。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
五、线程通信
1.常用方法
Object
- 等待
- public final void wait()
- public final void wait(long timeout)
- 必须在对obj加锁的同步代码块中。在一个线程中,调用obj.wait()时,此线程会释放其拥有的所有锁标记。同时此线程阻塞在锁的等待队列中。释放锁,进入等待队列
- 通知
- public final void notity()
- 从等待队列中随机唤醒一个
- public final void notifyAll()
- 唤醒所有等待线程
- 必须在对obj加锁的同步代码块中。从obj的waiti中释放一个或全部线程。对自身没有任何影响
- public final void notity()
利用synchronized、wait()、notifyAll()
//面包
public class Bread {
private String name;
private int no;
public Bread(String name, int no) {
this.name = name;
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
}
//面包容器
public class BreadArray {
private Bread []breads = new Bread[6];
private int size;
public synchronized void product(Bread bread){
while(size>=6){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
breads[size++] = bread;
System.out.println(bread.getName()+"生产了"+bread.getNo()+"号面包");
this.notifyAll();
}
public synchronized void consum(){
while(size<=0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Bread bread = breads[--size];
breads[size] = null;
System.out.println(Thread.currentThread().getName()+"消费了"+bread.getNo()+"号面包,生产者是:"+bread.getName());
this.notifyAll();
}
}
//生产者
public class Product implements Runnable{
private BreadArray breadArray;
public Product(BreadArray breadArray) {
this.breadArray = breadArray;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
breadArray.product(new Bread(Thread.currentThread().getName(), i));
}
}
}
//消费者
public class Consum implements Runnable{
private BreadArray breadArray;
public Consum(BreadArray breadArray) {
this.breadArray = breadArray;
}
@Override
public void run() {
for (int i = 0; i < 30; i++) {
breadArray.consum();
}
}
}
//测试类
public class Test {
public static void main(String[] args) {
BreadArray breadArray = new BreadArray();
Product product = new Product(breadArray);
Consum consum = new Consum(breadArray);
new Thread(product,"生产者1").start();
new Thread(consum,"消费者1").start();
new Thread(product,"生产者2").start();
new Thread(consum,"消费者2").start();
}
}
面试题:sleep()和wait有什么区别?
(1)sleep()是休眠 使用Thread.sleep()调用,wait()是等待,使用锁.wait()调用
(2)sleep()休眠时会释放cpu,不会释放资源(锁),自动唤醒
(3)wait()等待时会释放cpu和资源(锁),一般需要其他线程唤醒。
Condition
-
Condition接口也提供类似Object的监视器方法,与Lock配合可以实现等待/通知模式
-
Condition可以通俗的理解为条件队列。当一个线程在调用了await方法以后直到线程等待的某个条件为真的时候才会被唤醒
- await()
- 当前线程进入等待状态
- signal()
- 唤醒一个等待线程
- signalAll()
- 唤醒所有等待线程
利用Lock和Condition实现生产者消费者
//面包类
public class Bread {
//名字
private String name;
//编号
private int no;
public Bread(String name, int no) {
this.name = name;
this.no = no;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNo() {
return no;
}
public void setNo(int no) {
this.no = no;
}
}
//容器
public class BreadCon {
//最多能存储6个
private Bread[] breads = new Bread[10];
//有效个数
private int size;
//定义锁
Lock lock = new ReentrantLock();
//创建生产者队列
Condition proCondition = lock.newCondition();
//创建消费者队列
Condition conCondition = lock.newCondition();
//生产
public void product(Bread bread){
lock.lock();
try {
while (size >= 10) {
try {
proCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
breads[size++] = bread;
System.out.println(bread.getName()+"生产了"+bread.getNo()+"号面包");
conCondition.signal();
}finally {
lock.unlock();
}
}
//消费
public void consum(){
lock.lock();
try{
while(size<=0){
try {
conCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Bread bread = breads[--size];
breads[size] = null;
System.out.println(Thread.currentThread().getName()+"消费了"+bread.getNo()+"号面包生产者是:"+bread.getName());
proCondition.signal();
}finally {
lock.unlock();
}
}
}
//生产者
public class Product implements Runnable{
private BreadCon breadCon;
public Product(BreadCon breadCon) {
this.breadCon = breadCon;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
breadCon.product(new Bread(Thread.currentThread().getName(),i));
}
}
}
//消费者
public class Consume implements Runnable{
private BreadCon breadCon;
public Consume(BreadCon breadCon) {
this.breadCon = breadCon;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
breadCon.consum();
}
}
}
//测试类
public class Test {
public static void main(String[] args) {
BreadCon breadCon = new BreadCon();
Product product = new Product(breadCon);
Consume consume = new Consume(breadCon);
new Thread(product, "生产1").start();
new Thread(product, "生产2").start();
new Thread(consume, "消费者1").start();
new Thread(consume, "消费者2").start();
}
}
面试题:使用Lock和Condition实现三个线程交替输出20遍“ABC"
public class Alternate {
//创建锁
Lock lock = new ReentrantLock();
private int i = 1;//1-->代表A 2-->代表B 3-->代表C
//创建队列
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
Condition conditionC = lock.newCondition();
public void printA() {
lock.lock();
try {
while (i != 1) {
try {
conditionA.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("A");
i=2;
conditionB.signal();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while(i!=2){
try {
conditionB.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("B");
i = 3;
conditionC.signal();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
while(i!=3){
try {
conditionC.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("C");
i = 1;
conditionA.signal();
} finally {
lock.unlock();
}
}
}
//测试类
public class Test01 {
public static void main(String[] args) {
Alternate a = new Alternate();
//固定线程池
ExecutorService es = Executors.newFixedThreadPool(3);
//提交任务
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
a.printA();
}
}
});
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
a.printB();
}
}
});
es.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
a.printC();
}
}
});
//关闭线程池
es.shutdown();
}
}
六、线程池
线程容量,可设定线程分配的数量上限。
将预先创建的线程对象存入池中,并重用线程池中的线程对象。
避免频繁的创建和销毁。
1.常用接口或类
- Executor
- 线程池的顶级接口
- ExecutorService 线程接口
- submit(Runnable task):提交任务代码
- shutdown():关闭线程池。会等待所有提交的任务执行完毕,才关闭
- Executors工厂类
- 通过此类可以获得以下四种线程池
- newFixedThreadPool(int nThread)
- 创建固定数量的线程池
- nThread:线程数量
- newCachedThreadPool()
- 创建动态大小线程池
- 线程池大小根据提交任务变化
- newSingleThreadPool()
- 创建单线程池
- 只有一个线程的线程池
- newScheduledThreadPool(int n)
- 调度线程池,实现周期执行或者延迟执行
- 固定执行
- .scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)
- 参数:提交任务 初始延迟 延迟 单位
- .scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnit unit)
- 延迟执行
- .scheduleAtFixedTate(Runnable command,long initialDelay,long period,TimeUnit unit)
- 参数:提交任务 初始延迟 周期 时间单位
- .scheduleAtFixedTate(Runnable command,long initialDelay,long period,TimeUnit unit)
- 固定执行
- 调度线程池,实现周期执行或者延迟执行
2.ThreadPoolExecutor
阿里巴巴Java开发手册
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
ThreadPoolExecutor的七个参数
- corePoolSize
- 核心线程数(不会销毁)
- maximumPoolSize
- 最大线程数(用完会销毁)
- KeepAliveTime
- 非核心线程的存活时间
- unit
- 时间单位
- workQueue
- 工作队列
- threadFactory
- 线程工厂(默认:Executor.defaultThreadFactory)
- handle
- 拒绝策略
- AbortPolicy:中断,抛出异常(核心业务使用,使用最多)
- 启动线程数量超过最大线程数量+队列数量时,会抛出异常
- DiscardPolicy:直接抛弃,不抛出异常(非核心业务)
- 启动线程数量超过最大线程数量+队列数量时,会直接抛弃线程
- DiscardOldestPolicy:把旧的抛弃,加入新的
- 把旧的抛弃,加入新的。首先启动核心线程,核心线程没结束,又有新的线程加入,先放在队列中,如果在有新的线程,则加入非核心线程(不超过最大线程数),超过最大线程则,抛弃队列中的线程
- CallerRunsPolicy:线程池创建者执行
- 线程池创建者执行;启动线程超过最大线程池数量+队列数量,如果没有空闲线程会由线程池创建者执行
- AbortPolicy:中断,抛出异常(核心业务使用,使用最多)
- 拒绝策略
七、安全集合
1. Queue接口
1.1 ConcurrentLinkedQueue(实现类)
- 线程安全,可高效读写的队列,高并发下性能最好的队列
- 无锁、CAS比较交换算法,修改的方法包含三个核心参数(V,E,N)
1.2 BlockingQueue(子接口)
- 阻塞队列,增加了两个线程状态为无限期等待的方法
- 方法
- void put(E e)
- 将指定元素插入此队列中,如果没有可用空间,则等待
- E take()
- 获取并移除此队列头部元素,如果没有可用元素,则等待
- void put(E e)
//生产者,消费者问题
1.2.1 ArrayBlockingQueue(实现类)
- 阻塞队列
- 数组结构实现,有界队列。(手动固定上限)
1.2.1 LinkedBlockingQueue(实现类)
- 阻塞队列
- 链表结构实现,有界队列。(默认上线Integer.MAX_VALUE)
2.CopyOnWriteArrayList
- 线程安全的ArrayList,加强版读写分离
- 写有锁,读无锁,读写之间不阻塞,优于读写锁
- 写入时,先copy一个容器副本,再添加新元素,最后替换引用
3.CopyOnWriteArraySet
- 线程安全的Set,底层使用CopyOnWriteArrayList实现
- 唯一不同在于,使用addIfAbsent()添加元素,会遍历数组
- 如存在元素,则不添加(扔掉副本)
4.ConCurrentHashMap
- 初始容量默认为16段(Segment),使用分段锁设计
- 不对整个Map加锁,而是为每个Segment加锁
- 当多个对象存入同一个Segment时,才需要互斥
- 最理想状态为16个对象分别存入16个Segment,并行数量为16
- JDK1.8改为CAS无锁算法
八、Callable接口
语法
public interface Callable<V>{
public V call() throws Exception;
}
- 与Runnable接口类似,实现之后代表一个任务
- Callable具有泛型返回值,可以声明异常
对比Runnable
Runnable没有异常抛出,没有返回值
Callable具有泛型返回值,可以声明异常
九、Future接口
概念
异步接受ExecutorService.submit()所返回的状态结果,当中包含了call()的返回值。可与Callable配合使用
方法
V get():以阻塞形式等待Future中的异步畜栏里结果(call()的返回值)
//需求:使用两个线程,并发计算1-50,51-100的和,再进行汇总统计
十、CAS算法
- CAS:Compare And Swap(比较交换算法)
- 可以说是并发包的底层实现原理,Java中的乐观锁基本都是通过CAS操作实现的,CAS是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败
- 其实现方式是基于硬件平台的汇编指令(compxchg),是靠硬件来实现的,效率高
- 并且比较和交换过程是同步的
- compareAndSwap(V,E,N)方法包含三个核心参数
- V:要更新的变量
- E:预期值
- N:新值
- 只有当V==E时,V=N;否则表示已被更新过,则取消当前操作
十一、乐观锁和悲观锁
乐观锁
总是认为线程不安全,不管什么情况都进行加锁,要是获取锁失败,就阻塞。
CAS是乐观锁
悲观锁
总是认为是线程安全,不怕别的线程修改变量,如果修改了再重新尝试,直到成功。
synchronized就是悲观锁
十二、Collections中的工具方法
- 可查api
十三、并发工具类
1. CountDownLatch(闭锁)
-
方法
- await():倒计数到零之前等待
- countDown():递减锁存的计数,如果到达零则释放所有等待线程
- getCount():返回当前计数
-
同步计数器,初始化的时候传入需要计数的线程等待数
-
作用
- 闭锁可以用来确保某些操作直到其他活动都完成才能继续执行
//模拟所有员工都到达后,老板开始开会
public class TestCountDownLatchDemo01 {
public static void main(String[] args) {
//创建5个线程,模拟5个员工
CountDownLatch cd = new CountDownLatch(5);
for (int i = 0; i < 5; i++) {
new Employe(cd, (new Random().nextInt(10)+1)*1000).start();
}
new Boss(cd).start();
}
static class Boss extends Thread{
//定义计数器
private CountDownLatch bc ;
public Boss(CountDownLatch count){
this.bc = count;
}
@Override
public void run() {
System.out.println("老板要开始会了");
try {
//计数器倒计数到零之前等待
bc.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有员工都到齐了");
System.out.println("可以开会了");
}
}
static class Employe extends Thread{
private CountDownLatch cd;
private long time;
public Employe(CountDownLatch cd,long time){
this.cd = cd;
this.time = time;
}
@Override
public void run() {
try {
sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"员工到了");
cd.countDown();
}
}
}
2. CyclicBarrier(屏障)
-
构造函数:CyclicBarrier(int partied) 屏障拦截的线程数量
-
作用
- 让一组线程在到达一个屏障时被阻塞,等到最后一个线程到达屏障点,才会运行被拦截的线程继续运行
-
方法
- await():调用该方法时,表示线程已经到达屏障,随即阻塞
//实现多个线程同时执行
public class Demo03 {
public static void main(String[] args) {
//创建屏障,屏障结束后执行的任务
CyclicBarrier cb = new CyclicBarrier(5,new Runnable(){
@Override
public void run() {
System.out.println("都准备好了");
}
});
for (int i = 0; i < 5; i++) {
new MyThread(cb,(i+1)*3000).start();
}
}
static class MyThread extends Thread{
private CyclicBarrier cb;//创建屏障
private long time;
public MyThread(CyclicBarrier cb,long time){
this.cb = cb;
this.time = time;
}
@Override
public void run() {
try {
Thread.sleep(time);//延时
System.out.println(Thread.currentThread().getName()+"到了。。正在等待。。");
cb.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
}
3. Semaphore(信号量)
- Semaphore是Synchronized或Lock的加强版
- 作用
- 用来控制同时访问特定资源的线程数量,通过协调保证合理的使用公共资源
- 方法
- acquire()
- 从信号量获取一个许可
- release()
- 释放一个许可,将其返回给信号量
- acquire()
//案例:使用Semaphore信号量控制并发的个数
4. Exchange(交换器)
- 类似于一个交换器,Exchanger类允许在两个线程之间定义同步点。当两个线程都到达同步点时,他们交换数据,因此第一个线程的数据进入到第二个线程中,第二个线程的数据进入到第一个线程中。
//案例:实现两个线程交换数据
十四、 多线程的三个特征
补充:多线程要保证并发线程正确执行,必须要保证三个特性
1. 原子性(互斥性)
一个或多个操作不能被分割,要么全部执行,要么就都不执行
2. 可见性
多线程访问同一个变量,一个线程修改了这个变量,别的线程能立即看到修改的值
3. 有序性
程序执行的顺序按照代码的先后顺序执行。但是处理器为了提高程序运行效率,可能会对输入代码进行优化,它不保证程序中各个语句的执行顺序和编写顺序一致。但最终结果时一致的。
- synchronized可保证原子性和可见性,但不能保证有序性
- volatile可保证可见性和禁止指令重排,但不能保证原子性。(不稳定,高变化。不缓存每次都在堆中取数)Lock接口间接借助了volatile关键字间接地实现了可见性和有序性
问题:i++是原子操作吗?
不是。
//可使用以下方式
AtomicInteger a = new AtomicInteger();
a.getAndIncrement();//使用CAS算法(i++)
public class PlusDemo {
private int num=0;
AtomicInteger atomicInteger=new AtomicInteger();
//并发特别高的情况下。用来计数,统计getNum方法执行的次数
LongAdder longAdder=new LongAdder();
// i++ 包含三步,(1)读取i (2)改 执行加1 (3)结果写入i
public int getNum() {
// synchronized (this){//效率低
// return num++;
// }
longAdder.decrement();
longAdder.sum();//统计数据
return atomicInteger.getAndIncrement();//使用CAS算法
}
}
//测试类
public class TestAtomic {
public static void main(String[] args) {
PlusDemo plusDemo=new PlusDemo();
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int r= plusDemo.getNum();
System.out.println(r);
}
}).start();
}
}
}