文章目录
多线程概述
进程:正在运行的程序,是系统进行资源分配和调度的独立单位。每一个进程都有自己的内存空间和系统资源。
线程:是进程中的单个顺序控制流,是一条执行路径。当进程包含多条执行路径,则称为多线程程序。
多进程的是为了提高CPU使用率,多线程是为了提高应用程序的使用率。
Java程序运行时,会启动JVM,等于启动了一个应用进程,该进程会自动启动一个“主线程”,然后主线程去调用某个类的main方法,所以main方法运行在主线程中。
JVM启动时至少启动了垃圾回收和主线程,所以是多线程的。
并发:允许两个任务彼此干扰,统一时间点,只有一个任务运行,两个任务是交替执行的。
并行:两个任务执行在时间上是重叠的,两个任务在同一时刻互不干扰,同时执行。
线程的生命周期
线程是一个动态执行的过程,它有一个从产生到死亡的过程。
- 新建:创建线程对象。
- 就绪:线程对象启动,处于就绪队列中,等到JVM里线程调度器的调度。
- 运行:线程获取到了CPU执行权。
- 阻塞:当线程获取所需资源失败时,进入到阻塞状态;当获得资源后可重新进入就绪状态。
- 死亡:当运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
- 线程的状态转换图如下:
线程的实现
通过继承Thread类
- 定义类继承Thread类;
- 重写run()方法;
- 创建子类对象;
- 启动线程。
package multithread;
public class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
public void run() {
//需要被线程执行的代码
for(int x=0;x<8;x++) {
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}
package multithread;
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread("Rachael");
MyThread my2 = new MyThread("Joey");
my1.start();
my2.start();
}
}
运行结果如下:
- Java采用的抢占式调度模型,谁优先级高谁先执行,线程的默认优先级是5,我们可以通过
setPriority(int newPriOrity)
更改线程的优先级。 - 但是线程的优先级高,不代表一定会先执行完毕,只有在次数特别多的情况下,才能体现出来。
public final void setDaemon(boolean on)
设置守护线程,通常来说,守护线程经常被用来执行一些后台程序,例如JVM中的垃圾回收线程就是典型的守护线程。当正在运行的程序都是守护线程时,Java虚拟机退出。join()方法
:让主线程等待,一直到其他线程终止。public void interrupt()
:中断线程。public static void sleep(long millis)
:在指定的毫秒数内让当前正在执行的线程休眠,暂停执行,例如Thread.sleep(50)
。public static void yield()
:让掉自己的CPU执行时间,从运行状态变为就绪状态,CPU会从众多的就绪线程选择,也就是说,那个线程还是很有可能会被再次执行。
通过实现Runnable接口
- 定义类实现Runnable接口;
- 重写run()方法;
- 创建实现Runnable接口类的对象;
- 创建Thread类对象,并把Runnable对象作为构造参数传递。
package multithread;
public class MyRunnable implements Runnable{
//public static Thread currentThread();
//该方法可以获取任意方法所在的线程
@Override
public void run() {
for(int x = 0; x < 10; x++) {
System.out.println(Thread.currentThread().getName()+":"+x);
}
}
}
package multithread;
public class MyRunnableDemo {
public static void main(String[] args) {
MyRunnable my = new MyRunnable();
Thread t1 = new Thread(my,"Michael");
Thread t2 = new Thread(my, "Emaly");
t1.start();
t2.start();
}
}
运行结果如下:
实现Callable接口,并通过FutureTask包装器来创建Thread线程
package multithread;
import java.util.concurrent.Callable;
public class ThreadDemo implements Callable<String>{
@Override
public String call() throws Exception {
return "This is a thread demo!";
}
}
package multithread;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest {
public static void main(String[] args) {
ThreadDemo demo = new ThreadDemo();
FutureTask<String> task = new FutureTask<String>(demo);
Thread t = new Thread(task);
t.start();
String res = null;
try {
res = task.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(res);
}
}
使用线程池创建线程
为什么会使用线程池?
许多服务器端应用程序需要处理来自客户端的大量任务请求,但是为每个请求创建一个新的线程的开销很大,创建和销毁线程花费的时间和消耗的系统资源可能比花在用户请求上的时间和资源更多。
而线程池为线程管理的开销问题和资源不足问题提供了解决方案:通过让多个任务重用线程,线程创建的开销被极大地降低了;通过适当地调整线程池中的线程数目,也就是说当请求数目超过某个阈值时,就强制其他任何新到的请求一直等待,知道获得可用线程来处理为止,从而防止系统资源不足。
创建线程池的声明格式如下:
import java.util.concurrent.*;
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit millseconds, B
lockingQueue runnalbeTaskQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
其中输入参数:
- 线程数目corePoolSize:当提交一个任务到线程池中,线程池会创建一个线程来执行任务,等到需要执行的任务数大于线程池基本大小时就不再创建;
- 线程最大数目maximumPoolSize:线程池允许创建的最大线程数,如果任务队列已满,并且已经创建的线程数小于最大线程数,那么线程池会再创建新的线程执行任务;
- 线程活动保持时间keepAliveTime:线程池的工作线程空闲后保持存活的时间,超过该时间线程会被自动销毁;
- 线程活动保持时间的单位TimeUnit:可选单位有天、小时、分钟等;
- ThreadFactory:创建线程的工厂;
- 拒绝策略RejectedExecutionHandler:当队列和线程池都满了,那么必须采取特定策略处理提交的新任务,策略有AbortPolicy(无法处理时抛异常)、CallerRunsPolicy(用调用者所在线程来运行任务)、DisCardOldestPolicy(丢弃队列里最近的一个任务,并执行当前任务)、DiscardPolicy(不处理,直接丢弃掉);
- 任务队列runnnalbeTAskQueue:用于保存等待执行的阻塞任务队列。
package multithread;
public class WorkThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" is running!");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" ends!");
}
}
ackage multithread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class WorkTest {
public static void main(String[] args) {
//5个固定大小线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
for(int i=0;i<10;i++) {
Runnable worker = new WorkThread();
//向线程池提交了10个任务
executor.execute(worker);
}
//平缓的关闭过程:不在接受新的任务,同时等待已经提交的任务执行完成
executor.shutdown();
//保证shuedowm方法
while(!executor.isTerminated()) {
}
System.out.println("Finish all threads");
}
}
运行截图:
线程安全
考虑这样一个问题:设计程序模拟电影院售票的情况。
package multithread;
public class MovieTicket extends Thread{
//定义成员变量
private static int tickets=10;
public void run() {
while(true) {
if(tickets>0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"正在出售:"+(tickets--)+"张票");
}
}
}
}
package multithread;
public class MovieTicketDemo {
public static void main(String[] args) {
//假设开了三个窗口卖票
MovieTicket t1 = new MovieTicket();
MovieTicket t2 = new MovieTicket();
MovieTicket t3 = new MovieTicket();
t1.setName("window1");
t2.setName("window2");
t3.setName("window3");
t1.start();
t2.start();
t3.start();
}
}
此时运行结果如下:
我们可以发现出现了不正确的情况:不同的窗口售卖了相同的票,这是因为多个线程并发,同时对共享数据进行了操作,出现了线程安全问题。
怎么解决线程安全问题?
利用线程同步机制解决,任意时刻只能有一个线程在执行。
//同步代码语法
synchronized(对象){
//线程同步代码块
}
public class MovieTicket extends Thread{
//定义成员变量
private static int tickets=10;
public void run() {
while(true) {
synchronized(MovieTicket.class) {
if(tickets>0) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"正在出售:"+(tickets--)+"张票");
}
}
}
}
}