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(); } }
但是开启一个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()+"关门"); } }
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(); } } }
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(); } } }
悲观锁
悲观锁一旦锁住某个对象,只有当其操作完之后,别的线程才可以获得锁进行操作
乐观锁
可以看到,-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(); } } }
运行结果如下:
我们在写的时候,应该是独占的,但是上面并没有,所以,我们给其加上一个读写锁
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(); } } }
运行结果如下:
读写锁的发展历程
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("写锁");*/ } }
这样子是可以正常得出结果的
但是,锁不能升级,即:如果先获取了读锁,是不能再获取写锁的
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("写锁"); } }
这样子写程序无法正常结束
阻塞队列
线程池
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(); } } }
但是,在实际开发中,并不会用以上的方式来创建线程池,而是会自定义,原因之一如下:
线程池的七大参数
线程池的4种拒绝策略
例子: