线程是操作系统能够进行运算调度的最小单元,每个进程有多个线程组成,每个线程都有一个执行任务即我们编写的一段代码。进程默认启动一个线程来执行main函数,这个线程一般称为主线程。操作系统运算调度指的是CPU的调度,操作系统启动以后需要管理成百上千个线程,而CPU的数量是有限的,所以需要调度这些线程使每个线程都有机会执行。
为什么使用线程
现代计算机的CPU基本都有多个内核,通过Java线程可以充分利用多核CPU的运算能力。比如我们可以创建多个线程,每个线程执行一个死循环,这样就能让CPU的使用率达到100%。还可以利用线程来简化系统设计,如GUI编程中可以使用一个线程来专门处理与用户的交互工作,使用另外一个线程专门进行复杂耗时的运算工作,这样设计使每个线程专注于一件事情,代码容易编写。如果把这两个工作放到一个线程中代码将非常难写,同时还有可能出现卡UI的现象,即未能及时响应用户的操作。
创建线程
下面是创建线程的示例代码:
package com.shaoshuidashi;
class Task implements Runnable {
public void run(){
while(true){
try {
System.out.println("im in Task thread");
Thread.sleep(1000);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
}
public class Explore {
public static void main(String[] args){
Thread trd = new Thread(new Task());
trd.start();
while(true){
try {
System.out.println("im in main thread");
Thread.sleep(1000);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
}
/*输出
im in main thread
im in Task thread
im in main thread
im in Task thread
......
*/
我们创建线程是为了执行一些特定的任务,通过实现Runnable接口的run方法,把需要线程执行的任务代码放到run方法中即可,上例中线程的任务是每间隔一秒打印“im in Task thread”。接下来创建Thread对象并把任务对象传递给它,这样线程已经创建好了,紧接着调用Thread对象的start方法启动线程,线程启动以后我们的任务就会被执行。子线程启动后,我们不想主线程退出,所以让主线程每间隔一秒打印“im in main thread”。
线程池
线程的创建和销毁是有一定开销的,频繁创建销毁线程会对应用程序的性能造成影响,对于需要频繁创建销毁线程的场景,我们可以把使用过的线程缓存起来,等下次需要线程时直接从缓存里面取线程,这样就免去了线程的创建和销毁开销。线程缓存的另外一个名字叫做线程池。下面是线程池的使用示例:
package com.shaoshuidashi;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class Task implements Runnable {
public void run(){
while(true){
try {
System.out.println(Thread.currentThread());
Thread.sleep(1000);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
}
public class Explore {
public static void main(String[] args){
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Task());
exec.execute(new Task());
exec.execute(new Task());
while(true){
try {
System.out.println(Thread.currentThread());
Thread.sleep(1000);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
}
/*输出
Thread[main,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-2,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-1,5,main]
Thread[pool-1-thread-3,5,main]
Thread[pool-1-thread-2,5,main]
Thread[main,5,main]
......
*/
Java使用ExecutorService来表示线程池,上例使用Executors的静态方法newCachedThreadPool创建了一个线程池,线程池有很多种类型,这里创建的是CachedThreadPool。通过ExecutorService对象的execute方法可以把任务传递给线程池,上例中我们传递了三个任务到线程池,任务每间隔1秒打印线程名字,从输出可以看出每一个任务都有不同的线程执行。
常用的线程池有:
- CachedThreadPool,动态创建线程,最多可以创建Integer.MAX_VALUE个线程。
- FixedThreadPool,创建固定数量的线程,数量有使用者指定。
- SingleThreadExecutor,只创建一个线程,所有任务排队执行。
创建示例如下:
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
后台线程
在C/C++中当主线程退出以后整个应用程序也将退出,但是Java和C/C++不同,主线程退出以后只要还有其它线程在执行,整个应用程序就不会退出。有一种方法可以达到主线程退出整个应用就退出的效果,那就是把非主线程设置为后台线程。后台线程的特点是:当所有的非后台线程结束时,程序会终止,同时会杀死进程中的所有非后台进程。
package com.shaoshuidashi;
class Task implements Runnable {
public void run() {
while(true){
try {
System.out.println(Thread.currentThread());
Thread.sleep(1000);
}catch (Exception e){
System.out.println(e.getMessage());
}
}
}
}
public class Explore {
public static void main(String[] args) throws InterruptedException{
Thread trd = new Thread(new Task());
trd.setDaemon(true);
trd.start();
Thread.sleep(10*1000);
}
}
调用线程对象的setDaemon方法可以设置线程为后台线程,上例程序运行10s后将退出。如果把代码trd.setDaemon(true)注释,应用程序会一直运行不会退出。
最后
多线程不仅可以充分利用CPU资源,合理的使用线程还可以使软件设计更简洁,线程的创建和销毁有一定的开销,使用线程池可以避免这些开销。
在实际开发中,线程之间往往需要共享数据,多个线程直接读写共享数据会产生很多意想不到的错误,所以需要对共享的数据进行保护,下篇文章将学习怎么在多个线程间共享数据。
![](https://i-blog.csdnimg.cn/blog_migrate/5c71d872cb5196a38481cfadf81da38a.jpeg)