先来了解一下进程与线程
进程是计算机调度资源的最小单位
线程是进程调度资源的最小单位 一个进程至少有一个线程
java中创建线程的四种方式
1、继承Thread类,重写其中的run()方法,run方法中就是要执行的任务
优点:就是简单直接
缺点:就是一个类不能继承多个类,耦合性较高 首先创建一个MyTHread1类,这个类继承自Thread类,然后重写Thread类中的run方法,在run方法中输出100次线程的名字和你好
class MyThread1 extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println("你好");
}
}
}
在主函数中创建MyThread1对象,调用start()方法启动线程
Thread thread1 = new MyThread1();
thread1.start();
2、实现Runnable接口,重写其中的run方法,run中就是要执行的任务
优点:将任务与其他任务进行分割,降低了耦合性
首先创建Mythread2类,实现Runnable接口,并重写run方法,在run实现输出100次hello
world
class MyThread2 implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println("hello world");
}
}
}
在主函数中创建MyThread2的对象,因为这里使用的是实现Runnable接口,所以需要先创建新线程,再调用start方法启动线程
Runnable thread2 = new MyThread2();
new Thread(thread2).start();
注意:在这里如果直接在main函数中调用类中的run()方法的话,使用的是main线程,而不是自己创建的线程,这样的话,创建线程就没有意义了,所以需要使用start启动线程
接下来我们在同一个main方法中同时启动两个线程
Thread thread1 = new MyThread1();
/*
* start();启动线程
* 开辟一条新的线程任务
*/
thread1.start();
Runnable thread2 = new MyThread2();
new Thread(thread2).start();
/*
* 直接执行run方法是用的main线程执行
*/
//thread1.run();
我们会发现输出的时候会出现交叉的情况,也就是说线程1执行输出了一部分线程1的名字和你好,就会出现线程2执行输出的helloworld,出现这种情况的原因是线程1在使用cpu时间片执行一部分输出之后,cpu时间片用完了,轮到了线程2使用cpu时间片
下面是前两个方法的完整代码,
public class ThreadDemo1 {
public static void main(String[] args) {
Thread thread1 = new MyThread1();
/*
* start();启动线程
* 开辟一条新的线程任务
*/
thread1.start();
Runnable thread2 = new MyThread2();
new Thread(thread2).start();
//运行会出现交叉的原因是cpu时间片用完了
/*
* 直接执行run方法是用的main线程执行
*/
//thread1.run();
}
}
//创建线程1继承Thread类
class MyThread1 extends Thread{
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println("你好");
}
}
}
//创建线程2实现Runnable接口
class MyThread2 implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println("hello world");
}
}
}
3、实现Callable接口,重写call()方法,call中就是线程任务
首先创建MyCallable类实现Callable接口,重写call()方法,Callable接口支持泛型,为了不报警告,需要在实现的时候加上数据类型
sleep()方法是使线程进入休眠状态(也就是阻塞状态)一定时间,之后再进入就绪状态
class MyCallable implements Callable<String>{
@Override
public String call() {
System.out.println("你好");
return "任务执行结束";
}
}
前三种方法之间的区别是,继承Thread类和实现Runnable接口,重写的是run()方法,实现Callable接口重写的是call()方法,并且run方法没有返回值,call方法有返回值,所以当我们需要任务有返回值的时候就可以使用第三种方法
4、使用线程池创建线程
线程池的概念:存放空闲线程的集合
线程池的作用:提高了效率,减少了线程创建和关闭的时间
创建线程池使用的是Executors类,调用类中的方法即可创建线程池
线程池有四种创建方式
(1)、根据内存创建线程池:Executors.newCachedThreadPool()
优点:可以存储无限多条线程
缺点:太占内存了
首先根据内存创建线程池
ExecutorService pool = Executors.newCachedThreadPool();
调用execute方法给线程池一个任务:输出执行任务的线程的名字
这里创建了两个任务
exceute(command)方法中,参数里面就是要执行的任务,任务的创建可以使用匿名内部类的方法写进Runnable接口中的run方法中,也可以使用lambda表达式
public class ThreadPoolDemo01 {
public static void main(String[] args) {
//根据内存创建线程池,里面可以有无限多个线程
ExecutorService pool = Executors.newCachedThreadPool();
pool.execute(new Runnable() {
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
});
pool.execute(()->{
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
//关闭线程池
pool.shutdown();
}
}
输出结果
(2)创建固定大小的线程池:Executors.newFixedThreadPool(线程条数)
线程条数固定,当有多个任务进入线程池,如果没有空闲线程,后面进入的任务会等待前面的任务执行完毕之后,线程空闲下来才会执行
创建一个只有3条线程的线程池
ExecutorService pool = Executors.newFixedThreadPool(3);
然后给线程池10个任务,每隔100毫秒输出一次是哪个线程再执行任务,用完之后关闭线程池
for(int i=0;i<10;i++) {
pool.execute(()->{
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+":执行任务");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//关闭线程池
pool.shutdown();
}
结果:
(3)第三种方法创建的也是固定大小的线程池,但是这个线程池支持延迟及周期性任务:Executors.newScheduledThreadPool(线程条数)
创建一个只有三条线程的线程池
ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
注意这里返回值类型要选择ScheduledExecutorService
创建延迟性线程任务使用的是schedule(command, delay, unit)方法
command:线程任务,该任务是Callable中的call方法任务;
delay:延迟时间,也就是在指定时间之后任务开始执行;
unit:时间单位,给定延迟时间的单位,使用的是TimeUnit类中的属性
pool.schedule(()->{
//输出执行任务的线程名字
System.out.println(Thread.currentThread().getName());
}, 1, TimeUnit.SECONDS);
创建周期性任务使用的是scheduleAtFixedRate(command, initialDelay,
period, unit)方法
command:线程任务
initialDelay:延迟时间(多久之后执行第一个任务)
period:周期时间(每隔多久执行一次任务)
unit:时间单位
pool.scheduleAtFixedRate(()->{
System.out.println(Thread.currentThread().getName());
}, 1, 3, TimeUnit.SECONDS);
注意:周期性任务不能关闭线程池,不然没有效果
(4)创建单线程的线程池:Executors.newSingleThreadExecutor()
单线程线程池的特点就是执行的任务有先后顺序,先来先执行,后来后执行
public static void main(String[] args) {
//创建一条线程的线程池,该线程执行有先后顺序
ExecutorService pool = Executors.newSingleThreadExecutor();
for(int i=0;i<5;i++) {
pool.execute(()->{
try {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
//关闭线程池
pool.shutdown();
}