java学习02-多线程,线程池,线程锁,线程同步
定义多线程的3种方式
1. 继承Thread类
继承Thread类,重写run方法,使用 start启动线程
public class MyThread extends Thread {
@Override
public void run() {
//do something
}
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
使用内部匿名类
Thread myThread = new Thread() {
@Override
public void run() {
super.run();
}
};
myThread.start();
优点: 简单,直接,一个实例就是一个线程
缺点: 一个类实例只能开启一个线程,不能开启多个线程
MyThread myThread = new MyThread();
myThread.start();
//将抛出异常: java.lang.IllegalThreadStateException
myThread.start();
2. 实现Runnable接口
- 线程类实现Runnable接口,重写run()方法
- 使用new Thread(Runnable item).start()启动线程
public class MyRunnable implements Runnable {
@Override
public void run() {
//do something
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
}
}
使用内部匿名类
Runnable myRunnable = new Runnable(){
@Override
public void run() {
//do something
}
};
new Thread(myRunnable).start();
优点: 可以继承其它类,一个对象实例可以被多个线程公用,线程池只接受Runnable和Callable
缺点: 线程操作相对复杂
//一个Runnable实例可以被多个线程使用
//自然可以共用实例内部资源,例如成员变量
MyRunnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
new Thread(myRunnable).start();
//缺点是操作线程对象不是很方便
//使用Therad获取当前线程对象
this.setName("Thread设置线程名");
//使用Runnable获取当前线程对象
Thread.currentThread().setName("Runnable设置线程名")
3. 实现callable接口
正常线程是一个个相互独立的代码块,所以,它不会有返回值,异常也不会向上抛出到主线程.
而callable是允许线程返回运行结果,并可以向上抛出异常的
- 实现callable接口,重写call方法
- 使用线程池或者 FutureTask运行callable线程
//定义一个返回值类型为String的Callable
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
//do something
return "返回字符串";
}
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
//使用FutureTask承载Callbale
//Future实现接口Runnable,可以使用Thread启动
FutureTask<String> futureTask = new FutureTask<>(myCallable);
new Thread(futureTask).start();
try {
//futureTask.get()是一个阻塞方法,阻塞到线程运行完毕后执行
String result = futureTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
使用线程池来运行Callable线程
MyCallable myCallable = new MyCallable();
//线程池在本文第二部分说明
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> submit = executorService.submit(myCallable);
try {
String result = submit.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
线程池
服务器的线程是一种有限资源,过多的线程会占用系统资源,影响系统的稳定性.
使用线程池可以有效的管理线程资源,同时因为减少了线程创建的过程,可以有效提高程序的响应速度.
1. 手动生成线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 4, 30L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4),
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("线程数超过线程容量,拒绝执行任务");
}
});
生成核心线程数2个,最大线程数4个,30s闲置销毁时间,如果超出最大线程数会出现阻塞,进入排队
这里2个核心线程数不会受销毁时间影响,如果超过2个线程数,多出来的部分,如果闲置超过30S就会被销毁,在下次需要的时候再次创建
Executors定义的4种类型,阿里巴巴提供的开发手册中不建议使用Executors来创建线程池,这里用来了解线程池的几种类型,其实最终还是用 new ThreadPoolExecutor 来生成线程池,可以参考对应的源码来实现自己需要的线程池类型.
2. 可缓存线程池 newCachedThreadPool
创建一个无限数量线程的线程池,如果线程闲置超过一定时长会自动销毁线程(默认60S)
ExecutorService executorService = Executors.newCachedThreadPool();
//支持Runnable和Future
executorService.submit(new Runnable() {
@Override
public void run() {
//do something
}
});
3. 定长线程池 newFixedThreadPool
创建一个有固定长度的线程池,并且在线程闲置的时候不会自动销毁线程
如果超过规定数量的线程需要入栈,会发生阻塞等待
//创建长度为3的线程池
ExecutorService executorService = Executors.newFixedThreadPool(3);
4. 单线程线程池 newSingleThreadExecutor
创建一个只有单线程的线程池
例如,在我主要业务完成后,需要写一些日志类的记录,但是我又不关心这类数据是否成功,就可以使用单线程线程池来进行后续的操作,并直接返回结果
//创建长度为3的线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
5. 周期性线程池 newScheduledThreadPool
初始化一个固定长度的线程池,并在首次执行时间到了后周期执行该线程,如果线程没有执行完毕,并超过线程长度,进入排队等待阶段
//定义了一个长度为3,10s后执行,每30S执行一次的周期性线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
//do something
}
},10L,30L,TimeUnit.SECONDS);
线程锁
多线程在访问同一个资源的时候会产生线程安全问题,所以需要一把锁来保证统一时间只能有一个线程在改变资源
1. 原子锁
当线程操作的对象是原子级的,无需加锁,因为原子操作不可分割,无需加锁
public class MultiThread implements Runnable {
private static AtomicInteger ai = new AtomicInteger();
@Override
public void run() {
for(int i=0;i<100;i++) {
ai.incrementAndGet();
System.out.println(ai);
}
}
public static void main(String[] args) {
MultiThread multiThread = new MultiThread();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,4,10L,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(4));
threadPoolExecutor.execute(multiThread);
threadPoolExecutor.execute(multiThread);
threadPoolExecutor.shutdown();
}
}
2. 自动档 synchronized
明确的说,这是一种重量级锁,当这个资源,类对象或者类被锁定的时候,仅有一个线程可以访问,其它的线程都会等待这把锁释放
前提条件:必须是统一把锁才能达到锁的目的
public class MultiThread implements Runnable {
private static Integer index = 0;
@Override
public void run() {
for(int i=0;i<100;i++) {
synchronized (index){
index ++;
}
}
//等同于
for(int i=0;i<100;i++) {
synchronized (MultiThread.class){
index ++;
}
}
}
//也可等同于
//当index不被static声明的时候
public static synchronized void sync(){
index ++;
}
}
统一把锁的情况:
- 同一个对象的代码块或成员变量
- static 声明的成员变量
- 锁 class(等同于 static声明的成员变量)
不是一把锁的情况,以下的情况锁会失败
public class MultiThread implements Runnable {
//index不被static声明
private Integer index = 0;
@Override
public void run() {
for(int i=0;i<100;i++) {
synchronized (index){
index ++;
}
}
}
public static void main(String[] args) {
MultiThread multiThread = new MultiThread();
//这里重新new了一个对象
MultiThread multiThread2 = new MultiThread();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,4,10L,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(4));
threadPoolExecutor.execute(multiThread);
threadPoolExecutor.execute(multiThread2);
}
}
3. 手动档 Lock
使用synchronized 很方便,但是有些场景下不适用,例如我要求在针对读写场景,实现不同的锁类型,读取不锁,写锁,这个时候就要使用Lock进行锁操作
- ReenTrantLock 重入锁,和synchronized区别不大
public class LockThread implements Runnable {
private Lock lock;
public LockThread(Lock lock){
this.lock = lock;
}
@Override
public void run() {
lock.lock();
try {
//do something
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
Lock lock = new ReentrantLock();
new Thread(new LockThread(lock)).start();
new Thread(new LockThread(lock)).start();
}
}
- ReadWorkLock 读写分离锁
public class LockThread implements Runnable {
private ReadWriteLock lock;
public LockThread(ReadWriteLock lock){
this.lock = lock;
}
@Override
public void run() {
lock.writeLock().lock();
//lock.readLock().lock();
try {
//do something
}catch (Exception e){
e.printStackTrace();
}finally {
lock.writeLock().unlock();
//lock.readLock().unlock();
}
}
public static void main(String[] args) {
ReadWriteLock lock = new ReentrantReadWriteLock();
new Thread(new LockThread(lock)).start();
new Thread(new LockThread(lock)).start();
}
}
线程同步工具
1. 闭锁 CountDowmLatch
当我们主线程需要在全部线程执行完毕后,再继续执行业务,可以使用CountDowmLatch
它会阻塞主线程,并等全部其它线程执行完毕后,继续执行后续代码
public class CountDownLatchThread implements Runnable {
private CountDownLatch countDownLatch;
public CountDownLatchThread(CountDownLatch countDownLatch){
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
//do something
countDownLatch.await();
}catch (InterruptedException e){
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(new CountDownLatchThread(countDownLatch)).start();
new Thread(new CountDownLatchThread(countDownLatch)).start();
new Thread(new CountDownLatchThread(countDownLatch)).start();
countDownLatch.await();
//do something
}
}
2. 栅栏 CyclicBarrier
当程序需要执行到某一个步骤后,再同时进入下一步骤的时候使用
例如:
赛跑程序,只有当所有人都站到起跑线的时候(资源准备完毕),再进行比赛,这样的业务可以使用CyclicBarrier来完成
public class CyclicBarrierThread implements Runnable {
private CyclicBarrier cyclicBarrier;
public CyclicBarrierThread(CyclicBarrier cyclicBarrier){
this.cyclicBarrier = cyclicBarrier;
}
@Override
public void run() {
try {
//do something
cyclicBarrier.await();
//do something
}catch (BrokenBarrierException e){
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
new Thread(new CyclicBarrierThread(cyclicBarrier)).start();
new Thread(new CyclicBarrierThread(cyclicBarrier)).start();
new Thread(new CyclicBarrierThread(cyclicBarrier)).start();
}
}
3. 阶段器 Phaser
让一个程序分阶段可控的同步进行.
例如:英语考试的时候,要所有人都听完听力才能继续答题,等全部人答完题才能进行交卷.
- 定义程序分几个阶段 Phaser phaser = new Phaser(2).这里就定义了两个阶段的阶段器
- 对参与的数量进行注册,phaser.register(),后面阶段器根据注册数量判断所有的参与者是不是都已经到达某个阶段
- 阶段阻塞 phaser.arriveAndAwaitAdvance() 在需要等待全员就位的位置进行阻塞等待
- 释放阻塞 phaser.arriveAndDeregister() ,有几个阶段就需要释放几次阻塞,相当于下一个阶段的发令枪
public class PhaserThread {
public static void main(String[] args) {
Phaser phaser = new Phaser(2);
new Thread(new Thread01(phaser)).start();
new Thread(new Thread02(phaser)).start();
phaser.arriveAndDeregister();
phaser.arriveAndDeregister();
}
}
class Thread01 implements Runnable {
private Phaser phaser;
public Thread01(Phaser phaser) {
phaser.register();
this.phaser = phaser;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread01第一阶段完成");
phaser.arriveAndAwaitAdvance();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread01第二阶段完成");
phaser.arriveAndAwaitAdvance();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread01第三阶段完成");
}
}
class Thread02 implements Runnable {
private Phaser phaser;
public Thread02(Phaser phaser) {
phaser.register();
this.phaser = phaser;
}
@Override
public void run() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread02第一阶段完成");
phaser.arriveAndAwaitAdvance();
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread02第二阶段完成");
phaser.arriveAndAwaitAdvance();
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread02第三阶段完成");
}
}
4. 数据交换 Exchanger
用于处理两个线程间的数据交换(只支持两个线程)
public class ExchangerThread implements Runnable {
private Exchanger<String> exchanger;
public ExchangerThread(Exchanger<String> exchanger){
this.exchanger = exchanger;
}
@Override
public void run() {
try {
//do something
String message = exchanger.exchange("数据交换时间到");
System.out.println(message);
}catch (InterruptedException e){
e.printStackTrace();
}
}
public static void main(String[] args) throws Exception {
Exchanger exchanger = new Exchanger<String>();
new Thread(new ExchangerThread(exchanger)).start();
new Thread(new ExchangerThread(exchanger)).start();
}
}
5. 信号量 Semaphore
主要功能是同一个Semaphore控制可入线程数量,超过数量的会被拦截在外,等有线程释放信号的时候才会继续
public class SemaphoreThread implements Runnable {
private Semaphore semaphore;
public SemaphoreThread(Semaphore semaphore){
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
//do something
}catch (InterruptedException e){
e.printStackTrace();
}finally {
semaphore.release();
}
}
public static void main(String[] args) throws Exception {
Semaphore semaphore = new Semaphore(3);
new Thread(new SemaphoreThread(semaphore)).start();
new Thread(new SemaphoreThread(semaphore)).start();
new Thread(new SemaphoreThread(semaphore)).start();
}
}
6. wait升级 Condition
Condition 和 wait() 方法的使用过程是一样的,都是要配合锁使用
当线程获取到锁后,调用Condition或wait()都会释放锁,并等待信号继续
- lock.newCondition() 获取一个 Condition
- Conditon中的await()对应Object的wait()
- Condition中的signal()对应Object的notify()
- Condition中的signalAll()对应Object的notifyAll()