多线程
多线程的定义
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程:
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程。
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
实例来看Windows任务管理器中的多个进程。
每个进程的内存空间是独立的,换句话说就是拥有独立内存空间的应用程序。线程则是他们的执行路径。所谓的多线程就是在同一个时间段,执行多个进程。
线程调度
Windows一共有八个线程,八个脑子,但是电脑有很多的应用程序,需要同时工作,不可能八个处理器只处理八个。所以就需要线程的调度。在同一很小的时间段通过高速的切换来对很多的线程进行处理。这样对于人而言就好像是同时处理这些线程。(换句话说一年一个人做了很多事情,这里的人的一年,可能就相当于CPU的一秒甚至更短,由此可见CPU运行的速度之快。)
下面就是各种线程的调度方式:
分时调度:
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度:(Windows采用)
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
同步与异步(线程安全与不安全)
同步:排队执行 , 效率低但是安全.
异步:同时执行 , 效率高但是数据不安全.
大个比方一大堆人在吃火锅,同步就是排队一个人吃一筷子,一个一个吃。异步就是大家一块夹肉吃。很明显异步很快,但是容易出现2个人夹一块肉的情况,这样就是程序中的bug,虽然效率高,但是不安全。
并发与并行
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
多线程技术的具体实现
继承Thread
创造一个继承Thread类的一个类,并重写其中的run方法,将要执行的代码写入run方法当中。
在主类中创建该类的对象,并且使用start方法调用run方法。
package Test;
public class Demo1 {
public static void main(String[] args) {
MyThread m = new MyThread();
m.start();
for(int i = 0;i<10;i++) {
System.out.println("b:"+i);
}
}
}
package Test;
public class MyThread extends Thread{
public void run() {
for(int i = 0;i<10;i++) {
System.out.println("a:"+i);
}
}
}
由此可见,mian方法的线程和其中子线程的m.start线程是同时运行的。
抢占式运行,每次的结果可能都不一样。
两个线程都有自己的内存空间。
实现接口Runnable
和Thread一样,要实现run方法,在run里面写任务。这个run里面的是给线程执行的一个任务。需要运行他,则需要再创建一个线程,再调用这个任务,让其执行。就是说还需要Thread但是不需要重写了。
package Test;
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0;i<10;i++) {
System.out.println("a:"+i);
}
}
}
package Test;
public class Demo2 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
for(int i = 0;i<10;i++) {
System.out.println("b:"+i);
}
}
}
先创建一个任务,MyRunnable r,在创建一个执行任务的线程,执行任务的人Thread t。这样分开的好处(相比于继承Thread):
1.创建任务给线程分配的方式,实现多线程,适合多个线程实现多个任务的方式这样一种情况。
2.解决单继承的局限性,继承只能继承一个父类,而接口可以实现很多个接口。
3.分开处理有面向对象的思想,强化了程序的健壮性,
4.后面的线程池技术,只接受Runnable任务,不接受Thread的线程。
综上实现多线程的时候多用于Runnable
另外使用匿名内部类可以实现线程。如下:
package Test;
public class Demo3 {
public static void main(String[] args) {
new Thread() {
public void run() {
for(int i = 0;i<10;i++) {
System.out.println("a:"+i);
}
}
}.start();
for(int i = 0;i<10;i++) {
System.out.println("b:"+i);
}
}
}
Thread类
常用构造方法:
可以传入任务目标和,线程名称。
getId()返回县城标识符。
getName()返回线程名称。
getPriority() 返回线程优先级。
setPriority(int newPriority)设置线程优先级。
其中stop方法已经不用了,现在使用通知线程,让其自己终结自己。合理的释放资源。
其中sleep()是最常用的,其中传参是毫秒,让线程停止一段时间,可以让他每个1秒传出一个数据。
setDaemon(boolean on)设置线程是否为守护线程。
其中一个程序死亡的条件是所有用户线程全部结束,而守护线程不是,所有用户线程死亡,守护线程全部死亡,守护线程守护公主一样,即人在塔在。
获取线程名称:
Thread.currentThread().getName();
currentThread()返回当前线程。
getName()返回当前线程名称。
package Test;
public class MyThread extends Thread{
public void run() {
for(int i = 0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
package Test;
public class Demo1 {
public static void main(String[] args) {
MyThread m = new MyThread();
m.start();
for(int i = 0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
sleep()
package Test;
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
//MyRunnable r = new MyRunnable();
//Thread t = new Thread(r);
//t.start();
for(int i = 0;i<10;i++) {
Thread.sleep(1000);
System.out.println("b:"+i);
}
}
}
线程阻塞
所有消耗时间的操作,比方说接受用户输入,读取文件这些耗时操作。
比方说sleep(),wait(),这些方法都有线程阻塞的产生。
线程中断
线程就像是一个人的人生,他是否该结束,死亡,关闭,应该由他自己来决定。之前的stop强行结束就会造成资源无法释放等不好的结果。所以stop方法被淘汰,现在采用给线程打标记的方法,如果线程自己检测到了相应得到标记就会自己中断,并且释放资源。在内部决定自己怎样结束。
package Test;
public class Demo2 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
for(int i = 0;i<5;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
t.interrupt();
}
}
package Test;
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//e.printStackTrace();
System.out.println("发现终端异常,返回结束线程!");
return;
}
}
}
}
添加t.interrupt();今日catch块,是否死亡或者其他操作,由我们来决定。
守护线程
线程分为守护线程和用户线程。
用户线程:当一个进程不包括任何存活的用户线程时,进程结束。
守护线程:当最后一个用户线程死亡时,守护线程死亡。
一般创建都是用户线程。
要在start开始前设置。
t.setDaemon(true);
package Test;
public class Demo2 {
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.setDaemon(true);
t.start();
for(int i = 0;i<5;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//t.interrupt();
}
}
package Test;
public class MyRunnable implements Runnable{
@Override
public void run() {
for(int i = 0;i<10;i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//System.out.println("发现终端异常,返回结束线程!");
//return;
}
}
}
}
看。当mian方法结束的时候,守护线程t也停止了。
线程安全
代码演示:
package Test;
public class Demo4 {
public static void main(String[] args) {
Runnable r = new Tickets();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
static class Tickets implements Runnable{
private int count = 10;//初始票数
@Override
public void run() {
while(count>0) {//开始卖票
System.out.println("正在准备卖票。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
count--;
System.out.println("出票成功,剩余票数:"+count);
}
}
}
}
可以看到这里出现了问题,剩余票数出现了负数。
这是因为采用的抢占式,当A线程运行到sleep休眠的时候,其他线程抢占时间片,改变了count,但是A线程的count已经存储了,这样就会出现问题,这就是线程的不安全问题,判断和使用的数据不一样,不符合预期。解决方法也很简单,只要你在运行响应代码的时候,其他代码就不准抢。换句话说就是排队执行,等一个线程任务完全运行完了,才能允许其他线程能够运行。接下来将会演示三种解决方案:
同步代码块
关键字:synchronized
格式:synchronized(锁对象){
}
任何对象都可以作为锁对象。将对象传入synchronized中,就会被打上锁标记,知道代码块结束的时候,解除锁标记,就会完成。其他的线程就可以开始抢占。注意:100个线程看一把锁才能够起作用。参数都是一样的。
package Test;
public class Demo4 {
public static void main(String[] args) {
Runnable r = new Tickets();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
static class Tickets implements Runnable{
private int count = 10;//初始票数
private Object o = new Object();
@Override
public void run() {
while(true) {//开始卖票
synchronized(o) {
if(count>0) {
System.out.println(Thread.currentThread().getName()+"正在准备卖票。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,剩余票数:"+count);
}
else {
break;
}
}
}
}
}
}
这里是可以看到没有再出现负数的情况,这里全部都是Thread-0是因为这个线程离锁最近,所以抢到的几率非常之大。
像这种情况是比较少见的。
注意这里声明的锁Object o声明不能再方法里面,一定要在外面公共的,各个进程一定要看同一把锁,在座的各位应该都懂。
同步方法
相比于同步代码块,同步方法是将synchronized修饰到了方法上面而不是一个代码上面。演示如下:
package Test;
public class Demo4 {
public static void main(String[] args) {
Runnable r = new Tickets();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
static class Tickets implements Runnable{
private int count = 10;//初始票数
@Override
public void run() {
while(true) {//开始卖票
if(sale()) {
}
else {
break;
}
}
}
synchronized public boolean sale() {
if(count>0) {
System.out.println(Thread.currentThread().getName()+"正在准备卖票。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,剩余票数:"+count);
}
else {
return false;
}
return true;
}
}
}
效果都是一样的,但很明显这样是对方法进行了修饰,方法就是在方法之前加上synchronized,十分的简单。这里的锁就是r这个对象,即new Thread®.start();中的r。
显式锁
同步代码块,同步方法都是隐式锁。十分方便,展示如下:
package Test;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo4 {
public static void main(String[] args) {
Runnable r = new Tickets();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
static class Tickets implements Runnable{
private int count = 10;//初始票数
Lock l = new ReentrantLock();
@Override
public void run() {
while(true) {//开始卖票
l.lock();
if(count>0) {
System.out.println(Thread.currentThread().getName()+"正在准备卖票。");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,剩余票数:"+count);
}
else {
break;
}
l.unlock();
}
}
}
}
显示锁相比于隐式锁,我们能够自己开锁,锁住,能够更加方便的操作。操作方式就是Lock声明对象,然后lock方法锁住,unlock解锁。
显式锁和隐式锁的区别
一.出身不同
从sync和lock的出身(原始的构成)来看看两者的不同。
Sync:Java中的关键字,是由JVM来维护的。是JVM层面的锁。
Lock:是JDK5以后才出现的具体的类。使用lock是调用对应的API。是API层面的锁
sync是底层是通过monitorenter进行加锁(底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖于monitor对象的。只有在同步块或者是同步方法中才可以调用wait/notify等方法的。因为只有在同步块或者是同步方法中,JVM才会调用monitory对象的);通过monitorexit来退出锁的。
而lock是通过调用对应的API方法来获取锁和释放锁的。
二.使用方式不同
Sync是隐式锁。Lock是显示锁
所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁的操作。
我们大家都知道,在使用sync关键字的时候,我们使用者根本不用写其他的代码,然后程序就能够获取锁和释放锁了。那是因为当sync代码块执行完成之后,系统会自动的让程序释放占用的锁。Sync是由系统维护的,如果非逻辑问题的话话,是不会出现死锁的。
在使用lock的时候,我们使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。手动获取锁方法:lock.lock()。释放锁:unlock方法。需要配合tyr/finaly语句块来完成。
三.等待是否可中断
Sync是不可中断的。除非抛出异常或者正常运行完成
Lock可以中断的。中断方式:
1:调用设置超时方法tryLock(long timeout ,timeUnit unit)
2:调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断
生活中小case来理解这一区别:官二代一般不会做饭。都会去餐厅点餐等待着餐厅出餐。普通人的你既可以去餐厅等待,如果等待时间长的话,你就可以回去自己做饭了。
四.加锁的时候是否可以公平
Sync;非公平锁
lock:两者都可以的。默认是非公平锁。在其构造方法的时候可以传入Boolean值。
true:公平锁
false:非公平锁
生活中小case来理解这个区别:官二代一般都不排队,喜欢插队的。普通人的你虽然也喜欢插队。但是如果遇到让排队的情况下,你还是会排队的。
五.锁绑定多个条件来condition
Sync:没有。要么随机唤醒一个线程;要么是唤醒所有等待的线程。
Lock:用来实现分组唤醒需要唤醒的线程,可以精确的唤醒,而不是像sync那样,不能精确唤醒线程。
公平锁和非公平锁
Lock l = new ReentrantLock(true);
其中参数填写true就是公平锁。
公平锁:表示线程获取锁的顺序是按照加锁的顺序来分配的,及先来先得,先进先出的顺序。
非公平锁:表示获取锁的抢占机制,是随机获取锁的,和公平锁不一样的就是先来的不一定能拿到锁,
有可能一直拿不到锁,所以结果不公平
死锁
多线程以及多进程改善了系统资源的利用率并提高了系统 的处理能力。然而,并发执行也带来了新的问题——死锁。所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
例子如下:
package Test;
public class Demo5 {
public static void main(String[] args) {
Cul c = new Cul();
Police p = new Police();
new MyThread(c,p).start();
c.say(p);
}
static class MyThread extends Thread{
private Cul c;
private Police p;
public MyThread(Cul c,Police p) {
this.c = c;
this.p = p;
}
public void run() {
p.say(c);
}
}
static class Cul{
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(Cul c) {
System.out.println("警察:你放了人质,我就放了你!");
c.fun();
}
public synchronized void fun() {
System.out.println("警察营救了人质,罪犯跑球了。");
}
}
}
这段代码就是一个死锁的情况,警察与罪犯互相僵持不下,一个希望先逃走,一个希望营救人质。
如何避免死锁的产生:当一个方法有锁的情况下不要调用其他锁的方法。
Tip:人民警察从不和罪犯谈条件,如果有,那一定是缓兵之计!
多线程通信
如果一个线程下载,一个线程进行使用,当下载完成的时候需要通知其他线程进行操作,这个时候就需要多线程之间的通信。具体的一些方法如下:
wait()使线程进入等待,可以传入参数,定义时间,时间结束或者中断,或者唤醒(notify(),notifyAll())。
具体例子:
生产者与消费者
package Test;
public class Demo6 {
public static void main(String[] args) {
//多线程通信 生产者与消费者问题
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
//厨师
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if(i%2==0){
f.setNameAndTaste("老干妈小米粥","香辣味");
}else {
f.setNameAndTaste("煎饼果子","甜辣味");
}
}
}
}
//服务员
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food{
private String name;
private String taste;
//true表示可以生产
boolean flag = true;
public synchronized void setNameAndTaste(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 get(){
if(!flag){
System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);
flag = true;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
如果只是存在线程安全是没法解决这个问题的,会出现厨师一直做饭,服务生一直端菜的情况,所以需要用到多线程通信的功能。
生产者消费者模式是并发、多线程编程中经典的设计模式。
生产者:Cook
消费者:Waiter
盒子:Food
当厨师执行setNameAndTaste方法的时候,执行完毕,先使用notifyAll全部唤醒,再将自己休眠。这个时候服务生的get方法就被唤醒,开始端菜,同理,端完菜使用notifyAll全部唤醒,再将自己休眠,如此反复完成循环运作。
线程六种状态
初始状态
实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。
就绪状态
就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
调用线程的start()方法,此线程进入就绪状态。
当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
锁池里的线程拿到对象锁后,进入就绪状态。
运行中状态
线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
阻塞状态
阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
等待
处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
超时等待
处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
终止状态
当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
等待队列
调用obj的wait(), notify()方法前,必须获得obj锁,也就是必须写在synchronized(obj) 代码段内。
Callable
好处:可以和之前的一样,能够并行执行,也可以等其他的线程完成之后再执行,也就是有了主次之分。
Callable使用步骤:
//1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
//2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
// 3. 通过Thread,启动线程
new Thread(future).start();
线程池
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程 就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的好处
·降低资源消耗。
·提高响应速度。
·提高线程的可管理性。
Java中的四种线程池 . ExecutorService
缓存线程池:优先重用空闲的线程池。可以无限放大,线程数可以随意增加,比较适合处理执行时间比较小的任务。
如果每个线程执行时间较长,则容易因线程数过多而导致系统性能急剧下降。
定长线程池:newFixedThreadPool创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
单线程线程池:和定长一样,只不过指定长度为1.
周期性任务定长线程池:顾名思义,可以设定周期,延迟时常,定长。
package Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo7 {
/*单线程线程池
执行流程
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()+"锄禾日当午");
}
});
}
}
package Test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class Demo7 {
/*周期任务 定长线程池
执行流程
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
周期性任务执行时
定时执行 当某个任务触发时 自动执行某任务
**/
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
//定时执行一次
//参数1:定时执行的任务
//参数2:时长数字
//参数3:2的时间单位 Timeunit的常量指定
/* scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},5, TimeUnit.SECONDS); //5秒钟后执行*/
/*
周期性执行任务
参数1:任务
参数2:延迟时长数字(第一次在执行上面时间以后)
参数3:周期时长数字(没隔多久执行一次)
参数4:时长数字的单位
* **/
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},5,1,TimeUnit.SECONDS);
}
}
package thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo13 {
public static void main(String[] args) {
//线程池
//创建线程
//创建任务
//执行任务
//关闭线程
//缓存线程池
//无限制长度
//任务加入后的执行流程
//1判断线程池是否存在空闲线程 2存在则使用 3不存在则创建线程并使用
//向线程池中加入新的任务
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()+"锄禾日当午");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}