前言
众所周知,每年的双十一,十二,以及六一八等,都是巅峰级别的并发问题,同一时间段甚至每秒都有成千上万请求,那么是什么在支撑着这些大量的请求呢?是多线程,简而言之就是多个程序同时在运行~~,本文会涉及多线程基本使用以及API概述
线程创建方式
Thread方式
描述
Thread相当于线程的接口,可以说每个线程执行都离不开它,归根结底,都是它在执行线程任务~
一:匿名内部类方式
new Thread(){
//内置有一个run方式:该方法是线程执行时业务处理~
@Override
public void run() {
System.out.println("匿名内部类线程声明执行"+"线程名称\t"+this.getName());
}
//调用start方法,即触发当前线程开始执行
}.start();
运行结果
匿名内部类线程声明执行线程名称 Thread-0
二:父子继承方式
//继承Thread 重写线程开启时执行的run方法并打印当前线程名称
public class MyThread extends Thread{
@Override
public void run() {
System.out.println("父子继承方式线程名称\t"+this.getName());
}
//声明构造方法 外部使用当前bean时直接开启线程即调用run方法~
public MyThread(){
this.start();
}
public static void main(String[] args) {
new MyThread();
}
}
运行结果
父子继承方式线程名称 Thread-0
Runnable接口方式
代码如下
//实现runnable接口重写run方法
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println("实现runnable接口方式线程名称\t"+Thread.currentThread().getName());
}
public static void main(String[] args) {
//声明一个线程对象并引用接口方法的执行类作为线程进行开启
new Thread(new MyThread2()).start();
}
}
运行结果
实现runnable接口方式线程名称 Thread-0
拓展:有人会问为什么线程类可以去引用别的类作为线程并执行呢?OK跟进底层源码进行查看如下
Callable接口方式(带有返回值的线程任务)
这个应该是实际当种运用最为广泛的,因为前两种我们发现它都没有返回值,有时候线程处理任务都需要一个返回值进行逻辑业务处理,而Callable的出现真是如大旱望甘霖啊~
代码如下
//实现Callable 泛型类型为String(字符串)
class MyThread3 implements Callable<String> {
@Override
public String call() throws Exception {
//获取随机数
return String.valueOf((int)(Math.random()*10+1));
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//声明一个默认线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//由线程池提交任务执行并返回线程执行完毕之后的结果值
Future<String> submit = executorService.submit(new MyThread3());
//调用Future get方法获取结果
System.out.println(submit.get());
}
}
运行结果
10之内的任何一个随机数字
拓展
- Future是什么
Future表示异步计算的结果。 提供方法以检查计算是否完成,等待其完成,以及检索计算结果。 只有在计算完成时才能使用方法get检索结果,必要时将其阻塞直到准备就绪。
异步说明:
例如,去饭店吃饭,点了汤和主食,主食需要5分钟,而汤只需要2分钟,如果厨师是同步即一次只能干一件事等主食坐好再去做汤由此是7分钟,倘若厨师是异步,在做主食的同时,闲下来的时间去把汤熬上,这样的话 会尽可能减少时间快速做好,~~
API
变量和类型 方法 描述
boolean cancel(boolean mayInterruptIfRunning) 尝试取消执行此任务。
V get() 如果需要等待计算完成,然后检索其结果。
V get(long timeout, TimeUnit unit) 如果需要,最多等待计算完成的给定时间,然后检索其结果(如果可用)。
boolean isCancelled() 如果此任务在正常完成之前取消,则返回 true 。
boolean isDone() 如果此任务完成,则返回 true 。
更多操作可以依靠API进行一些案例测试
线程等待与通知(通信)
代码如下
注意:凡是使用线程wait与notify线程之间任务执行必须使用synchronized锁进行同步
原因:就当前Thread线程方式为例,它是异步交替执行,可能执行一个线程期间其它任务直接唤醒然后另一个又进入交替执行导致线程死锁或数据紊乱等问题,加synchronized的目的就是正确执行wait方式时,必须获取当前对象监听器,wait等待时会释放监听器由其它线程执行任务时可以获取监听器并尝试对它进行唤醒。
class HAHA {
static class MyThread2 extends Thread {
private Object o;
public MyThread2(Object oo) {
this.o = oo;
}
int x = 10;
@Override
public void run() {
synchronized (o) {
while (true) {
if (x > 0) {
System.out.println(System.currentTimeMillis()+"我执行了自减操作" + x--);
if (x == 5) {
try {
//等待
System.out.println(System.currentTimeMillis()+"我执行了等待操作");
o.wait();
} catch (InterruptedException e) {
}
}
}else {
break;
}
}
}
}
}
static class MyThread9 extends Thread {
private Object o;
public MyThread9(Object oo) {
this.o = oo;
}
@Override
public void run() {
synchronized (o) {
o.notify();
System.out.println(System.currentTimeMillis()+"当前值是5,线程等待被唤醒");
System.out.println("准备休眠两秒再唤醒当前线程");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
}
}
}
public static void main(String[] args) {
Object o = new Object();
MyThread2 myThread2 = new MyThread2(o);
MyThread9 myThread9 = new MyThread9(o);
new Thread(myThread2).start();
new Thread(myThread9).start();
}
}
运行结果
可以看到,期间等于5的时候线程处于等待状态,然后隔了两秒线程被唤醒
1593596066941我执行了自减操作10
1593596066941我执行了自减操作9
1593596066941我执行了自减操作8
1593596066941我执行了自减操作7
1593596066941我执行了自减操作6
1593596066941我执行了等待操作
1593596066999当前值是5,线程等待被唤醒
准备休眠两秒再唤醒当前线程
1593596068999我执行了自减操作5
1593596068999我执行了自减操作4
1593596068999我执行了自减操作3
1593596068999我执行了自减操作2
1593596068999我执行了自减操作1
线程安全与处理
多线程也有弊端,如果是在高并发情况下访问频率过高或程序运行过慢会导致数据紊乱或死锁的产生,因此要想用好多线程就必须在保证数据安全并且程序运行状态优良的情况下合理使用才可~
代码如下
//使用实现Runnable方式创建线程任务并执行
public class MyRunable implements Runnable {
int counts=15;
@Override
public void run() {
while (true) {
if (counts <= 0) {
break;
}
//模拟网络延迟这里休眠0.5秒
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + counts);
counts--;
}
}
public static void main(String[] args) {
MyRunable myRunable = new MyRunable();
new Thread(myRunable,"张三").start();
new Thread(myRunable,"李四").start();
}
}
运行结果
张三 15
李四 15
张三 13
李四 13
李四 11
张三 11
张三 9
李四 9
张三 7
李四 7
李四 5
张三 5
张三 3
李四 2
张三 1
李四 1可想而知,肯定会出现数据紊乱,因为线程与线程之间没有任何安全保障,谁抢到了CPU的资源即都可以访问,换而言之,同一时间内当我一个线程正在操作这个counts–还没结束的时候另外一个线程就已经在处理了,由此导致数据没有准确性~
解决方法-synchronized
//用synchronized包裹计算方法体,锁对象为当前实例Bean,前提你在运行时一定要是同一个对象~
public void run() {
while (true) {
synchronized (this) {
if (counts <= 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "\t" + counts);
counts--;
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
**作用:
实现线程期间的同步作用,只要是它修饰的方法或对象,线程执行期间每次只能进入一个线程执行,期间其它线程都在排队等待,从而不会出现串行执行的问题保证数据的安全性**
用法:
指定加锁对象:给指定对象加锁,进入同步代码时需要获取锁才可执行
指定实例方法:给当前实例方法加锁,方法上锁不能串行访问只能等待方法执行完毕即执行下一次
指定静态方法:对当前类加锁,进入同步代码前需要获取当前锁即可运行~
拓展-lock锁
synchronized对比lock区别
synchronized:是java中关键字,由jvm维护,由底层自动加锁以及执行完毕之后的释放锁等一系列操作
lock:jdk1.5后推出的类,提供对应API,是API层面锁形式,由开发组手动去获取锁并释放锁,
对比上述实例-简单改造如下
//声明锁对象-在进行计算操作时打开锁-执行完毕之后释放锁(这里采用finally,程序运行期间很可能各种异常bug,异常出现之后锁不会释放所以这里使用这个可以让程序不管运行还是失败都会释放锁~)
public class MyRunable implements Runnable {
ReentrantLock lk = new ReentrantLock();
int counts = 15;
@Override
public void run() {
while (true) {
lk.lock();
try {
if (counts <= 0) {
break;
}
System.out.println(Thread.currentThread().getName() + "\t" + counts);
counts--;
Thread.sleep(500);
} catch (Exception e) {
e.printStackTrace();
} finally {
lk.unlock();
}
}
}
public static void main(String[] args) {
MyRunable myRunable = new MyRunable();
new Thread(myRunable, "张三").start();
new Thread(myRunable, "李四").start();
}
}
线程池使用
实际使用中肯定不能用它默认的线程池,首先扩展性不好,针对业务不同那各项的配置要求参数也不同,所以我们需要自己定义线程池
可以参照我的另外一篇实际开发中用到的线程池系列文章
开发中线程不安全的API
并发高的情况下,如下几个API都是不安全的
- ArrayList
声明实例时,使用Vector代替
//Vector的底层中 元素方法加了同步锁
List<String> list = new Vector<>();
- HashSet
Set<String> set2 = Collections.synchronizedSet(new HashSet<>());
Set<String> set3 = new CopyOnWriteArraySet<>();
- HashMap
Map<String, String> map2= Collections.synchronizedMap(new HashMap<>());
Map<String, String> map3 = new ConcurrentHashMap<>();
其它还有,凡是不安全的API都要考虑线程安全问题,