目录
1.创建一个多线程程序
在没有接触线程之前,我们的程序都是通过 main ( ) 方法,也就是主方法来运行。主方法其实就是一个线程,被称为主线程,写在主方法里的代码都会从上往下依执行。如果现在有两个方法:方法 A 与方法 B,我们想让这两个方法同时运行,也就是并发执行,又该如何用代码来实现呢?Java 为我们提供了一个专门的多线程类 Thread,通过这个类我们就能实现多个线程同时执行。
public static void main(String[] args) {
//使用匿名内部类的写法实例化一个Thread对象
new Thread() {
@Override
public void run() {
A();
}
}.start();//线程开始执行
new Thread() {
@Override
public void run() {
B();
}
}.start();
}
public static void A() {
int count = 1;
while (count<=100){
System.out.println("A方法打印第"+count+"次");
count++;
}
}
public static void B() {
int count = 1;
while (count<=100){
System.out.println("B方法打印第"+count+"次");
count++;
}
}
A方法打印第53次
A方法打印第54次
A方法打印第55次
A方法打印第56次
A方法打印第57次
B方法打印第18次
B方法打印第19次
B方法打印第20次
B方法打印第21次
B方法打印第22次
B方法打印第23次
A方法打印第58次
A方法打印第59次
A方法打印第60次
A方法打印第61次
A方法打印第62次
A方法打印第63次
A方法打印第64次
A方法打印第65次
B方法打印第24次
B方法打印第25次
我们截取了一部分的运行结果,可以很明显的看到:在 A 打印完第 57 次时停止打印并让 B 开始打印,当 B 打印了几次后又开始让 A 打印,A 和 B 的打印是随机的,这就实现了多线程并发执行。
2.多线程的内存图解
如图所示,当在栈 1 中的主线程运行到 start ( ) 方法时,就会在内存中重新开辟一个栈空间,把 start ( ) 方法对应的 run ( ) 方法调入新的栈空间执行。此时第一个 run ( ) 已经脱离主线程,CPU 此时就可以对栈空间进行选择。假设 CPU 仍选择栈 1 ,那就会执行第二个 start ( ) 方法,又开辟了一个新的栈空间供对应的 run ( ) 方法执行。main ( ) 执行完第二个 start ( ) 后结束线程销毁栈 1 ,CPU 继续选择栈 2 或栈 3 执行。
3.Thread 的常用方法
static Thread | currentThread() 返回对当前正在执行的线程对象的引用。 |
boolean | isInterrupted() 测试线程是否已经中断。 |
long | getId() 返回该线程的标识符。 |
String | getName() 返回该线程的名称。 |
int | getPriority() 返回线程的优先级。 |
Thread.State |
|
ThreadGroup | getThreadGroup() 返回该线程所属的线程组。 |
void | join() 等待该线程终止。 |
void | join(long millis) 等待该线程终止的时间最长为 millis 毫秒。 |
void | run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。 |
void | |
void | setPriority(int newPriority) 更改线程的优先级。 |
static void | sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
void | start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 |
String | toString() 返回该线程的字符串表示形式,包括线程名称、优先级和线程组。 |
static void | yield() 暂停当前正在执行的线程对象,并执行其他线程。 |
void | setDaemon(boolean on) 将该线程标记为守护线程或用户线程。 |
void | interrupt() 中断线程。 |
static boolean | interrupted() 测试当前线程是否已经中断。 |
boolean | isAlive() 测试线程是否处于活动状态。 |
boolean | isDaemon() 测试该线程是否为守护线程。 |
下面通过一个案例来使用这些方法:
public static void main(String[] args) throws InterruptedException {
//------------主线程开始-------------
System.out.println("主线程开始了");
//获取此时正在运行的线程
Thread main = Thread.currentThread();
//获取线程标识符、线程名、线程优先级、线程状态
System.out.println();
System.out.println("线程ID:"+main.getId());
System.out.println("线程名:"+main.getName());
System.out.println("线程优先级:"+main.getPriority());
System.out.println("线程状态:"+main.getState());
//设置线程信息
System.out.println("设置主线程信息");
main.setName("主线程");//线程名
main.setPriority(10);//优先级
//再次打印信息
System.out.println();
System.out.println("线程ID:"+main.getId());
System.out.println("线程名:"+main.getName());
System.out.println("线程优先级:"+main.getPriority());
//创建一个名为"新线程",优先级为2的线程
Thread thread = new Thread("新线程"){
@Override
public void run() {
Thread thread1 = Thread.currentThread();
System.out.println("新线程开始运行了");
System.out.println("新线程开始休眠"+new Date());
try {
//新线程休眠10秒
thread1.sleep(10000);
} catch (InterruptedException e) {
}
System.out.println("新线程结束休眠"+new Date());
System.out.println("新线程的线程组:"+thread1.getThreadGroup());
System.out.println("新线程结束了");
}
};
System.out.println();
System.out.println("新线程被创建了");
thread.setPriority(2);
//设置为用户线程
thread.setDaemon(true);
//开启线程
thread.start();
//判断新线程状态
System.out.println();
System.out.println("线程ID:"+thread.getId());
System.out.println("线程名:"+thread.getName());
System.out.println("线程优先级:"+thread.getPriority());
System.out.println("是否活跃:"+thread.isAlive());
System.out.println("是否是用户级线程:"+thread.isDaemon());
System.out.println("线程是否被中断:"+thread.isInterrupted());
//主线程等待新线程执行完
System.out.println();
System.out.println("主线程等待新线程结束");
thread.join();
System.out.println();
System.out.println("主线程结束了");
}
------------------执行结果------------------
主线程开始了
线程ID:1
线程名:main
线程优先级:5
线程状态:RUNNABLE
设置主线程信息
线程ID:1
线程名:主线程
线程优先级:10
新线程被创建了
线程ID:12
线程名:新线程
线程优先级:2
是否活跃:true
是否是用户级线程:true
新线程开始运行了
线程是否被中断:false
主线程等待新线程结束
新线程开始休眠Tue Feb 11 20:29:59 CST 2020
新线程结束休眠Tue Feb 11 20:30:09 CST 2020
新线程的线程组:java.lang.ThreadGroup[name=main,maxpri=10]
新线程结束了
主线程结束了
4.使用 Runable 接口创建线程
Thread 类是 Runable 接口的实现类,实现了 Runable 接口的 run ( ) 方法。除了上面所提到的通过实例化 Thread 类或者其子类来创建线程以外,也可以在 Thread 类的构造方法中传入一个 Runable 的实现类来创建多个相同动作的线程。
public static void main(String[] args) {
//使用匿名内部类创建Runable实例化对象
Runnable runnable = new Runnable() {
@Override
public void run() {
//规定线程动作,
//所有以此实现类为参数构造的Thread对象都执行此run()方法
System.out.println(Thread.currentThread().getName()
+ "执行了Runable的run()方法。。。");
}
};
//创建多个相同动作的线程
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
Thread thread3 = new Thread(runnable);
thread1.start();
thread2.start();
thread3.start();
System.out.println(thread1.getId() + " " + thread1.getName());
System.out.println(thread2.getId() + " " + thread2.getName());
System.out.println(thread3.getId() + " " + thread3.getName());
}
-----------执行结果------------
12 Thread-0
Thread-1执行了Runable的run()方法。。。
Thread-0执行了Runable的run()方法。。。
Thread-2执行了Runable的run()方法。。。
13 Thread-1
14 Thread-2
注意:
① Runable 实现类中所声明的属性和方法,由所有通过该实现类创建的线程所共享。
② 避免了继承 Thread 类的单继承的局限性。类只能单继承,继承了 Thread 类就不能继承其他的类。而实现了 Runnable 接口,还可以继承其他的类,实现其他的接口。
③ Runnable 接口把设置线程任务和开启新线程进行了分离,增强了程序的可拓展性。
④ Runnable 接口实现时将同类型线程统一封装,更符合面向对象思想。
5.线程池简介
上面所创建的这两种线程在运行结束后都会被虚拟机销毁,如果任务数量多的话,频繁的创建和销毁线程会浪费大量时间,降低任务效率,创建过多的线程也会使内存开销吃紧。那么有没有一种方法能很好的解决这种问题呢?答案就是线程池。线程池可以让任务线程运行完成任务后不用立即销毁,而是让线程重复使用,节省了大量时间。
事实上,线程池运用的思想就是 “以空间换时间” ,线程池就是指定容量的线程的集合,通常采用 LinkedList 作为线程池的容器。需要线程时,使用 removeFirst 从线程池中取出一个线程,用完再使用 addLast 归还线程,当线程池中线程不够用时其他线程就必须等待归还线程。在 JDK 1.5 中加入了线程池类 Executors 供我们直接创建一个性能稳定的线程池,以下是线程池的创建与操作代码实现:
public static void main(String[] args) {
//创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程
ExecutorService executors = Executors.newFixedThreadPool(2);
//定义线程任务
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行了");
}
};
//提交多个 Runnable 任务用于执行
executors.submit(runnable);
executors.submit(runnable);
executors.submit(runnable);
executors.submit(runnable);
executors.submit(runnable);
}
执行结果:
pool-1-thread-1执行了
pool-1-thread-2执行了
pool-1-thread-1执行了
pool-1-thread-2执行了
pool-1-thread-1执行了
因为在创建线程池时只给了两个线程,因此可以看到这些线程任务均是由这两个线程来执行的。而且这个线程池是一直存在的,不会被自动回收,只有在执行 shutdown ( ) 方法之后才会回收此线程池。
其实 Executors 提供了不止一种线程池,在创建线程池时可以根据不同的需求创建不同类型的线程池。以下是 Executors 为我们提供的线程池:
static ExecutorService | newCachedThreadPool() 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。 |
static ExecutorService | newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。 |
static ExecutorService |
|
static ScheduledExecutorService | newScheduledThreadPool(int corePoolSize) 创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。 |
static ScheduledExecutorService | newSingleThreadScheduledExecutor() 创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。 |
线程池的优点:
① 服务器的线程数量是有限的,使用线程池使得每个工作线程都可以重复利用,有效的减少了因过多的创建和销毁线程所带来的时间和空间(内存)压力,省时省资源。
② 可以根据系统的承受能力,调整线程池中工作线程的数量,提高服务器工作效率,降低因为消耗过多内存导致服务器崩溃的风险。