一、Java多线程简介
1. 什么是多线程(multithreading)
多线程(multithreading)是指从软件或者硬件上实现多个线程并发执行的技术。
2. 并发和并行
- 并发:在一段时间间隔内,在一个处理器上,通过cpu调度算法,先后完成指令执行
- 并行:在一段时间间隔内,在多个处理器上,同时指令执行
注:在《Java编程思想》中提到并发通常是提高运行在单处理器上的程序的性能,而直觉告诉我们,在单处理器上进行并发操作时开销更大,因为需要进行线程间的切换;但是,在程序的执行过程中,如果发生了阻塞(通常是IO),整个程序都会停滞,但是利用并发,可以使得程序中的其他任务继续执行。这,就是为什么要研究并发
3. 进程和线程
- 进程:是系统进行资源分配和调度的基本单位,是线程的容器,一个进程包含多个线程程
- 线程:是进程的基本组成单位
4. 线程的生命周期
5. 线程的五种基本状态
- 新建状态(new):线程创建后,进入新建状态
- 就绪状态(Runnable):调用线程的start()方法,线程进入就绪状态,等待cpu调度执行
- 运行状态(Running):cpu调度处于就绪状态的线程,线程进入运行阶段
- 阻塞状态(Blocked):运行期的线程,由于某种原因,放弃cpu的使用权,进入阻塞阶段,进入阻塞阶段的线程,马上进入就绪状态,等待cpu的再次调用
阻塞状态分为3种
- 等待阻塞:调用wait()方法,线程进入等待阻塞状态
- 同步阻塞:线程获取synchronized同步锁失败(被其他线程占用),进入同步阻塞状态
- 其他阻塞:调用sleep()或join()方法,线程进入阻塞状态,待sleep()超时、join()等待终止或超时、或者IO处理完成,线程重新进入就绪状态
- 死亡状态(Dead):因为异常,或者程序执行结束退出run()方法,线程结束了生命周期
二、线程的创建及使用
1. 继承Thread类,重写run()方法(Thread类本身也实现了Runnable接口)
- 继承Thread类重写run()方法,run()方法的方法体是线程要完成的任务
- 创建继承了Thread类的实例,该实例为线程实例
- 调用线程对象的start()方法来启动线程
package com.lt.thread;
/**
* 1.继承Thread类创建线程
* @author lt
* @date 2019年4月9日
* @version v1.0
*/
public class Thread_01 extends Thread {
@Override
public void run() {
for(int i=1; i<=5; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) {
Thread t1 = new Thread_01();
Thread t2 = new Thread_01();
t1.start();
t2.start();
}
}
结果
Thread-1:1
Thread-0:1
Thread-1:2
Thread-0:2
Thread-0:3
Thread-0:4
Thread-0:5
Thread-1:3
Thread-1:4
Thread-1:5
2. 实现Runnable接口,重写run()方法
- 定义Runnable接口的实现类,重写run()方法,run()方法的方法体是线程要完成的任务
- 创建实现了Runnable接口的实现类的实例,并将此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
- 调用线程对象的start()方法来启动线程
package com.lt.thread;
/**
* 2.实现Runnable接口
* @author lt
* @date 2019年4月9日
* @version v1.0
*/
public class Thread_02 implements Runnable {
@Override
public void run() {
for(int i=1; i<=5; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
public static void main(String[] args) {
Thread_02 _t1 = new Thread_02();
Thread_02 _t2 = new Thread_02();
Thread t1 = new Thread(_t1);
Thread t2 = new Thread(_t2);
t1.start();
t2.start();
}
}
结果
Thread-0:1
Thread-1:1
Thread-0:2
Thread-1:2
Thread-0:3
Thread-1:3
Thread-0:4
Thread-1:4
Thread-0:5
Thread-1:5
3. 使用Callable和Future创建线程
- 创建Callable的实现类,并重写call()方法,方法体即线程执行体,并且有返回值
- 创建Callable实现类的实例,并用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
- 使用FutureTask对象作为Thread的target来创建Thread对象,该对象为真正的线程对象
- 调用线程对象的start()方法启动线程
package com.lt.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* 3.使用Callable和Future创建线程
* @author lt
* @date 2019年4月9日
* @version v1.0
*/
public class Thread_03 implements Callable<Object>{
@Override
public Object call() throws Exception {
for(int i=1; i<=5; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
return Thread.currentThread().getName()+"执行完成!";
}
public static void main(String[] args) throws Exception {
Thread_03 _t1 = new Thread_03();
Thread_03 _t2 = new Thread_03();
FutureTask<Object> _f1 = new FutureTask<>(_t1);
FutureTask<Object> _f2 = new FutureTask<>(_t2);
Thread t1 = new Thread(_f1);
Thread t2 = new Thread(_f2);
t1.start();
t2.start();
//获取线程中的返回值,get()方法会导致程序阻塞,直到子线程结束后才会得到返回值
System.out.println(_f1.get());
System.out.println(_f2.get());
}
}
结果
Thread-0:1
Thread-1:1
Thread-0:2
Thread-1:2
Thread-0:3
Thread-1:3
Thread-0:4
Thread-0:5
Thread-1:4
Thread-0执行完成!
Thread-1:5
Thread-1执行完成!
- 程序启动运行main()方法时,java虚拟机启动一个进程,当程序执行到start()方法时,在当前进程里,创建新的线程,整个应用开始在多线程状态下运行。
- 当调用start()方法时,程序并不立马执行,此时线程进入Runnable状态,什么时候执行,由操作系统决定
- 多线程的执行顺序是乱序的,
- Thread类本身也实现了Runnable接口
三、线程常用的方法
1. join()
A线程中调用B线程的join()方法,A线程会被阻塞进入阻塞状态(Blocked),直到B线程执行完成
package com.lt.thread;
/**
* 线程常用的方法:join()
* join():A线程中调用B线程的join()方法,A线程会被阻塞,直到B线程执行完成
* @author lt
* @date 2019年4月9日
* @version v1.0
*/
public class Thread_04 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=1; i<=10; i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
});
for(int i=1; i<=10; i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
if(i==7){
t1.start();
t1.join();
}
}
}
}
结果
main-1
main-2
main-3
main-4
main-5
main-6
main-7
Thread-0-1
Thread-0-2
Thread-0-3
Thread-0-4
Thread-0-5
Thread-0-6
Thread-0-7
Thread-0-8
Thread-0-9
Thread-0-10
main-8
main-9
main-10
2. sleep()
让当前线程进入阻塞状态(Blocked),暂停指定的时长;睡眠期结束后,线程进入就绪状态(Runnable),并不会立即执行,如果需要立即执行,可以调用sleep(1)
package com.lt.thread;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* sleep()
* 让当前线程进入阻塞状态,暂停指定的时长;睡眠期结束后,线程进入就绪状态(Runnable),并不会立即执行,
* 如果需要立即执行,可以调用sleep(1)
* @author lt
* @date 2019年4月9日
* @version v1.0
*/
@SuppressWarnings("all")
public class Thread_05 {
public static void main(String[] args) throws Exception {
while(true){
//隔一秒打印一次时间
Thread.currentThread().sleep(1000);
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
}
}
}
结果
2019-05-07 15:52:54
2019-05-07 15:52:55
2019-05-07 15:52:56
2019-05-07 15:52:57
2019-05-07 15:52:58
2019-05-07 15:52:59
2019-05-07 15:53:00
2019-05-07 15:53:01
2019-05-07 15:53:02
2019-05-07 15:53:03
2019-05-07 15:53:04
2019-05-07 15:53:05
2019-05-07 15:53:06
2019-05-07 15:53:07
3. setDaemon():守护线程
调用当前线程的setDaemon()方法便可将线程转化为守护线程;守护线程为其他线程提供服务,当其他线程进入死亡状态时,守护线程自动死亡
4. setPriority():改变线程优先级
线程的优先级并不是控制线程执行的先后顺序,而是表示线程占用CPU时间片长短,优先级越高,占用的时间片时间越长
package com.lt.thread;
/**
* 线程常用的方法:join()
* join():A线程中调用B线程的join()方法,A线程会被阻塞,直到B线程执行完成
* @author lt
* @date 2019年4月9日
* @version v1.0
*/
public class Thread_06 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=1; i<=20; i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}, "线程1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=1; i<=20; i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}, "线程2");
t1.setPriority(8);
t2.setPriority(2);
t1.start();
t2.start();
}
}
结果
线程1-1
线程1-2
线程1-3
线程1-4
线程1-5
线程1-6
线程1-7
线程1-8
线程1-9
线程1-10
线程1-11
线程1-12
线程1-13
线程1-14
线程1-15
线程2-1
线程1-16
线程2-2
线程1-17
线程2-3
线程1-18
线程2-4
线程2-5
线程1-19
线程2-6
线程1-20
线程2-7
...
5. yield():线程让步
线程调用yield()方法,线程进入就绪状态(Runnable)
四、start()和run()方法的区别
1. start()
启动一个新线程,在新线程中会执行相应的run()方法,start方法不能被反复调用
2. run()
在当前线程中启执行run()并不会启动新的线程,可以被重复调用
package com.lt.thread;
/**
* 1.调用start()方法,启动新的线程,在新的线程中执行run()方法
* 2.调用run()方法,在当前线程执行run()方法,并不会启动新线程
* @author lt
* @date 2019年4月9日
* @version v1.0
*/
public class Thread_07 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=1; i<=10; i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}, "线程1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=1; i<=10; i++){
System.out.println(Thread.currentThread().getName()+"-"+i);
}
}
}, "线程2");
t1.run();
t2.start();
}
}
结果
main-1
main-2
main-3
main-4
main-5
main-6
main-7
main-8
main-9
main-10
线程2-1
线程2-2
线程2-3
线程2-4
线程2-5
线程2-6
线程2-7
线程2-8
线程2-9
线程2-10