博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java多线程和高并发
阅读量:3516 次
发布时间:2019-05-20

本文共 10874 字,大约阅读时间需要 36 分钟。

本文介绍Java多线程运用知识点.

一.如何新建一个线程?

(1).继承java.lang.Thread,覆盖run().

(2).实现接口java.lang.Runnable,覆盖run().

(3)实现接口java.lang.Callable,覆盖call()此方法可以带返回值和抛出异常.

二.如何选择线程的实现方式?

实现接口的线程有更大的灵活性,更符合面向对象分工的思想,将线程任务独立出来.如果线程任务有返回值,就采用实现Callable接口

三.线程控制方式

1.thread.join() : 调用其他线程的join()方法,当前线程会等待thread执行完成后继续.

2.thread.setDaemon(true) : 设置thread线程为后台线程(必须在线程启动之前设置),当所有前台线程结束后,系统通知后台线程结束

3.Thread.sleep() : 阻塞当前线程一定的时间(ms).

4.Thread.yield() : 将当前线程设置为就绪.也就是让系统重新调度

5.设置获取线程优先级 : setPripority(int),getPripority().可以为1-10,10个等级.默认有三种常量

MAX-PRIORITY=10,MIN_PRIORITY=1,NORM_PRIORITY=5.

四.线程同步

1.synchronized关键字.

使用synchronized可以同步语句块和同步方法.同步语句块需要指定一个加锁对象.对于实例方法,会给调用该方法的对象加锁,对于静态方法,会给类加锁.在解锁之前另一个调用那个对象(类)的方法的线程会被阻塞,直到解锁.当同步语句块或者方法结束后即解锁.

2.同步锁Lock.

一种更强大的线程同步机制-通过显示定义同步锁来实现同步.而且可以支持多个Condition对象以实现线程协作.

Condition提供方法:

await(),当前线程等待其他线程唤醒

signal(),唤醒此Lock对象上等待的一个线程.如果有多个,则被唤醒的线程是不确定的.

signalAll().唤醒此Lock对象上等待的所有线程.

在实现中,比较常用的是可重入锁ReentrantLock.

使用锁和条件的格式示例:

1: /**   2:  * 同步锁示例   3:  * @author   4:  *   5:  */   6: public class LockDemo {   7:   public static void main(String[] args) {   8:     Acount acount=new Acount();   9:     new Thread(new WithdrawTask(acount)).start();  10:     new Thread(new DepositTask(acount)).start();  11:   }  12: }  13: class Acount{  14:   //定义锁  15:   private final ReentrantLock lock=new ReentrantLock();  16:   //定义一个条件  17:   private final Condition condition1=lock.newCondition();  18:   private int blance=0;  19:   public int getBlance() {  20:     return blance;  21:   }  22:   public void withdraw(int amount){  23:     lock.lock();  24:     try{  25:       while(blance

五.死锁

有于加锁机制,程序会发生多个线程互相等待对方释放锁的情况,这就是死锁.

在多线程中要考虑死锁的情况.为避免死锁可以通过设置资源访问优先级,或者调整线程推进顺序等方法解决.

死锁情况示例:

1: import java.util.concurrent.locks.ReentrantLock;   2:   3: public class DeadLockDemo {   4:   public static void main(String[] args) {   5:     A a=new A();   6:     B b=new B();   7:     a.setB(b);   8:     b.setA(a);   9:     new Thread(a).start();  10:     new Thread(b).start();  11:   }  12: }  13: class A implements Runnable{  14:   private ReentrantLock lock=new ReentrantLock();  15:   private B b;  16:   public B getB() {  17:     return b;  18:   }  19:   public void setB(B b) {  20:     this.b = b;  21:   }  22:   public void fun(){  23:     lock.lock();  24:     System.out.println("A请求执行B的方法");  25:     b.fun();  26:     System.out.println("A方法执行");  27:     lock.unlock();  28:   }  29:   public void run(){  30:     while(true){  31:       fun();  32:     }  33:   }  34: }  35: class B implements Runnable{  36:   private ReentrantLock lock=new ReentrantLock();  37:   private A a ;  38:    39:   public A getA() {  40:     return a;  41:   }  42:   public void setA(A a) {  43:     this.a = a;  44:   }  45:   public void fun(){  46:     lock.lock();  47:     System.out.println("B请求执行A的方法");  48:     a.fun();  49:     System.out.println("B方法执行");  50:     lock.unlock();  51:   }  52:   public void run(){  53:     while(true){  54:       fun();  55:     }  56:   }  57: }

六.阻塞队列

BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(nullfalse,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:

  抛出异常 特殊值 阻塞 超时
插入 add((e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take() pool(time,unit)
检查 element() peek() _ _

阻塞队列主要用于实现生产者-消费者队列.

生产者-消费者场景示例:

 

1:   2: /**   3:  * 使用阻塞队列实现生产者消费者场景   4:  * @author WeiCong   5:  *   6:  */   7: public class ProducerAndConsumer {   8:   9:   public static void main(String[] args) {  10:     Buffer buffer=new MyBuffer(1);  11:     ExecutorService executor = Executors.newFixedThreadPool(3);  12:     executor.execute(new Producer(buffer));  13:     executor.execute(new Consumer(buffer));  14:     executor.shutdown();  15:   }  16: }  17:  18: class MyBuffer
extends Buffer{ 19: public MyBuffer(int size){ 20: super(size); 21: } 22: public MyBuffer(){ 23: super(); 24: } 25: public Object produceCore() { 26: return new Random().nextInt(100)+1; 27: } 28: } 29: 30: class Producer implements Runnable{ 31: private Buffer buffer; 32: public Producer(Buffer buffer){ 33: this.buffer=buffer; 34: } 35: public void run() { 36: try { 37: while(true) { 38: Object obj=buffer.produceCore(); 39: System.out.println("生产:"+obj); 40: buffer.produce(obj); 41: Thread.sleep(new Random().nextInt(1000)+10); 42: } 43: } catch (InterruptedException ex) {ex.printStackTrace();} 44: } 45: public Object produce() { return null; } 46: 47: } 48: class Consumer implements Runnable{ 49: private Buffer buffer; 50: public Consumer(Buffer buffer){ 51: this.buffer=buffer; 52: } 53: public void run() { 54: try { 55: while(true) { 56: System.out.println("\t\t消费:"+buffer.consume()); 57: Thread.sleep(new Random().nextInt(1000)+10); 58: } 59: } catch (InterruptedException ex) {ex.printStackTrace();} 60: } 61: 62: } 63: abstract class Buffer
{ 64: private BlockingQueue
queue=null; 65: public Buffer(Collection
c){ 66: queue=new LinkedBlockingQueue
(c); 67: } 68: public Buffer(int size){ 69: queue=new LinkedBlockingQueue
(size); 70: } 71: public Buffer(){ 72: queue=new LinkedBlockingQueue
(); 73: } 74: public T consume() throws InterruptedException { 75: T t=queue.take(); 76: return t; 77: } 78: public void produce(T obj) throws InterruptedException { 79: queue.put(obj); 80: } 81: public abstract T produceCore(); 82: } 83:

七.信号量

信号量用来限制同时访问资源的线程数.在访问资源前,必须从信号量获取许可.访问完成后将许可返回给信号量.允许设置公平策略(具体查看API)

java.util.concurrent.Semaphore.

+Semaphore(numberOfPermits: int) 创建指定数目许可的信号量.公平策略为false

+Semaphore(numberOfPermits: int,fair : boolean ) 创建带制定数目的许可和公平策略的信号量

+acquire(): void 获得信号量的许可,一直阻塞直到获取.

+release() : void 释放信号量许可

说明:只有一个许可的信号量可以实现线程互斥.

示例1:修改前面Acount的内部实现为使用信号量:

定义信号量:

1: //信号量   2:     private final Semaphore semaphore =new Semaphore(1);   3:  将deposit设置为只允许一个线程访问:
1: public void deposit(int amount){   2: //    lock.lock();   3:     try{   4:       semaphore.acquire();   5:       blance+=amount;   6:       System.out.println("存钱"+amount+",余额"+getBlance()+"");   7: //      condition1.signalAll();   8:     } catch (InterruptedException e) {   9:       e.printStackTrace();  10:     }finally{  11: //      lock.unlock();  12:       semaphore.release();  13:     }  14:   }

八.线程组和未处理的异常

使用线程组可以对一批线程进行分类管理.

java使用ThreadGroup表示线程组.创建线程组时可以指定父线程组和此线程组的名称.

通过Thread的构造方法可以为任务指定线程组.

如果线程执行过程中抛出了一个未处理的异常,JVM在结束该线程之前自动查找是否有对应的Thread.UncaughtExceptionHandler对象,如果有,则调用该对象的uncaughtException(Thread t,Throwable e)处理异常.

Thread类提供了静态方法可设置异常处理器.

ThreadGroup类实现了Thread.UncaughtExceptionHandler.所以每一个线程所属的线程组将会作为默认的处理器.

九.线程池

从JDK5开始java内建支持线程池.

通过Executors工厂可以差un关键不同的线程池:

ExecutorService newCachedThreadPool() 创建具有缓存功能的线程池
ExecutorService newFixedThreadPool() 创建一个可重用的,具有固定线程数的线程池
ExecutorService newSingleThreadExecutor() 创建一个单线程线程池
ScheduledExecutorService newScheduledThreadPool() 创建具有指定数目的线程池,可以在指定延迟后执行线程任务
ScheduledExecutorService newSingleThreadScheduledExecutor() 单个线程池,可延迟执行任务
ExecutorService newWorkStealingPool(int parallelism) (jdk8)创建持有足够多的线程的线程池来支持给定的并行级别,该方法能使用多个队列减少竞争.
ExecutorService newWorkStealingPool() (jdk8)上一个线程池的简化版,自动根据CPU数目设置并行级别.
   
   

ExecutorService代表尽快执行线程的线程池.

ScheduledExecutorService 代表可在指定延迟后或周期性地执行线程任务的线程池.通过相应的方法设置延迟.

线程池用完后应该调用shutdown()方法,调用此方法的线程池不在接收任务,并且启动关闭序列,但会将所有提交的任务执行完成.

使用shutdownNow()方法将会立即强制结束线程.

十.ForkJoinPool

为从分利用多核CPU,多CPU的性能优势.我们可以将一个大任务分解成多个小任务放在多个CPU上执行,之后再合并执行结果.

ForkJoinPool就支持这种并行计算.

ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池.

示例:

1:   2: /**   3:  * java 8 增强的ForkJoinPool ,充分利用多CPU,多核CPU的优势,将一个任务分解成多个小任务并行执行来提高效率   4:  */   5: public class ForkJoinPoolTest {   6:   public static void main(String[] args) throws Exception {   7:     test2();   8:     test1();   9:   }  10:   public static void test2() throws Exception {  11:     int[] arr = new int[10000];  12:     for (int i = 0; i < arr.length; i++) {  13:       arr[i] = i + 1;  14:     }  15:     long start = System.nanoTime();// 获取系统累加开始时间点  16:     ForkJoinPool pool = new ForkJoinPool();  17:     Future
future = pool.submit(new SumTask(arr, 0, arr.length-1)); 18: System.out.println(future.get()); 19: pool.shutdown(); 20: long end = System.nanoTime(); 21: long ms = TimeUnit.NANOSECONDS.toMillis(end - start);// 得到累加所用的时间 22: System.out.println("用时:" + ms + " ms"); 23: } 24: 25: public static void test1() throws InterruptedException { 26: ForkJoinPool pool = new ForkJoinPool(); 27: pool.submit(new PrintTask(0, 300)); 28: pool.awaitTermination(2, TimeUnit.SECONDS); 29: pool.shutdown(); 30: } 31: 32: } 33: /**有返回值的任务 34: * 计算数组1-10000的和分解为多个求相隔1000的小任务 */ 35: class SumTask extends RecursiveTask
{ 36: private static final int e = 1000; 37: private int[] arr; 38: private int start; 39: private int end; 40: public SumTask(int[] arr, int start, int end) { 41: this.start = start; 42: this.end = end; 43: this.arr = arr; 44: } 45: protected Integer compute() { 46: int sum = 0; 47: if (end - start < e) { 48: for (int i = start; i <=end; i++) { 49: sum += arr[i]; 50: } 51: } else { 52: int middle = (start + end) / 2; 53: SumTask left = new SumTask(arr, start, middle); 54: SumTask right = new SumTask(arr, middle+1, end); 55: left.fork(); 56: right.fork(); 57: return left.join() + right.join(); 58: } 59: return sum; 60: } 61: } 62: /**无返回值的任务 63: * 将输出0-300的任务分解为输出相隔为50的多个小任务 */ 64: class PrintTask extends RecursiveAction { 65: // 每个任务最多处理50个 66: private static final int THRESHOLD = 50; 67: private int start; 68: private int end; 69: 70: public PrintTask(int start, int end) { 71: this.start = start; 72: this.end = end; 73: } 74: protected void compute() { 75: if (end - start < THRESHOLD) { 76: for (int i = start; i <= end; i++) { 77: System.out.println(Thread.currentThread().getName() + "::" + i 78: + " "); 79: } 80: } else { 81: // 当数量多余50个时,任务分解 82: int middle = (start + end) / 2; 83: PrintTask left = new PrintTask(start, middle); 84: PrintTask right = new PrintTask(middle, end); 85: left.fork(); 86: right.fork(); 87: } 88: } 89: }

十一.线程安全的集合类

JDK1.5之后,在java.util.concurrent包下供了大量支持高效并发访问的集合接口和实现类.

几乎对所有的传统集合都进行了包装,大致分为两类:

1.以Concurrent开头的集合类.代表了支持并发访问的集合.

2.以CopyOnWrite开头的集合类,采用底层赋值数组的方式来实现写操作.

十二.补充

读写锁,Java提供的同步互斥工具

Lock readLock() 获取一个可以被多个线程读的锁,排斥所有写操作.

Lock writeLock()获取一个写锁,排斥所有其他读/写操作.

转载地址:http://lvvqj.baihongyu.com/

你可能感兴趣的文章
两天,我把分布式事务搞完了
查看>>
肝!分享两本高质量网络/算法书籍,进大厂必备!
查看>>
命名实体识别:详解BiLSTM_CRF_Pytorch_Tutorial代码
查看>>
建表参数(pctfree,IniTrans,maxtrans)含义
查看>>
springBoot官方入门篇一 引入依赖并运行
查看>>
java大牛25点
查看>>
LSTM 讲解
查看>>
SpringBoot中启动的端口与设置的端口不一致
查看>>
Lua 元表及元方法
查看>>
C#常用的设计模式
查看>>
C#-快速排序算法
查看>>
docker 部署SpringBoot项目
查看>>
mybatis基础知识(四)&输入映射与输出映射
查看>>
gitflow工作流
查看>>
【MongoDB】update修改器($set、$unset、$inc、$push、$pull、$pop)
查看>>
JAVA 继承
查看>>
电脑键盘突然不能打字,很多键变成快捷键了
查看>>
Hbase表映射Hive表三种方法
查看>>
Java中获取List长度
查看>>
this关键字有什么用处?怎么用? 1.访问成员变量,区分成员变量和局部变量。 2.访问成员方法。 3.访问构造方法。 4.返回对当前对象的引用 5.将对当前对象的引用作为参数传递给其他方法。
查看>>