首页 > 其他分享 >JUC(五)Callable

JUC(五)Callable

时间:2023-03-09 19:57:13浏览次数:39  
标签:JUC Thread System Callable println new public out

Callable接口

创建线程的几种方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 通过Callable接口
  4. 线程池

使用Runnable接口无法获取到线程返回的结果,因此在jdk1.5后java提供了Callable接口。

Callable接口的特点

  • 需要实现带返回结果的call方法
  • 无法计算返回结果则会抛出异常

FutureTask

实现Thread没有Callable构造的问题

由于Thread没有Callable构造的问题,所以callable不能像runnable一样直接创建一个线程,这时候需要通过一个中间类--FutureTask来实现,可以看到FutureTask既实现了Runnable也包含callable:

public class FutureTask<V> implements RunnableFuture<V> {
    private Callable<V> callable;
	...
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
    ...
}
class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        return 1024;
    }
}
public class CallableTest {

    public static void main(String[] args) {
        //var integerFutureTask = new FutureTask<Integer>(new MyCallable());
        var integerFutureTask = new FutureTask<>(() -> {
            return 1024;
        });
        new Thread(integerFutureTask).start();
    }
}
FutureTask原理
  • 为任务单独开启一个线程
  • 线程执行FutureTask只会执行一次,第二次会直接返回一个结果
class MyCallable implements Callable {
    @Override
    public Object call() throws Exception {
        return 200;
    }
}
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //var integerFutureTask = new FutureTask<Integer>(new MyCallable());
        var integerFutureTask = new FutureTask<>(() -> {
            return 1024;
        });
        new Thread(integerFutureTask).start();
        while(!integerFutureTask.isDone()) {
            System.out.println("wait..");
        }
        System.out.println(integerFutureTask.get());
        new Thread(integerFutureTask).start();
        while(!integerFutureTask.isDone()) {
            System.out.println("wait..");
        }
        System.out.println(integerFutureTask.get());
    }
}

wait..
wait..
wait..
wait..
wait..
wait..
wait..
1024
1024

相同FutureTask的两个线程第二个线程执行直接返回了结果

JUC辅助类

CountDownLatch计数器

类似os的整型信号量,countDown方法对计数-1,await判断计数小于零则堵塞当前线程。

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        var count = new CountDownLatch(6);
        for (int i = 0; i < 6; i++) {
            int x = i;
            new Thread(() -> {
                System.out.println(x + " left.");
                count.countDown();
            }, String.valueOf(i)).start();
        }
        count.await();
        System.out.println("Over.");
    }
}

CyclicBarrier循环栅栏

  • CyclicBarrier为循环阻塞,当阻塞个数达到设置数量则会触发相应事件
  • 构造器用于设置阻塞数量和事件
  • await方法增加阻塞个数
public static void main(String[] args) {
    CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
        System.out.println("CyclicBarrier event happen.");
    });
    for (int i = 0; i < 7; i++) {
        int x = i;
        new Thread(() -> {
            System.out.println(x);
            try {
                cyclicBarrier.await();
            } catch (InterruptedException | BrokenBarrierException e) {
                e.printStackTrace();
            }
        }, String.valueOf(i)).start();
    }
}

Semaphore 信号量

  • acquire:消耗资源
  • release:释放资源
public static void main(String[] args) {
    Semaphore s = new Semaphore(3);

    for (int i = 0; i < 6; i++) {
        int x = i;
        new Thread(() -> {
            try {
                s.acquire();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(x + "comes in.");
            try {
                TimeUnit.SECONDS.sleep(new Random().nextInt(5));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(x + "leaves.");
            s.release();
        }).start();
    }
}

读写锁

乐观锁和悲观锁

image-20230307155354174

乐观锁和悲观锁都是并发控制的方式

  • 悲观锁是指在操作数据之前,先获取锁,确保自己是独占访问数据的,其他人必须等待锁的释放才能访问。它适用于数据冲突概率比较高的场景,如写操作比较频繁的场景。常见的悲观锁实现包括数据库中的行锁、表锁等。
  • 乐观锁是指不加锁,而是在每次操作数据时先读取数据的版本号,然后提交数据更新时比较版本号,如果版本号相同,则说明期间没有其他人修改过数据,可以直接更新,否则需要进行冲突处理。乐观锁适用于数据冲突概率比较低的场景,如读操作比较频繁的场景。常见的乐观锁实现包括数据库中的CAS(Compare And Swap)操作、版本号机制等。

总的来说,悲观锁是保守的并发控制方式,能够确保数据的一致性,但会导致系统的性能下降;而乐观锁是一种乐观的并发控制方式,性能较高,但需要处理冲突。在实际场景中,应根据具体情况选择合适的并发控制方式。

表锁和行锁

  • 表锁是对整张表进行加锁即一个事务在对表进行读或写操作时会锁定整张表,其他事务只能等待锁的释放才能访问该表。表锁通常应用于数据操作比较少或数据操作比较大的情况,如数据导入、备份和复制等操作,以减少锁的竞争。

  • 行锁是对表中的一行或多行进行加锁即一个事务在对表中的某行或某几行进行读或写操作时会锁定这些行,其他事务只能等待锁的释放才能访问这些行。行锁通常应用于数据操作比较频繁、并发访问比较高的情况,如在线事务处理系统,以保证数据的一致性。

  • 行锁会发生死锁而表锁不会。

读锁与写锁

  • 读锁又称为共享锁,允许多个线程进行读操作;写锁又称为独占锁,线程在进行写操作的时候不允许其他线程进行写操作
  • 读锁也能够造成死锁:比如两个线程都在进行读写操作,线程一写操作要在线程二读之后,线程二写操作又要在线程一读之后
  • 写锁造成死锁:两个线程同时对两条记录进行写操作

模拟读写:

class MyCache {
    private volatile HashMap<Integer, Integer> map = new HashMap<>();
    public void set(Integer key, Integer val) {
        System.out.println(Thread.currentThread().getName() + " is setting " + key);
        map.put(key, val);
        System.out.println(Thread.currentThread().getName() + " is setting over " + val);
    }
    public Integer get(Integer key) {
        System.out.println(Thread.currentThread().getName() + " is getting " + key);
        var result = map.get(key);
        System.out.println(Thread.currentThread().getName() + " is getting over " + key);
        return map.get(result);
    }
}

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        var cache = new MyCache();
        for (int i = 0; i < 5; i++) {
            final var num = i;
            new Thread(() -> {
                cache.set(num, num);
            }, String.valueOf(num)).start();
        }

        for (int i = 0; i < 5; i++) {
            final var num = i;
            new Thread(() -> {
                cache.get(num);
            }, String.valueOf(num)).start();
        }
    }
}

结果出现写操作没有结束但是读的情况

0 is setting 0
3 is getting over 3
4 is setting 4
ReentrantReadWriteLock
  • ReentrantReadWriteLock.writelock()设置写锁
  • ReentrantReadWriteLock.readlock()设置读锁
class MyCache {
    private final HashMap<Integer, Integer> map = new HashMap<>();
    private final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
    public void set(Integer key, Integer val) {
        try {
            rwlock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + " is setting " + key);
            map.put(key, val);
            System.out.println(Thread.currentThread().getName() + " is setting over " + val);
        } finally {
            rwlock.writeLock().unlock();
        }
    }
    public Integer get(Integer key) {
        try {
            rwlock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + " is getting " + key);
            var result = map.get(key);
            System.out.println(Thread.currentThread().getName() + " is getting over " + key);
            return map.get(result);
        } finally {
            rwlock.readLock().unlock();
        }
    }
}

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        var cache = new MyCache();
        for (int i = 0; i < 5; i++) {
            final var num = i;
            new Thread(() -> {
                cache.set(num, num);
            }, String.valueOf(num)).start();
        }

        for (int i = 0; i < 5; i++) {
            final var num = i;
            new Thread(() -> {
                cache.get(num);
            }, String.valueOf(num)).start();
        }
    }
}
写锁的降级

上面的读写锁中存在这一个缺陷:

  • 由于读锁是共享锁,所以不断有读操作执行的话,写线程就会饥饿

  • 写操作的时候,是能够执行读操作的

    public static void main(String[] args) {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
        ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();

        // 锁降级
        writeLock.lock();
        System.out.println("get writeLock.");

        readLock.lock();
        System.out.println("get readLock.");

        writeLock.unlock();
        readLock.unlock();
    }

对写锁降级为读锁:在写操作的时候就上读锁,防止其他写操作上写锁,这样就保证了写操作之后能立刻被读

读锁不能升级为写锁
public static void main(String[] args) {
    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
    ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
    
    readLock.lock();
    System.out.println("get readLock.");

    writeLock.lock();
    System.out.println("get writeLock.");

    writeLock.unlock();
    readLock.unlock();
}

会因无法获取到写锁而使主线程一直处于堵塞队列

标签:JUC,Thread,System,Callable,println,new,public,out
From: https://www.cnblogs.com/tod4/p/17201191.html

相关文章

  • JUC(八)ThreadLocal
    ThreadLocal简介ThreadLocal提供局部线程变量,这个变量与普通的变量不同,每个线程在访问ThreadLocal实例的时候,(通过get或者set方法)都有自己的、独立初始化变量副本。Threa......
  • JUC包续
    StampedLock      Semaohore         原理            CountdownLatch 输出不换行,并且覆盖    C......
  • JUC包
    AQS原理   ......
  • 【Python】 basemap 报错‘module‘ object is not callable
    报错:Traceback(mostrecentcalllast):File"/Users/ddd/Desktop/map_01.py",line5,in<module>m=Basemap()#使用Basemap()创建一个地图File"/Users/ddd......
  • 创建线程方法三:使用Callable和Future创建线程
    一:从Java5开始,Java提供了Callable接口,该接口是Runnable接口的增强版,Callable接口提供了一个call()方法,可以看作是线程的执行体,但call()方法比run()方法更强大。call()方......
  • JUC复习随手笔记
    1.await——》wait,signal——》notify,signalAll——》notifyAllawait会先释放锁,然后执行parkpark本身不释放锁2.ConcurrentHashMap1.71.81.7底层实现是分段......
  • 创建多线程方式3:实现Callable接口 改造下载图片案例
    packagecom.Test;importorg.apache.commons.io.FileUtils;importjava.io.File;importjava.io.IOException;importjava.net.URL;importjava.util.concurrent.*;//开......
  • java多线程:详解JUC
    对应狂神说JUC视频1.JUC是什么java.util下的几个包的简称涉及到多线程的开发java.util.concurrentjava.util.atomicjava.util.concurrent.locks2.线程和进程进程:多个程序......
  • JUC学习-线程池部分
    自定义线程池packagecom.appletree24;importjava.util.ArrayDeque;importjava.util.Deque;importjava.util.HashSet;importjava.util.concurrent.Exe......
  • Python报错TypeError: 'NoneType' object is not callable
    Python报错TypeError:'NoneType'objectisnotcallable 保存内容如下  检查src文件后没有发现问题,最终在公共方法找到原因注释掉return了,取消后问题解决 ......