多线程:
一、定义
Java程序本身就是多线程。
二、相关概念
1.操作系统
内核实现其他资源的管理和调度
2.进程
一个进程就是操作系统中运行的一个应用程序,每个进程都有进程ID标志唯一性
3.线程
线程是计算机进行调度的最小资源,线程运行一般也包含在进程中。
三、java中的线程
在java中Thread就表示线程
注意: 1.启动线程不能直接调用run方法,而是调用start启动。
2.虽然在Thread中提供了stop终止线程,但是可能会出现不可控问题,不推荐使用应该通过程序逻辑控制线程的终止条件。
3.如果线程运行过程中出现异常也会导致线程异常终止
四、java中创建线程的方法(3种)
Ⅰ: 通过一个类继承Thread类,调用创建对象的start方法启动线程
例子:
public class Demo1 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
// myThread.run();这里不应该使用run,不然就是方法重写,而不是启动线程
myThread.start();
}
}
class MyThread extends Thread{
@Override
public void run() {
//当调用start方法是启动线程运行的逻辑代码
System.out.println(1);
try { System.out.println("让我睡5秒");
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(2);
System.out.println(3);
}
}
注意:这种创建线程的方法不推荐使用,因为java支持的是单继承,这里会导致类的扩展性降低。
Ⅱ: 通过实现一个Runnable接口,并实现run方法,并将创建的对象传递给Thread构造
例子:
public class Demo2 {
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1();
// myThread1没有start方法,所以要使用Thread的构造器new一个Thraed对象
Thread thread = new Thread(myThread1);
thread.start();
System.out.println(Thread.activeCount());
}
}
class MyThread1 implements Runnable{
@Override
public void run() {
//当调用start方法是启动线程运行的逻辑代码
System.out.println(1);
try { System.out.println("让我睡3秒");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(2);
System.out.println(3);
}
}
Ⅲ:创建一个类实现JUC(Java并发工具包)下的Callable<V>j接口,泛型V表示线程运行结束后返回的类型
例子:
public class Demo3 {
public static void main(String[] args) throws Exception{
int questions=50;
AStudent aStudent = new AStudent(questions);
FutureTask<String> stringFutureTask = new FutureTask<String>(aStudent);//必须使用这个才能创建Thread对象
Thread thread = new Thread(stringFutureTask);
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//通过futureTask获取执行的结果
String s = stringFutureTask.get();
System.out.println(s);
}
}
class AStudent implements Callable<String> {
private int question;
public AStudent(int question){
this.question=question;
}
@Override
public String call() {
while (this.question > 0) {
System.out.println("A当前回答的问题" + this.question--);
}
return "A答题结束";
}
}
五、线程类的相关方法
构造方法 | |
---|---|
Thread() | |
Thread(Runnable target) | |
常用方法 | |
Thread.currentThread() | 获取当前线程 |
String getName() | 获取线程的名字 |
void setName(string name) | 设置线程的名称 |
Long getId() | 获取线程ID |
final void setDaemon(boolean on) | 是否设置为守护线程 |
Thread.activeCount() | 当前活动的线程数 |
Tread.sleep(tm) | 设置睡眠时间 |
Object类中关于线程状态相关方法 | |
final native void notify() | 通知 |
final native void notifyAll() | 通知所有 |
native void wait(long timeout) | 等待,timeout表示等待超时间 |
native void wait() | 等待 |
native void stop() | 终止线程(不推荐) |
六、线程运行状态
顺序进行:
线程运行状态 | |
---|---|
1.创建 NEW | 实例化线程对象时(初始) |
2.就绪,运行 RENNING | 就绪:实现化线程对象并调用start方法(等待分配cpu运行资源)</b>运行:线程分配到了运行资源,只有就绪的线程才可以进入运行状态 |
3.线程终止 TERMINATION | stop(),线程正常执行结束,运行时出现异常导致线程终止 |
4.阻塞 BLOCKING | 当线程遇到synchronized并且没有强盗锁资源时 |
5.限时等待 WAITTING TIMEOUT | 线程调用类似于wait(timeout),join()线程加入时会进入显示等待。当等待超时后线程会重新进入就绪状态 |
6.等待WAITTING | 调用wait()进入等待,在等待期间会释放锁资源,并且不抢占cpu资源,只有被其他线程通知notify()时才会被唤醒 |
wait()线程等待,sleep()睡眠:
被设置为wait()的线程,不会争抢CPu资源,而且还会释放锁sleep()睡眠,不会争抢CPU,而且不会释放锁
七、线程并发安全问题
1、定义
如果多个线程在对同一个资源进行修改操作时,就可能会产生并发安全问题。
2、如何处理并发安全问题
1-使用同步代码块 synchronized(对象-用来承载锁的对象){
//将需要同步的代码包裹起来
}
注意: 用作承载锁的对象必须是所有的线程都能看到的同一个对象 同步代码块一次只有一个线程在其中执行,会降低效率,所以在设 计同步代码块的时候,应该在能包裹住会造成线程并发安全问题代码 的前提下尽量少的包裹其他代码,减少在同步中执行的时间,可以整体的提升效率
同步方法: 方法中所有代码都需要上锁,可以将当前方法设置为同步方法;在方法前加上synchronized。 如果是同步的非静态方法,锁对象选择的就是this。 同步的静态方法,锁对象是当前类的class字节码。
八、死锁
锁之间互相等待无法进行下去的状态就称为产生了死锁。 如何解决死锁?
-
其中锁占用一方强制退出
-
等待超时机制
案例:打印机和扫描仪相互抢占导致死锁
public class Thread08 {
public static void main(String[] args) {
Thread p1 = new Thread(new Person1());
Thread p2 = new Thread(new Person2());
p1.start();
p2.start();
}
}
//打印机
class Printers {
}
//扫描仪
class Scanners {
}
class Person1 implements Runnable{
public void run() {
synchronized (Printers.class) {
System.out.println("Person1 正在使用打印机...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Scanners.class) {
System.out.println("Person1 正在使用扫描仪...");
}
}
}
}
class Person2 implements Runnable{
public void run() {
synchronized (Scanners.class) {
System.out.println("Person2 正在使用扫描仪...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i=0; i<10; i++) {
if(i==6) {
throw new RuntimeException("no");
}
}
synchronized (Printers.class) {
System.out.println("Person2 正在使用打印机...");
}
}
}
}
九、关于线程的其他细节
1、守护线程
线程可以分为前台线程和后台线程
如果一个线程被设置为守护线程后,当前台没有线程时,哪怕没有执行完也会自动退出。设置守护线程的方式:在运行线程前调用setDaemon方法
方法: void setDaemon(boolean on) //默认为false,true表示设置为守护(后台)线程
public class Demo6{
public static void main(String[] args) {
Person person = new Person();
Thread thread = new Thread(person);
thread.setDaemon(true);//守护线程
thread.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"线程已经执行结束了");
}
}
class Person implements Runnable{
@Override
public void run() {
for (int i=1;i<=10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前执行的编号:"+i);
}
}
}
2、线程加入
当正在执行的线程执行到了join(),就会将执行权让给调用此方法的线程,自己转入冻结状态,
直到调用此方法的线程结束,当前线程才会从冻结状态醒来,接着执行。
方法:
final void join()
public class Demo7 {
public static void main(String[] args) throws Exception{
System.out.println("准备炒菜");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("菜没了"); try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Buy buy = new Buy();
Thread thread = new Thread(buy);
//先启动再加入
thread.start();
thread.join();
System.out.println("继续炒菜炒菜");
}
}
class Buy implements Runnable{
@Override
public void run() {
System.out.println("准备出门。。。。");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("买菜");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("回家");
}
}
3、线程退让:
让当前线程暂停执行,可以执行其他线程
Thread.yield();
4、线程执行的优先级
修改线程的优先级1--10,越大优先级越高,但是这仅仅是理论上的优先级,真正执行时,
谁执行谁不执行,仍然是概率的,只是概率高低有所不同。通常 1、5、10表现的效果比较明显线程默认优先级都是5。
方法: void setPriority(int newPriority) //优先级:1~10
final int getPriority() //获取优先级
十、volatile关键字
1、作用 关键字volatile的主要作用是使变量在多个线程间可见。 2、特点 保证可见性: 也就是说某个线程修改了共享变量,线程在修改变量时不会把值缓存在寄存器或者其他地方,而 是会把值刷新回主内存。当其它线程读取该共享变量时,会从主内存重新获取变量最新值,而不是 从当前线程的工作内存中获取。
保证有序性(禁止指令重排序): 所谓指令重排序:为了提高性能,编译器和处理器常常会对既定的代码执行顺序进行指令重排序。
3、volatile与synchronized的区别 -关键字volatile是线程同步的轻量级实现,所以volatile的性能略胜于synchronized, 并且volatile只能修饰变量,而synchronized可以修饰方法、代码块等; -多线程访问volatile不会发生阻塞,而synchronized会出现阻塞; -volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性, 也可以间接保证可见性,因为它会将私有内存和共有内存中的数据做同步; -关键字volatile解决的是变量在多个线程之间的可见性;而synchronized关键字解决的 是多个线程之间访问资源的同步性。
十一、ThreadPoolExecutor
1、构造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
参数介绍: int corePoolSize:核心线程大小,线程池最小的线程数量。 int maximumPoolSize:最大线程大小,线程池的最大的线程数量 long keepAliveTime:超过corePoolSize的线程在不活动的状态下存活的最大时间。 TimeUnit unit:keepAliveTime的时间单位。 BlockingQueue<Runnable> workQueue:任务队列。 ThreadFactory threadFactory:线程池工厂,用来创建线程。 RejectedExecutionHandler handler:拒绝策略。
2、执行流程
(1)当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。 (2)当线程池超过corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行。 (3)当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务。 (4)当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理。 (5)当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,释放空闲线程。 (6)当设置allowCoreThreadTimeOut(true)时,该参数默认false,线程池中corePoolSize线程空闲时间达到keepAliveTime也将被关闭。
3、拒绝策略
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,这是默认的一种方式。 ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务 ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。
4、线程池设置
cpu密集型 cpu密集的意思是该任务需要大量的运算,而没有阻塞,cpu一直全速运行。 cpu密集型任务配置尽可能少的线程数量:以保证每个cpu高效的运行一个线程。 通常设置方式: 与cpu核心数相同的线程数或者加1 io密集型 io密集型,即该任务需要大量的io,即大量的阻塞。 在单线程上运行io密集型的任务会导致浪费大量的cpu运算能力浪费在等待。所以在io密集型任务中使用多线程可以大大的加速程序运行,这种加速主要就是利用了被浪费掉的阻塞时间。 通常设置方式: cpu核数 * 2个线程的线程池。
获取核心数 Runtime.getRuntime().availableProcessors();
5、使用Executors创建线程池:
1. Executors.newCachedThreadPool 是一个根据需要创建新线程的线程池,当一个任务提交时,corePoolSize为0不创建核心线程,SynchronousQueue是一个不存储元素的队列,可以理解为队里永远是满的,因此最终会创建非核心线程来执行任务。 2. Executors.newSingleThreadExecutor 单线程线程池,只有一个核心线程,用唯一的一个共用线程执行任务,保证所有任务按指定顺序执行 3. Executors.newFixedThreadPool 定长线程池,核心线程数和最大线程数由用户传入,可以设置线程的最大并发数,超出在队列等待 4. Executors.newScheduledThreadPool 定长线程池,核心线程数由用户传入,支持定时和周期任务执行
Executors总结:
FixedThreadPool和SingleThreadExecutor 允许的请求队列长度为Integer.MAX_VALUE,可能会堆积 大量的请求,从而引起OOM异常
CachedThreadPool 和newScheduledThreadPool允许创建的线程数为Integer.MAX_VALUE,可能会创 建大量的线程,从而引起OOM异常