引言
记录了多线程概念、继承Thread、实现Runnable、线程休眠、线程安全问题及解决方法、多线程通信问题、线程池。
多线程技术概述
线程与进程
进程
- 是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
线程
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。一个进程最少有一个线程。
- 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
线程调度
分时调度
- 所有线程轮流使用CUP的使用权,平均分配每个线程占用CPU的时间。
抢占式调度
- 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
抢占式调度
- CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核心而言,某个时刻,只能执行一个线程,而CPU的在多个线程间切换速度相对来说比我们感觉的要快,看上去就是在同一时刻运行。其实,多线程程序并不能提高程序的运行速度,但能提高程序的运行效率,让CPU的使用率更高。
同步与异步
同步
- 排队执行,效率低但是安全。
异步
- 同时执行,效率高但是不安全。
并发与并行
并发
- 两件或多件事在同一时间段内发生。
并行
- 两件或多件事在同一时刻发生。
多线程技术
继承Thread
代码示例:
先编写一个子线程类去继承Thread:
这里的代码 就是一条 新的执行路径
这个执行路径的触发方式,不是直接调用run()方法,而是通过Thread对象的start()来启动任务
public class Thread1 extends Thread {
//run()方法就是线程要执行的任务方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("***床前明月光***"+i);
}
}
}
测试类:
程序入口
public class ThreadTest1 {
public static void main(String[] args) {
Thread1 t = new Thread1();
t.start();
for (int i = 0; i < 10; i++) {
System.out.println("---疑是地上霜---"+i);
}
}
}
运行结果:
分析:
每次执行的结果都不一样,因为Java使用的是抢占式调度。两个线程执行时,执行是随机性的。
画图分析:
每个线程都拥有自己的栈空间,共用一份堆内存。
由一个线程所调用的方法,那么这个方法也会执行在这个线程里面。
这里还有一个更简单的方式继承Thread,它是使用匿名对象的方式实现:
public class RunnableTest2 {
//实现Runnable
public static void main(String[] args) {
//通过匿名对象的方式继承Thread,并执行线程
new Thread(){
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("春分十里不如你"+i);
}
}
}.start();
for (int i = 0; i < 5; i++) {
System.out.println("天山八月情似火"+i);
}
}
}
实现Runnable
代码示例:
编写一个任务类实现Runnable接口:
/**
* 用于给线程进行执行的任务
*/
public class Runnable2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("一二三四五"+i);
}
}
}
程序测试:
public class RunnableTest2 {
//实现Runnable
public static void main(String[] args) {
// 1. 创建一个任务对象
Runnable2 r = new Runnable2();
// 2. 创建一个线程,并为其分配一个任务
Thread t = new Thread(r);
// 3. 执行线程
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("上山打老虎"+i);
}
}
}
运行结果:
分析:
实现Runnable
与 继承Thread
相比有如下优势:
- 通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况。
- 可以避免单继承所带来的局限性。
- 任务与线程本身是分离的,提高了程序的健壮性。
- 更利于线程池技术,因为线程池接收
Runnable
类型的任务,不接收Thread
类型的线程。
Thread类
-
线程是程序中执行的线程。
-
Java虚拟机允许应用程序同时运行多个执行线程。
-
每个线程都有优先权。 具有较高优先级的线程优先于具有较低优先级的线程执行。
-
有两种方法可以创建新的执行线程。
- 一种是将类声明为
Thread
的子类。再创建这个类的对象,把对象分配到线程,最后执行线程。 - 另一种是通过在匿名对象内实现并执行线程。
优先级:
变量和类型 | 字段 | 描述 |
---|---|---|
static int | MAX_PRIORITY | 线程可以拥有的最大优先级。 |
static int | MIN_PRIORITY | 线程可以拥有的最低优先级。 |
static int | NORM_PRIORITY | 分配给线程的默认优先级。 |
常用的构造方法:
构造器 | 描述 |
---|---|
Thread() | 分配新的 Thread 对象 |
Thread(Runnable target) | 分配新的 Thread 对象 |
Thread(Runnable target,String name) | 分配新的 Thread 对象,并设置一个线程名称 |
Thread(String name) | 分配线程的名称 |
常用的方法:
变量和类型 | 方法 | 描述 |
---|---|---|
long | getId() | 返回此Thread的标识符。 |
String | getName() | 返回此线程的名称。 |
int | getPriority() | 返回此线程的优先级。 |
void | setPriority(int newPriority) | 更改此线程的优先级。 |
void | start() | 此线程开始执行; Java虚拟机调用此线程的run 方法。 |
static void | sleep(long millis) | 导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。 |
void | setDaemon(boolean on) | 将此线程标记为 daemon 线程或用户线程。 |
static Thread | currentThread() | 返回对当前正在执行的线程对象的引用。 |
设置和获取线程名称
public class SetGetThreadName {
public static void main(String[] args) {
/**
* 获取线程名称
* currentThread() 返回当前执行线程
* getName() 返回此线程的名称。
* getId() 返回此Thread的标识符。
* getPriority() 返回此线程的优先级。
*/
System.out.println(Thread.currentThread().getName());//main
System.out.println(Thread.currentThread().getId());//1
System.out.println(Thread.currentThread().getPriority());//5
/**
* 设置线程名称
* 方法一:
*/
new Thread(new MyRunnable(),"嘿嘿嘿").start();
/**
* 方法二:
*/
MyRunnable r = new MyRunnable();
Thread t2 = new Thread(r,"咕呱呱");
t2.start();
/**
* 方法三:
*/
Thread t3 = new Thread(new MyRunnable());
t3.setName("蔷薇");
t3.start();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println(Thread.currentThread().getId());
System.out.println(Thread.currentThread().getPriority());
}
}
}
线程休眠sleep
public class ThreadSleep {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
System.out.println("锄禾日当午"+i);
Thread.sleep(1000);//1000ms=1s
}
}
}
线程阻塞
线程阻塞不只是线程休眠。
线程是一条路径,比如说程序有100行代码,从第1行到100行这是一条完整的路径。如果在这程序中,有10行代码是读取某个文件,那么在读取文件的时候需要等1秒,这1秒钟就是阻塞。
简单来说,所有耗时间的操作都是阻塞。如:读取文件所需的时间、用户输入的时间等。
线程的中断
public class ThreadSleep {
public static void main(String[] args) throws InterruptedException {
//线程的中断
//一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定
Thread t = new Thread(new MyRunnable());
t.start();
for (int i = 0; i < 5; i++) {
System.out.println("锄禾日当午"+i);
Thread.sleep(1000);//1000ms=1s
}
//给t线程
t.interrupt();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 8; i++) {
System.out.println("汗滴禾下土"+i);
try {
Thread.sleep(1000);//1000ms=1s
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("发现标记");
//用return,可以让线程自杀
return;
}
}
}
}
}
守护线程
线程分为:守护线程 和 用户线程
用户线程:当一个进程不包含任何的存活的用户线程时,进行结束。
守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
(不做标记时,都默认为用户线程)
void | setDaemon(boolean on) | 将此线程标记为 守护线程或用户线程。 |
---|
public class ThreadSetDeamon {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new MyRunnable());
//设置t为守护线程,输入true表示
t.setDaemon(true);
t.start();
for (int i = 1; i <= 5; i++) {
System.out.println("锄禾日当午"+i);
Thread.sleep(1000);//1000ms=1s
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 1; i < 10; i++) {
System.out.println("汗滴禾下土"+i);
try {
Thread.sleep(1000);//1000ms=1s
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程不安全问题
观察下面一段代码:
public class SafetyProblem {
public static void main(String[] args) {
Runnable run = new Ticket();//使用了多态创建对象
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
//卖车票
static class Ticket implements Runnable{
private int count = 10;//票数
@Override
public void run() {
while(count>0){
//卖票
System.out.println("正在出票...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,目前余票:"+count);
}
}
}
}
运行结果:
分析:
由于多个线程同时去操作一个数据count,在进入while循环判断和count–之间间隔了一个线程休眠,在这个间隔就会被其它线程插足执行,从而在while循环内数据被多次修改。可以看到在最后面票数出现了负数,显然是不合逻辑的,这种情况就是线程不安全问题。
那么这种问题如何解决呢?
下面就介绍了三种方法。
同步代码块、同步方法和显示锁Lock.
线程安全1-同步代码块
解决思路:在进入while循环时,不能让线程插足执行,每个线程按照排队执行。
public class SafetyProblem1 {
/**
* 线程同步:synchronized
* 解决方案一:同步代码块
* 格式: synchronized(锁对象){
* //锁对象:任何对象都可以作为锁对象
* }
* @param args
*/
public static void main(String[] args) {
Runnable run = new Ticket();//使用了多态创建对象
new Thread(run,"窗口一:").start();
new Thread(run,"窗口二:").start();
new Thread(run,"窗口三:").start();
}
//卖车票
static class Ticket implements Runnable{
private int count = 10;//票数
private Object o = new Object();
@Override
public void run() {
while(true){
//这里用Object做为锁对象
synchronized(o) {
if (count > 0) {
//卖票
System.out.println("正在出票...");
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,目前余票:" + count);
}else {
break;
}
}
}
}
}
}
运行截图:
分析:
在卖票的时候,三个线程必须用同一把锁,才能排队。如果不是同一把锁,各个线程各有各的锁那么就会出现之前线程不安全的问题。通过结果发现,票是都出完了,不管如何操作,都是10张票,所有说锁对象虽然能解决线程不安全的问题,但是执行效率却慢了。
线程安全2-同步方法
public class SafetyProblem2 {
/**
* 线程同步:synchronized
* 解决方案二:同步方法
* 格式(示例): public synchronized void 方法名(){}
* @param args
*/
public static void main(String[] args) {
Runnable run = new Ticket();//使用了多态创建对象
new Thread(run,"窗口一:").start();
new Thread(run,"窗口二:").start();
new Thread(run,"窗口三:").start();
}
//卖车票
static class Ticket implements Runnable{
private int count = 10;//票数
private Object o = new Object();
@Override
public void run() {
while(true){
boolean flag = sale();
if (!flag) {
break;
}
}
}
public synchronized boolean sale(){
if (count > 0) {
//卖票
System.out.println("正在出票...");
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,目前余票:" + count);
return true;
}
return false;
}
}
}
线程安全3-显式锁Lock
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SafetyProblem3 {
/**
* 同步代码块 和 同步方法 都属于隐式锁
* 解决方案三:显示锁Lock
* Lock 子类 ReentrantLock
* @param args
*/
public static void main(String[] args) {
Runnable run = new Ticket();//使用了多态创建对象
new Thread(run,"窗口一:").start();
new Thread(run,"窗口二:").start();
new Thread(run,"窗口三:").start();
}
//卖车票
static class Ticket implements Runnable{
private int count = 10;//票数
//显式锁 l
private Lock l = new ReentrantLock();
private Object o = new Object();
@Override
public void run() {
while(true){
//锁住
l.lock();
if (count > 0) {
//卖票
System.out.println("正在出票...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,目前余票:" + count);
}else {
break;
}
l.unlock();//解锁
}
}
}
}
分析:
显示锁比隐式锁好一些,更能体现锁的概念,更能体现我们程序员在开发程序中控制锁,可以自己创建锁,控制锁住,控制解锁。锁对象更能体现面向对象机制。
隐式锁和显示锁区别:
区别 | 隐式锁synchronized | 显示锁Lock |
---|---|---|
层面不同 | 是JVM层面的锁 | 是API层面的锁 |
使用方式不同 | 自动获取、释放锁 | 手动获取、释放锁 |
等待是否可中断 | 不可中断 | 可以中断 |
是否可设置公平锁 | 只能为非公平锁 | 两者都可以,默认为非公平锁 |
锁绑定多个条件来condition | 不能精确唤醒线程 | 可以精确唤醒线程 |
性能区别 | 性能低 | 性能高 |
公平锁与非公平锁
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
- 优点:所有的线程都能得到资源,不会饿死在队列中。
- 缺点:效率会下降,除了队列的第一线程,其它的线程都会造成阻塞,而CPU唤醒线程阻塞的开销会增加。
非公平锁:所有线程都去抢锁的第一个位置,它们会直接获取,如果得到,就直接获取到锁,如果获取不到,就再去队列等待下一次。
- 优点:可以减少CPU唤醒线程的开销整体的效率高,CPU不用唤醒所有线程。
- 缺点:可能会导致一些线程一直获取不到锁,或者长时间获取不到锁从而饿死在队列。
线程死锁
public class DealLock {
public static void main(String[] args) {
Culprit c = new Culprit();
Police p = new Police();
new MyThread(c,p).start();
c.say(p);
}
static class MyThread extends Thread{
private Culprit c;
private Police p;
public MyThread(Culprit c,Police p){
this.c = c;
this.p = p;
}
@Override
public void run() {
p.say(c);
}
}
static class Culprit{
public synchronized void say(Police p){
System.out.println("罪犯:你放了我,我放了人质");
p.fun();
}
public synchronized void fun(){
System.out.println("罪犯被抓了");
}
}
static class Police{
public synchronized void say(Culprit c){
System.out.println("警察:你放了人质,我放了你");
c.fun();
}
public synchronized void fun(){
System.out.println("警察救到了人质,并抓获罪犯");
}
}
}
运行结果有两种:1.线程死锁;2.正常运行。
线程死锁:
正常运行:
解决办法:
从根源上有可能产生锁的方法,不要再去调用有可能产生锁的方法。
多线程通信问题
生产者与消费者
public class Demo {
public static void main(String[] args) {
Food f = new Food();
new Chef(f).start();
new Waiter(f).start();
}
//厨师
static class Chef extends Thread{
private Food f;
public Chef(Food f) {
this.f = f;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if (i%2==0) {
f.setFood("油闷大虾", "香辣味");
}else {
f.setFood("凉拌皮蛋","清凉微辣");
}
}
}
}
//服务员
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.getFood();
}
}
}
//食物
static class Food{
private String name;//菜名
private String taste;//味道
//true 表示可以生产
private boolean flag = true;
public synchronized void setFood(String name, String taste){
if (flag) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
flag = false;
//唤醒所有线程
this.notifyAll();
//本线程中断,等待其它线程唤醒
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void getFood(){
// 如果 flag 为 false 就取食物
if (!flag) {
System.out.println("服务员端走的菜名是: " + name + ",味道是:" + taste);
flag = true;
//唤醒所有线程
this.notifyAll();
//本线程中断,等待其它线程唤醒
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行截图:
线程的六种状态
- 线程状态。线程可以处于以下状态之一:
NEW
尚未启动的线程处于此状态。RUNNABLE
在Java虚拟机中执行的线程处于此状态。BLOCKED
被阻塞等待监视器锁定的线程处于此状态。WAITING
无限期等待另一个线程执行特定操作的线程处于此状态。TIMED_WAITING
正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。TERMINATED
已退出的线程处于此状态。
通过画图理解线程的六种状态:
带返回值的线程Callable
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Callable1 {
/**
* 带返回值的线程Callable
* @param args
*/
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> c = new MyCallable();
FutureTask<Integer> task = new FutureTask(c);
new Thread(task).start();
//让Callable线程获取返回值后,才进行主线程的执行
Integer k = task.get();
System.out.println("返回值为:"+k);
for (int i = 0; i < 5; i++) {
System.out.println("西瓜:"+i);
}
}
//创建一个 MyCallable类 实现 Callable接口
static class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println("土豆:"+i);
}
return 100;
}
}
}
运行结果:
Runnable 与 Callable的区别:
- 相同点:
- 都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动线程
- 不同点
- Runnable没有返回值;Callable可以返回执行结果
- Callable接口的call()允许抛出异常;
- Runnable的run()不能抛出
线程池
线程池概念
线程池 Executors
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的优势
- 降低资源消耗
- 提高响应速度
- 提高线程的可管理性
Java中的四种线程池.ExecutorService
- 缓存线程池
- 定长线程池
- 单线程线程池
- 周期性任务定长线程池
缓存线程池
缓存线程池长度无限制。
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class ThreadPool01 {
/**
* 缓存线程池
* 任务加入后的执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程,并放入线程池,然后使用
*/
public static void main(String[] args) {
//创建缓存池对象
ExecutorService service = Executors.newCachedThreadPool();
//指挥线程池中执行新任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"土豆土豆");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"土豆土豆");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"土豆土豆");
}
});
//主线程休眠1s 后会选择线程池中空闲的随机一个
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"西瓜西瓜");
}
});
}
}
定长线程池
线程池长度是指定的数值。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool02 {
/**
* 定长线程池
* (长度是指定的)
* 任务加入后的执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并 放入线程池,然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(3);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"嘿嘿嘿");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"嘿嘿嘿");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"嘿嘿嘿");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"哈哈哈");
}
});
}
}
单线程线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPool03 {
/**
* 单线程线程池
* 执行流程:
* 1. 判断线程池是否为空
* 2. 空闲则使用该线程
* 3. 不空闲,则等待 线程池中的单个线程池空闲后使用
*/
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"床前明月光");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"床前明月光");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"床前明月光");
}
});
}
}
周期性任务定长线程池
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPool04 {
/**
* 周期性任务 定长线程池
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并 放入线程池,然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*
* 周期性任务执行时:
* 定时执行,当某个时机触发时,自动执行某任务
*/
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 类型一:定时执行一次
* 参数1. 定时执行的任务
* 参数2. 时长数字
* 参数3. 时长数字的时间单位,TimeUnit.常量指定
*/
System.out.println("在2秒后执行一次....");
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("孤寡孤寡");
System.out.println("3秒后周期性执行1秒");
}
},2, TimeUnit.SECONDS);
/**
* 类型二:周期性执行任务
* 参数1. 任务
* 参数2. 延迟时长数字(第一次执行在什么时间以后)
* 参数3. 周期时长数字(每个多久执行一次)
* 参数4。 时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},3,1,TimeUnit.SECONDS);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"汗滴禾下土");
}
},3,1,TimeUnit.SECONDS);
}
}
总结
对于多线程技术:
- 方便的通信和数据交换
- 更高效地利用CPU
- 使用线程可以把占据时间长的程序中的任务放到后台去处理 。
- 程序的运行效率加快 。
- 在一些等待的任务实现上如用户输入、文件读写等,线程就比较有用了。在这种情况下可以释放一些内存占用 。
- 如果有大量的线程,会影响性能 。
- 线程越多需要的内存空间也就越多 。
- 线程可能会给程序带来更多“bug”,因此要谨慎使用 。
- 线程的中止需要考虑其对程序运行的影响。
- 通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生 。