JUC入门(三)

创建线程的多种方式

  1)继承Thread

  2)实现Runnable

  3) 实现Callable

  4)线程池方式

Runnable和Callable接口的不同

  1)Callable有返回值,Runnable没有

  2)Callable能抛出异常,Runnable不能

  3)Callable是实现call方法,Runnable是实现run方法

例子如下:

package com.ma;

import java.util.concurrent.Callable;

class Mythread1 implements Runnable{

    @Override
    public void run() {

    }
}

class Mythread2 implements Callable {

    @Override
    public Integer call() throws Exception {
        return 200;
    }
}
public class CallRun {
    public static void main(String[] args) {
        new Thread(new Mythread1(),"AA").start();
        new Thread(new Mythread2(),"BB").start();
    }
}
View Code

但是开启一个new Thread无法直接将继承Callable的拿来用

因为Thread里面没有Callable接口

 

此时,因为我们要Thread来调用Callable,可是因为没有Callable接口

那么,我们就应该找一个中间件(既有Runable,又有Runable)-------FutrueTask

Runable接口有实现类FutureTask,FutureTask构造可以传递Callable

所以,我们可以通过以下方式来实现调用

//普通写法
        FutureTask<Integer> futureTask = new FutureTask<>(new Mythread2());
        //lambda表达式
        FutureTask<Integer> futureTask1 = new FutureTask<>(()->{
            System.out.println("=======================");
            return 1024;
        });
        new Thread(futureTask1,"lucy").start();

 

 

CountDownLatch

  有一个计数器,通过countDown方法来进行减1的操作,使用await方法等待计数器不大于0,然后继续执行await方法之后的语句

  CountDown方法会将计数器减1

  当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行

  例子:6个同学陆续离开教师后,才可以关门

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for(int i=1;i<=6;i++){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"走人了");
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"关门");
    }
}
View Code

 

CyclicBarrier(循环栅栏)

  CyclicBarrier的构造方法第一个参数是目标障碍数,每次执行CyclicBarrier.await()一次,障碍数就会加一

  到了目标障碍数,执行了await()方法的线程会同一时刻执行各自的方法

public class CyclicBarrierTest {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("*******集齐7颗龙珠就可以召唤神龙");
        });

        for(int i=1;i<=7;i++){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"星龙珠被收集到了");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}
View Code

 

Semaphore

  Semaphore 通常我们叫它信号量, 可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。

public class SemaphoreTest {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for(int i=1;i<=6;i++){
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName()+"抢占到了车位");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"离开了。。。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }
            },String.valueOf(i)).start();
        }
    }
}
View Code

 

悲观锁

 

悲观锁一旦锁住某个对象,只有当其操作完之后,别的线程才可以获得锁进行操作

 

乐观锁

 

 可以看到,-8000的操作完毕之后,将版本号修改为了1.1

 而下面-5000的操作要提交的时候,发现版本号已经不一致,所以无法成功提交

 

表锁   将表锁住,别的就不能操作这张表

行锁   只锁表中的某一行

 

读锁  共享锁  会发生死锁

写锁  独占锁  会发生死锁

例子:1线程在读取,2线程也在读取,1线程想要修改(得等2线程读取完才可以修改),2线程想要修改(得等1线程读取完才能修改),所以就造成了死锁

 

 读写锁:一个资源可以被多个读线程访问,或者可以被一个写线程访问,但是不能同时存在读写线程,写写线程

举个读写锁的例子(创建一个MyCache用来类比一本书籍,然后让5个读线程和5个写线程对其进行操作)

package com.ma;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

class MyCache{
    private volatile Map<String,Object> map = new HashMap<>();

    public void put(String key,Object value) throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"正在写操作");
        map.put(key,value);
        TimeUnit.SECONDS.sleep(3);

        System.out.println(Thread.currentThread().getName()+"写完了");
    }
    public Object get(String key) throws InterruptedException {
        System.out.println(Thread.currentThread().getName()+"正在读操作");
        Object o = map.get(key);
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName()+"取完了");
        return o;
    }
}
public class rwLockTest {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for(int i=1;i<=5;i++){
            final int num=i;
            new Thread(()->{
                try {
                    myCache.put(num+"",num+"");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }

        for(int i=1;i<=5;i++){
            final int num=i;
            new Thread(()->{
                try {
                    myCache.get(num+"");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}
View Code

运行结果如下:

 

 我们在写的时候,应该是独占的,但是上面并没有,所以,我们给其加上一个读写锁

package com.ma;

import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyCache{
    private volatile Map<String,Object> map = new HashMap<>();
    private ReadWriteLock rwlock = new ReentrantReadWriteLock();

    public void put(String key,Object value) throws InterruptedException {
        rwlock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"正在写操作");
            map.put(key,value);
            TimeUnit.SECONDS.sleep(3);
            System.out.println(Thread.currentThread().getName()+"写完了");
        }finally {
            rwlock.writeLock().unlock();
        }
    }
    public Object get(String key) throws InterruptedException {
        rwlock.readLock().lock();
        try{
            System.out.println(Thread.currentThread().getName()+"正在读操作");
            Object o = map.get(key);
            TimeUnit.SECONDS.sleep(3);
            System.out.println(Thread.currentThread().getName()+"取完了");
            return o;
        }finally {
            rwlock.readLock().unlock();
        }
    }
}
public class rwLockTest {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        for(int i=1;i<=5;i++){
            final int num=i;
            new Thread(()->{
                try {
                    myCache.put(num+"",num+"");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }

        for(int i=1;i<=5;i++){
            final int num=i;
            new Thread(()->{
                try {
                    myCache.get(num+"");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}
View Code

运行结果如下:

 

读写锁的发展历程

1)无锁的情况,                                                          缺点:多线程抢夺资源 ,状况十分混乱

2)添加了锁(使用synchronized和ReentrantLock)  缺点:但是读写都是独占的,不能共享

3)读写锁(ReentrantReadWriteLock)   读读可以共享,读写、写写不共享   

  线程进入读锁的前提条件:

    没有其他线程的写锁,

    没有写请求或者有写请求,但调用线程和持有锁的线程是同一个。

  线程进入写锁的前提条件:

    没有其他线程的读锁

    没有其他线程的写锁

  缺点:1)造成锁饥饿,一直读,没有写操作

     2)读时候,不能写,只有读完成之后,才可以写, 但是写操作可以读

 

 

锁降级  写线程获取写入锁后可以获取读取锁, 然后释放写入锁, 这样就从写入锁变成了读取锁, 从而实现锁降级的特征

例子:

package com.ma;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockDownLevelTest {
    public static void main(String[] args) {
        ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();

        rwlock.writeLock().lock();
        System.out.println("写锁");
        rwlock.readLock().lock();
        System.out.println("读锁");
        rwlock.writeLock().unlock();
        rwlock.readLock().unlock();

        /*rwlock.readLock().lock();
        System.out.println("读锁");
        rwlock.writeLock().lock();
        System.out.println("写锁");*/
    }
}
View Code

这样子是可以正常得出结果的

但是,锁不能升级,即:如果先获取了读锁,是不能再获取写锁的

package com.ma;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockDownLevelTest {
    public static void main(String[] args) {
        ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();

        rwlock.readLock().lock();
        System.out.println("读锁");
        rwlock.writeLock().lock();
        System.out.println("写锁");
    }
}
View Code

这样子写程序无法正常结束

 

阻塞队列

 

 

线程池

  1)线程池的优势:

    线程池做的工作是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行

  2)主要特点

    降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗

    提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行

    提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以统一分配,调优和监控。

线程的架构

  

 

 线程池的使用方式

  1)newFixedThreadPool(int)    定长线程池

  2)newSingleThreadExecutor()    单线程线程池

  3)newCachedThreadPool()         可扩线程池

例子如下:

package com.ma.pool;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolTest {
    public static void main(String[] args) {
        //一池5线程
        ExecutorService threadPool1 = Executors.newFixedThreadPool(5);
        //一池一线程
        ExecutorService threadPool2 = Executors.newSingleThreadExecutor();
        //一池可扩线程
        ExecutorService threadPool3 = Executors.newCachedThreadPool();

        try{
            for(int i=1;i<=10;i++){
                threadPool1.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"办理业务");
                });
            }
        }finally {
            threadPool1.shutdown();
        }

        try{
            for(int i=1;i<=10;i++){
                threadPool2.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"办理业务");
                });
            }
        }finally {
            threadPool2.shutdown();
        }

        try{
            for(int i=1;i<=10;i++){
                threadPool3.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"办理业务");
                });
            }
        }finally {
            threadPool3.shutdown();
        }
    }
}
View Code

但是,在实际开发中,并不会用以上的方式来创建线程池,而是会自定义,原因之一如下:

 

 

线程池的七大参数

线程池的4种拒绝策略

例子:

 

posted @ 2021-09-11 11:03  古比  阅读(52)  评论(0编辑  收藏  举报