Java从入门到实战总结-3.4、Java多线程
文章目录
- Java从入门到实战总结-3.4、Java多线程
- 1、多线程技术概述
- (1)、线程与进程
- (2)、线程调度
- (3)、异步和同步
- (4)、并发和并行
- 2、继承Thread实现线程
- 3、实现Runnable接口实现线程
- 4、Thread类
- (1)、类方法
- (2)、设置和获取线程名称
- (3)、线程休眠sleep
- (4)、线程阻塞
- (5)、线程中断
- (6)、守护线程
- 5、线程安全问题
- (1)、线程安全问题情形
- (2)、隐式锁-同步代码块
- (3)、隐式锁-同步方法
- (4)、显式锁Lock
- (5)、公平锁和非公平锁
- 6、线程死锁
- 7、多线程通信问题(生产者和消费者)
- 8、线程的六种状态
- 9、实现Callable接口实现线程
- (1)、Runnable与Callable
- (2)、Callable使用步骤
- (3)、其它
- (4)、示例
- 10、线程池
- (1)、概述
- (2)、Java中的四种线程池
- (3)、示例
- 11、Lambda表达式
1、多线程技术概述
(1)、线程与进程
- 进程
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
- 线程
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程;
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
(2)、线程调度
- 分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
- 抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
(3)、异步和同步
同步:排队执行 , 效率低但是安全。
异步:同时执行 , 效率高但是数据不安全。
(4)、并发和并行
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
2、继承Thread实现线程
创建类继承Thread后重写run函数写该线程中执行的任务,然后调用run方法执行线程中的任务,由于Java是抢占式的线程调度,所以主进程和子线程中调用方法是抢占式的,执行顺序随机。
package com.xiaoyaoyou.day11;public class ThreadTest {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
for(int i = 0; i < 10; i++) {
System.out.println("主线程运行+"+i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
super.run();
//这里的代码,就是一条新的执行路径
//执行该路径的方式不是直接调用该run方法,而是通过调用start()方法来启动任务
for (int i = 0; i < 10; i++) {
System.out.println("子线程运行+"+i);
}
}
}
结果:
子线程运行+0子线程运行+1
子线程运行+2
子线程运行+3
子线程运行+4
子线程运行+5
子线程运行+6
主线程运行+0
主线程运行+1
主线程运行+2
子线程运行+7
主线程运行+3
主线程运行+4
主线程运行+5
主线程运行+6
主线程运行+7
主线程运行+8
主线程运行+9
子线程运行+8
子线程运行+9
每个线程都有自己的栈空间,共用一份堆内存;
3、实现Runnable接口实现线程
- 1、创建任务类继承Runnable接口实现run方法
- 2、创建任务对象
- 3、创建一个线程,并分配一个任务
- 4、执行这个线程
实现Runnable与继承Thread相比有如下优势:
- 1、通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
- 2、可以避免单继承所带来的局限性
- 3、任务与线程本身是分离的,提供了程序的健壮性
- 4、后续学习的线程池技术,接受Runnable类型的任务,不接收Thread类型的线程
我们更多使用Runnable创建任务分配线程执行这种方式来实现多线程,继承Thread的方式一般通过匿名内部类实现单个线程任务。
package com.xiaoyaoyou.day11;public class RunnableTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println("主线程+"+i);
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for(int i = 0; i < 10; i++) {
System.out.println("子线程+"+i);
}
}
}
结果:
子线程+0主线程+0
子线程+1
主线程+1
子线程+2
主线程+2
子线程+3
主线程+3
主线程+4
主线程+5
主线程+6
主线程+7
主线程+8
子线程+4
主线程+9
子线程+5
子线程+6
子线程+7
子线程+8
子线程+9
4、Thread类
當創建一個實現Runnable類,會從類中實例化線程的對象。線程定義了多個構造函數。我們將使用一個如下所示:
Thread(Runnable threadOb, String threadName);在這裡,threadOb是實現Runnable接口和新線程的名稱是由threadName指定一個類的實例。
創建新線程後,它不會啟動運行,直到調用它的start()方法,它是內線程聲明。start()方法如下所示:
void start( );(1)、类方法
线程方法:
以下是在线程类提供重要的方法列表。
序号
方法及描述
1个
public void start() 在单独的执行路径中启动线程,然后在此Thread对象上调用run()方法。
2个
public void run() 如果使用单独的Runnable目标实例化了此Thread对象,则在该Runnable对象上调用run()方法。
3
public final void setName(String name) 更改线程对象的名称。还有一个用于检索名称的getName()方法。
4
public final void setPriority(int priority) 设置此Thread对象的优先级。可能的值在1到10之间。
5
public final void setDaemon(boolean on) 参数true表示此线程为守护线程。
6
public final void join(long millisec) 当前线程在第二个线程上调用此方法,导致当前线程阻塞,直到第二个线程终止或经过指定的毫秒数。
7
public void interrupt() 中断此线程,如果由于某种原因而被阻止,则导致该线程继续执行。
8
public final boolean isAlive() 如果线程处于活动状态,则返回true,这是在线程启动之后但运行完成之前的任何时间。
以前的方法是在一个特定的线程对象调用。在线程类下面的方法都是静态的。调用静态方法执行内部正在运行的线程上运行。
序号
方法及描述
1个
public static void yield() 使当前正在运行的线程屈服于等待调度的具有相同优先级的任何其他线程。
2个
public static void sleep(long millisec) 使当前运行的线程阻塞至少指定的毫秒数。
3
public static boolean holdsLock(Object x) 如果当前线程保持在给定对象的锁,则返回true。
4
public static Thread currentThread() 返回对当前正在运行的线程的引用,该线程是调用此方法的线程。
5
public static void dumpStack() 打印当前正在运行的线程的堆栈跟踪,这在调试多线程应用程序时非常有用。
(2)、设置和获取线程名称
静态方法currentThread可以获取到当前线程的引用,然后通过setName方法设置线程名称,
package com.xiaoyaoyou.day11;public class ThreadNameTest {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName());
new Thread(new MyRunnable1(), "testThread").start();
new Thread(new MyRunnable1()).start();
new Thread(new MyRunnable1()).start();
}
}
class MyRunnable1 implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
结果:
maintestThread
Thread-0
Thread-1
(3)、线程休眠sleep
package com.xiaoyaoyou.day11;public class ThreadSleepTest {
public static void main(String[] args) {
for(int i = 0; i < 10; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果,1s打印一次:
01
2
3
4
5
6
7
8
9
(4)、线程阻塞
一个线程进入阻塞状态的原因可能如下:
- sleep()
sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象锁并没有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。 - wait()
调用wait()/1.5中的condition.await()使线程挂起,直到线程获取notify()/notifyAll()消息,(或者在Java SE5中java.util.concurrent类库中等价的signal()/signalAll()消息),线程才会进入就绪状态;
wait()调用会释放当前对象锁(monitor),这样其他线程可以继续进入对象的同步方法。参见上一篇文章线程间协作——wait & notify & notifyAll - 另外,调用join()也会导致线程阻塞,因为源码中join()就是通过wait()实现的;
- 等待I/O;
- 无法持有锁进入同步代码
进入同步代码前无法获取锁,比如试图调用synchronized方法,或者显示锁对象的上锁行为ReentrantLock.lock(),而对应锁已被其他线程获取的情况下都将导致线程进入阻塞状态;
注意:yield()并不会导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。
(5)、线程中断
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。其它线程可以发送中断标记,但是是否结束由线程自身决定。
package com.xiaoyaoyou.day11;public class InterruptTest {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable2());
thread.start();
for(int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
thread.interrupt();
}
}
class MyRunnable2 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) {
System.out.println("发现中断,释放线程资源并退出");
return;
}
}
}
}
结果:
Thread-0:0main:0
Thread-0:1
main:1
Thread-0:2
main:2
Thread-0:3
Thread-0:4
main:3
Thread-0:5
main:4
发现中断,释放线程资源并退出
(6)、守护线程
线程分为守护线程和用户线程。
用户线程:当一个进程不包含任何的存活的用户线程时,进行结束
守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动结束
package com.xiaoyaoyou.day11;public class InterruptTest {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable2());
//设置为守护线程
thread.setDaemon(true);
thread.start();
for(int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
class MyRunnable2 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) {
System.out.println("发现中断,释放线程资源并退出");
return;
}
}
}
}
结果:
Thread-0:0main:0
Thread-0:1
main:1
Thread-0:2
main:2
Thread-0:3
Thread-0:4
main:3
Thread-0:5
main:4
5、线程安全问题
(1)、线程安全问题情形
常见的卖票就是多线程的经典问题,票的数量是有限的,多个线程同时抢票:
package com.xiaoyaoyou.day11;public class TicketTest {
public static void main(String[] args) {
Runnable runnable = new Ticket();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
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);
}
System.out.println("暂无余票");
}
}
发现余票出现了负数、同时出现相同余票、没有出现余票三等各种情况:
正在排队出票正在排队出票
正在排队出票
出票成功,余票:8
正在排队出票
出票成功,余票:8
正在排队出票
出票成功,余票:8
正在排队出票
出票成功,余票:6
正在排队出票
出票成功,余票:6
正在排队出票
出票成功,余票:5
正在排队出票
出票成功,余票:4
正在排队出票
出票成功,余票:2
正在排队出票
出票成功,余票:2
正在排队出票
出票成功,余票:1
出票成功,余票:0
暂无余票
出票成功,余票:-1
暂无余票
暂无余票
这是由于多个线程操作同一块变量,在抢占后某个线程执行的任务还未完成,另一个线程也执行了任务,这时就会造成冲突,就好比大家都去锅里捞一块肉就会出现同时夹住一个肉或者一个人已经夹走了这块肉另一个人只能夹个空气,然后尴尬的放下筷子。
(2)、隐式锁-同步代码块
为了解决上述问题,我们定一些规则让大家不要争抢,而是排队有序的执行任务,这样大家都为了完成这件事而努力,比一个人效率高,而且不会发生冲突。
规则之一就是同步代码块,一个线程抢到之后对其加锁,其他线程就不能再抢进来了,等这个线程执行完再开锁,这时再进行争抢(使用任意一个对象作为锁)。
package com.xiaoyaoyou.day11;public class TicketTest {
public static void main(String[] args) {
Runnable runnable = new Ticket();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
class Ticket implements Runnable {
private int count = 10;
private Object object = new Object();
@Override
public void run() {
while (true) {
synchronized (object) {
if (count > 0) {
//出票
System.out.println("正在排队出票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:" + count);
} else {
System.out.println("暂无余票");
break;
}
}
}
}
}
结果:
正在排队出票Thread-0出票成功,余票:9
正在排队出票
Thread-0出票成功,余票:8
正在排队出票
Thread-0出票成功,余票:7
正在排队出票
Thread-0出票成功,余票:6
正在排队出票
Thread-0出票成功,余票:5
正在排队出票
Thread-0出票成功,余票:4
正在排队出票
Thread-0出票成功,余票:3
正在排队出票
Thread-0出票成功,余票:2
正在排队出票
Thread-0出票成功,余票:1
正在排队出票
Thread-0出票成功,余票:0
暂无余票
暂无余票
暂无余票
这时发现没有出现之前的余票为负数以及多个线程抢到一个票的情况,但是明显速度慢了。
而且最后出现了多次暂无余票的情况,这也就是我们经常看到有票,但是付款的时候发现没有票的原因,因为你所在抢票的线程看似进入了任务,但是其它线程已经把票卖出去了。
这个里面还有一个问题就是第一个创建的线程容易出现一直是这个线程卖完票的情况,因为它是第一个创建的,所以更容易抢到这个锁,当该锁释放后循环重新开始又是它先接触到(回手掏?)。
这个锁必须是所有线程共用一个锁,否则就起不到效果了(合理的监管肯定是同一个第三方进行监管而不是自己管自己活着各自找自己的监管)。
(3)、隐式锁-同步方法
同步代码块是多代码块进行加锁,而同步方法则是对某个方法进行加锁。
package com.xiaoyaoyou.day11;public class TicketTest {
public static void main(String[] args) {
Runnable runnable = new Ticket();
new Thread(runnable, "thread1").start();
new Thread(runnable, "thread0").start();
new Thread(runnable, "thread2").start();
}
}
class Ticket implements Runnable {
private int count = 10;
@Override
public void run() {
while (true) {
if (!sale()) {
System.out.println("暂无余票");
break;
}
}
}
public synchronized boolean sale() {
if (count > 0) {
//出票
System.out.println("正在排队出票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
return true;
}
return false;
}
}
结果:
正在排队出票thread1出票成功,余票:9
正在排队出票
thread1出票成功,余票:8
正在排队出票
thread1出票成功,余票:7
正在排队出票
thread1出票成功,余票:6
正在排队出票
thread1出票成功,余票:5
正在排队出票
thread1出票成功,余票:4
正在排队出票
thread1出票成功,余票:3
正在排队出票
thread1出票成功,余票:2
正在排队出票
thread1出票成功,余票:1
正在排队出票
thread1出票成功,余票:0
暂无余票
暂无余票
暂无余票
(4)、显式锁Lock
通过创建锁对象,在使用前加锁,使用后解锁:
package com.xiaoyaoyou.day11;import java.util.Locale;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketTest {
public static void main(String[] args) {
Runnable runnable = new Ticket();
new Thread(runnable, "thread1").start();
new Thread(runnable, "thread0").start();
new Thread(runnable, "thread2").start();
}
}
class Ticket implements Runnable {
private int count = 10;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
if (!sale()) {
System.out.println("暂无余票");
break;
}
}
}
public boolean sale() {
lock.lock();
if (count > 0) {
//出票
System.out.println("正在排队出票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
lock.unlock();
return true;
}
lock.unlock();
return false;
}
}
结果:
正在排队出票thread1出票成功,余票:9
正在排队出票
thread1出票成功,余票:8
正在排队出票
thread1出票成功,余票:7
正在排队出票
thread1出票成功,余票:6
正在排队出票
thread1出票成功,余票:5
正在排队出票
thread1出票成功,余票:4
正在排队出票
thread1出票成功,余票:3
正在排队出票
thread1出票成功,余票:2
正在排队出票
thread1出票成功,余票:1
正在排队出票
thread1出票成功,余票:0
暂无余票
暂无余票
暂无余票
(5)、公平锁和非公平锁
公平锁:排队使用锁,按照线程创建进行排队使用锁
非公平锁:抢占使用锁,各个线程抢占,一般默认都是非公平锁
上面的显式锁创建时传true即可:
package com.xiaoyaoyou.day11;import java.util.Locale;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketTest {
public static void main(String[] args) {
Runnable runnable = new Ticket();
new Thread(runnable, "thread0").start();
new Thread(runnable, "thread1").start();
new Thread(runnable, "thread2").start();
}
}
class Ticket implements Runnable {
private int count = 10;
//公平锁
private Lock lock = new ReentrantLock(true);
@Override
public void run() {
while (true) {
if (!sale()) {
System.out.println("暂无余票");
break;
}
}
}
public boolean sale() {
lock.lock();
if (count > 0) {
//出票
System.out.println("正在排队出票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "出票成功,余票:" + count);
lock.unlock();
return true;
}
lock.unlock();
return false;
}
}
这个就解决了一些线程可能一直执行不到任务的问题:
正在排队出票thread0出票成功,余票:9
正在排队出票
thread1出票成功,余票:8
正在排队出票
thread2出票成功,余票:7
正在排队出票
thread0出票成功,余票:6
正在排队出票
thread1出票成功,余票:5
正在排队出票
thread2出票成功,余票:4
正在排队出票
thread0出票成功,余票:3
正在排队出票
thread1出票成功,余票:2
正在排队出票
thread2出票成功,余票:1
正在排队出票
thread0出票成功,余票:0
暂无余票
暂无余票
暂无余票
6、线程死锁
警察和绑匪的故事:绑匪说你放了我我放了人质,警察说你放了人质我放了你,这时两方都会等在这,警察等着绑匪放人质再继续执行,绑匪等着警察放了他再执行,大家都同时执行时就僵持在这了,也就是传说中的死锁。
package com.xiaoyaoyou.day11;public class BlockDeadTest {
public static void main(String[] args) {
Culprit c = new Culprit();
Police p = new Police();
//线程中警察喊话绑匪
Mythread mythread = new Mythread(c, p);
mythread.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("警察救了人质,但是罪犯跑了");
}
}
}
结果,卡死了:
罪犯:你放了我,我放了人质警察:你放了人质,我放过你
运行多次后可能会成功,就是某个任务先执行并且执行完了另一个任务才开始执行,这时就完美错开了,但是这种几率就比较小,产生死锁的概率就很大:
罪犯:你放了我,我放了人质警察救了人质,但是罪犯跑了
警察:你放了人质,我放过你
罪犯被放走了,罪犯也放了人质
对于可能产生死锁的情形排队执行完一个再执行另一个,某一方做让步即可。
7、多线程通信问题(生产者和消费者)
生产者与消费者问题:一个线程生产,一个线程消费;消费者下单后唤醒生产者并等待生产者生产,生产者生产完后唤醒消费者并给到消费者,然后等待消费者再次下单唤醒生产者。
如果不进行合理通信的话可能就会造成生产一堆东西而消费者没有消费或者消费者大量消费而没有生产者生产的问题。
package com.xiaoyaoyou.day11;public class ProduceConsumeTest {
public static void main(String[] args) {
new Produce().start();
new Consume().start();
}
static class Thing {
private int num = 0;
public Thing(int num) {
this.num = num;
}
public synchronized int getNum() {
return num;
}
public synchronized void setNum(int num) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.num = num;
System.out.println("目前产量:"+num);
}
}
static class Produce extends Thread{
private Thing thing = new Thing(0);
@Override
public void run() {
try {
for(int i = 0; i < 10; i++)
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
thing.setNum(thing.getNum() + 1);
}
}
static class Consume extends Thread{
private Thing thing = new Thing(0);
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
thing.setNum(thing.getNum() - 1);
}
}
}
}
结果:
目前产量:-1目前产量:-2
目前产量:-3
目前产量:-4
目前产量:-5
目前产量:1
目前产量:-6
目前产量:-7
目前产量:-8
目前产量:-9
目前产量:-10
通过notif和wait进行多线程通信,就是上面所说的等待和唤醒进行线程间通信来进行合理的生产和消费:
package com.xiaoyaoyou.day11;public class ProduceConsumeTest {
public static void main(String[] args) {
Thing thing = new Thing(0);
new Produce(thing).start();
new Consume(thing).start();
}
static class Thing {
private int num = 0;
private boolean flag = true;
public Thing(int num) {
this.num = num;
}
public synchronized void delNum() {
if (!flag) {
num--;
flag = true;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void addNum() {
if (flag) {
num++;
flag = false;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
static class Produce extends Thread{
private Thing thing;
public Produce(Thing thing) {
this.thing = thing;
}
@Override
public void run() {
for(int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("生产一次");
thing.addNum();
}
}
}
static class Consume extends Thread{
private Thing thing;
public Consume(Thing thing) {
this.thing = thing;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费一次");
thing.delNum();
}
}
}
}
结果:
生产一次消费一次
生产一次
消费一次
生产一次
消费一次
生产一次
消费一次
生产一次
消费一次
生产一次
消费一次
生产一次
消费一次
生产一次
消费一次
生产一次
消费一次
生产一次
消费一次
最后线程没有退出,但是线程最后没有退出,这个需要后面学习如何退出线程等待状态。
8、线程的六种状态
- 1、初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
- 2、运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。 - 3、阻塞(BLOCKED):表示线程阻塞于锁。
- 4、等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
- 5、超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
- 6、终止(TERMINATED):表示该线程已经执行完毕。
9、实现Callable接口实现线程
(1)、Runnable与Callable
//Callable接口public interface Callable<V> {
V call() throws Exception;
}
//Runnable接口
public interface Runnable {
public abstract void run();
}
(2)、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();
(3)、其它
- Runnable与Callable的相同点
都是接口
都可以编写多线程程序
都采用Thread.start()启动线程 - Runnable与Callable的不同点
Runnable没有返回值;Callable可以返回执行结果
Callable接口的call()允许抛出异常;Runnable的run()不能抛出 - Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。
(4)、示例
package com.xiaoyaoyou.day11;import jdk.nashorn.internal.codegen.CompilerConstants;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
System.out.println(futureTask.isDone());
new Thread(futureTask).start();
Integer j = futureTask.get();
System.out.println("返回值为:"+j);
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println(i);
}
}
static class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
for(int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println(i);
}
return 100;
}
}
}
结果:
false0
1
2
3
4
5
6
7
8
9
返回值为:100
0
1
2
3
4
5
6
7
8
9
10、线程池
(1)、概述
一般来说,多线程开发的流程为:创建线程->创建任务->执行任务->关闭线程,当有大量这样的过程时,创建线程过程和销毁线程过程会占用大量的时间,为了解决这种问题,就出现了线程池技术,我们提前创建好“一池子”的线程等待执行任务,任务结束后也不释放线程而是等待执行下一次任务,直到程序结束或彻底不需要线程的时候释放掉,这样就避免了不停创建线程和销毁线程的时间消耗。
线程池的好处
- 降低资源消耗。
- 提高响应速度。
- 提高线程的可管理性。
(2)、Java中的四种线程池
- 1、缓存线程池
- 2、定长线程池
- 3、单线程线程池
- 4、周期性任务定长线程池
(3)、示例
- 示例1:缓存线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPollTest {
/**
* 缓存线程池:(长度无限制)
* 任务加入后的执行流程:
* 1、判断线程池是否为空
* 2、存在则使用
* 3、不存在则创建线程并创建线程,并使用
* @param args
*/
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()+"缓存线程池测试");
}
});
}
}
结果:
pool-1-thread-1缓存线程池测试pool-1-thread-1缓存线程池测试
pool-1-thread-1缓存线程池测试
线程1被多次使用,减少了资源损耗。
- 示例2:定长线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPollTest {
public static void main(String[] args) {
fixedThreadPoll();
}
/**
* 定长线程池(长度是指定的数值)
* 任务加入后的执行流程:
* 1、判断线程池是否存在空闲线程
* 2、存在则使用
* 3、不存在空闲线程,且线程未满的情况下,则创建线程 并放入线程池,然后使用
* 4、不存在空闲线程,但线程已满的情况下,等待线程池存在空闲线程
*/
static public void fixedThreadPoll() {
ExecutorService service = Executors.newFixedThreadPool(2);
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()+"定长线程池测试");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"定长线程池测试");
}
});
}
}
结果:
pool-2-thread-1定长线程池测试pool-2-thread-2定长线程池测试
pool-2-thread-1定长线程池测试
pool-2-thread-2定长线程池测试
- 示例3:单线程线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPollTest {
public static void main(String[] args) {
singleThreadPoll();
}
/**
* 单线程线程池
* 执行流程:
* 1、判断线程池的那个线程是否空闲
* 2、空闲则使用
* 3、不空闲则等待池中的那个线程空闲后再使用
*/
static public void singleThreadPoll() {
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()+"单线程线程池测试");
}
});
}
}
结果:
pool-3-thread-1单线程线程池测试pool-3-thread-1单线程线程池测试
pool-3-thread-1单线程线程池测试
- 示例4:周期性任务定长线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ThreadPollTest {
public static void main(String[] args) {
scheduleThreadPoll();
}
/**
* 周期任务 定长线程池
* 执行流程:
* 1、判断线程池是否存在空闲线程
* 2、存在则使用
* 3、不存在空闲线程,且线程池未满的情况下,创建线程并放入线程池,然后使用
* 4、不存在空闲线程,但线程池已满的情况下,等待线程池存在空闲线程再使用
*
* 周期任务执行时:
* 定时执行,当某个时机处罚时,自动执行任务
*/
static public void scheduleThreadPoll() {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 1、定时执行一次
* 参数1:定时执行的任务
* 参数2:时长数字
* 参数3:时长数字的单位,TimeUnit的常量指定
*/
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("周期定长线程池执行一次测试");
}
}, 5, TimeUnit.SECONDS);
/**
* 周期性执行任务
* 参数1:任务
* 参数2:延迟时长数字(第一次执行在什么时间之后)
* 参数3:周期时长数字(每隔多久执行一次)
* 参数4:时长数字单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("周期定长线程池测试");
}
}, 5, 1, TimeUnit.SECONDS);
}
}
结果:
周期定长线程池执行一次测试周期定长线程池测试
周期定长线程池测试
周期定长线程池测试
周期定长线程池测试
周期定长线程池测试
...
11、Lambda表达式
函数式编程思想。线程中执行的任务如果使用创建对象再使用对象方法处理的话代码显得比较冗余臃肿,而直接创建一个函数将任务模块化执行即可,Lambda表达式的出现扩展了Java的函数式编程,在某些位置适合函数式模块化编程的位置直接创建一个重复任务使其被调用即可,而Lambda表达式省略了函数名和函数声明的过程,直接将函数实现和调用结合,对整个过程进行了简化。
package com.xiaoyaoyou.day11;public class LambdaTest {
public static void main(String[] args) {
new Thread(()-> {for(int i = 0; i < 10; i++) {
System.out.println("Lambda测试");
}}).start();
}
}
结果:
Lambda测试Lambda测试
Lambda测试
Lambda测试
Lambda测试
Lambda测试
Lambda测试
Lambda测试
Lambda测试
Lambda测试
如上,原本要实现的话需要创建任务类,实现run方法重写,然后再创建类进行传递并调用,现在都省略了。