一、介绍
1.线程与进程
进程
•是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
线程
•是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程
•线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程
2.线程调度
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使用率更高。
3.同步与异步&并发与并行
同步
排队执行 , 效率低但是安全.
异步
同时执行 , 效率高但是数据不安全.
并发
指两个或多个事件在同一个时间段内发生。
并行
指两个或多个事件在同一时刻发生(同时发生)。
二、线程的创建
1.继承Thread类
方式一:创建子类进行调用
public class test1 {
public static void main(String[] args) {
// 创建继承Thread的类的对象
MyThread mt = new MyThread();
// 通过调用start方法创建线程
mt.start();
// 主线程与子线程抢占式调度
for (int i = 0;i < 10;i++){
System.out.println("呵呵呵"+i);
}
}
}
class MyThread extends Thread{
// 重写run方法
@Override
public void run() {
for (int i = 0;i < 10;i++){
System.out.println("哈哈哈"+i);
}
}
}
打印:
呵呵呵0
哈哈哈0
哈哈哈1
哈哈哈2
哈哈哈3
哈哈哈4
哈哈哈5
哈哈哈6
哈哈哈7
哈哈哈8
哈哈哈9
呵呵呵1
呵呵呵2
呵呵呵3
呵呵呵4
呵呵呵5
呵呵呵6
呵呵呵7
呵呵呵8
呵呵呵9
方式二:通过匿名内部类直接调用
public class test1 {
public static void main(String[] args) {
// 创建继承Thread的匿名类并调用start方法
new Thread(){
@Override
public void run() {
for (int i = 0;i < 10;i++){
System.out.println("哈哈哈"+i);
}
}
}.start();
// 主线程与子线程抢占式调度
for (int i = 0;i < 10;i++){
System.out.println("呵呵呵"+i);
}
}
}
打印同上
2.实现Runnable接口
public class test1 {
public static void main(String[] args) {
// 创建实现Runnable接口的类的对象
MyRunnable mr = new MyRunnable();
// 创建Thread线程
Thread t1 = new Thread(mr);
// 开启线程
t1.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
呵呵呵1
呵呵呵2
呵呵呵3
呵呵呵4
呵呵呵5
呵呵呵6
哈哈哈0
哈哈哈1
哈哈哈2
哈哈哈3
哈哈哈4
哈哈哈5
哈哈哈6
哈哈哈7
哈哈哈8
哈哈哈9
呵呵呵7
呵呵呵8
呵呵呵9
实现Runnable接口相比继承Thread类的优势:
1.通过创建任务,分配给线程,更适合多个线程同时执行相同任务的情况
2.可以避免单继承带来的局限性
3.任务与进程本身是分离的,提高了程序的健壮性
4.线程池技术,接受Runnable类型任务,不接受Thread类型的线程
3.Callable接口
public class Test4 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建对象
MyCallable mc = new MyCallable();
// 创建任务,泛型与mc对象相同即可
FutureTask<Integer> task = new FutureTask<>(mc);
// 创建线程
new Thread(task).start();
// 打印返回值
System.out.println(task.get());
}
}
// 继承Callable接口的类:设置返回值泛型
class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
for(int i=0;i<5;i++){
System.out.println("哈哈哈");
}
return 100;
}
}
打印:
哈哈哈
哈哈哈
哈哈哈
哈哈哈
哈哈哈
100
Runnable 与 Callable的不同点:
Runnable没有返回值;Callable可以返回执行结果
Callable接口的call()允许抛出异常;Runnable的run()不能抛出
三Thread类
1.主要构造方法
(1)Thread()
分配心得Thread对象
(2)Thread(Runnable target)
分配Runnable任务给新的Thread对象
(3)Thread(Runnable target, String name)
分配Runnable任务给新的Thread对象,并设置线程的名称
2.主要方法
(1)void run()
如果此线程是使用Runnable对象构造的,则调用该Runnable对象的run方法;
public void run() {
if (target != null) {
target.run();
}
}
(2)void setPriority(int newPriority)
更改线程的优先级别
static int MAX_PRIORITY 线程可以拥有的最大优先级。
static int MIN_PRIORITY 线程可以拥有的最低优先级。
static int NORM_PRIORITY 分配给线程的默认优先级。
(3)static void sleep(long millis) 线程休眠
休眠正在执行的线程并指定休眠毫秒数
public class test1 {
public static void main(String[] args) {
// 创建实现Runnable接口的类的对象
MyRunnable mr = new MyRunnable();
// 创建Thread线程
Thread t1 = new Thread(mr);
// 开启线程
t1.start();
}
}
class MyRunnable implements Runnable{
// 实现抽象方法
@Override
public void run() {
for (int i = 0;i < 5;i++){
System.out.println("哈哈哈"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
打印:隔1S打印一行
哈哈哈0
哈哈哈1
哈哈哈2
哈哈哈3
哈哈哈4
(4)static void sleep(long millis)
休眠正在执行的线程并指定休眠毫秒数和纳秒数
(5)void start()
线程开始执行; Java虚拟机调用此线程的run方法
(6)static Thread currentThread() 获取线程名
返回当前正在执行的线程对象的引用
public class test1 {
public static void main(String[] args) {
// 创建实现Runnable接口的类的对象
MyRunnable mr = new MyRunnable();
// 创建Thread线程
Thread t1 = new Thread(mr,"子线程");
// 开启线程
t1.start();
System.out.println(Thread.currentThread().getName());
}
}
class MyRunnable implements Runnable{
// 实现抽象方法
@Override
public void run() {
for (int i = 0;i < 5;i++){
System.out.println("哈哈哈"+i);
}
System.out.println(Thread.currentThread().getName());
}
}
打印:
main
哈哈哈0
哈哈哈1
哈哈哈2
哈哈哈3
哈哈哈4
子线程
(7)void interrupt() 中断线程
发现interrupt标记后,进入catch块
public class test1 {
public static void main(String[] args) {
// 创建实现Runnable接口的类的对象
MyRunnable mr = new MyRunnable();
// 创建Thread线程
Thread t1 = new Thread(mr);
// 开启线程
t1.start();
t1.interrupt();
}
}
class MyRunnable implements Runnable{
// 实现抽象方法
@Override
public void run() {
for (int i = 0;i < 5;i++){
System.out.println("哈哈哈"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("发现中断标记,结束线程");
return;
}
}
}
}
打印:
哈哈哈0
发现中断标记,结束线程
(8)void setDaemon(boolean on) 守护线程
将线程设置为守护线程(true)或用户线程(false)
用户线程:当一个进程中不包含任何存活的用户线程时,进程结束
守护线程:当最后一个用户线程结束时,所有守护线程自动死亡
public class test1 {
public static void main(String[] args) {
// 创建实现Runnable接口的类的对象
MyRunnable mr = new MyRunnable();
// 创建Thread线程
Thread t1 = new Thread(mr);
// 设置线程为守护线程
t1.setDaemon(true);
// 开启线程
t1.start();
System.out.println("守护进程结束");
}
}
class MyRunnable implements Runnable{
// 实现抽象方法
@Override
public void run() {
for (int i = 0;i < 5;i++){
System.out.println("哈哈哈"+i);
}
}
}
打印:
守护进程结束
四、其他
1.线程阻塞
耗时操作,比如等待用户输入、读取文件等
2.线程安全
问题描述
public class test1 {
public static void main(String[] args) {
// 创建实现Runnable接口的类的对象
MyRunnable mr = new MyRunnable();
// 开启三个线程售票
new Thread(mr).start();
new Thread(mr).start();
new Thread(mr).start();
}
}
class MyRunnable implements Runnable{
// 票数为5
private int count = 5;
// 实现抽象方法
@Override
public void run() {
while(count>0){
System.out.println("正在出票");
// 休眠10秒,以便其他线程进入
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("余票为"+count);
}
}
}
打印:票数可能为负
正在出票
正在出票
正在出票
余票为4
正在出票
余票为3
正在出票
余票为4
正在出票
余票为2
正在出票
余票为1
余票为0
余票为-1
(1)同步代码块
格式:synchronize(锁对象){被锁住的代码段}
当线程执行时,检查锁对象是否上锁,如果未解锁,则继续等待
public class test1 {
public static void main(String[] args) {
// 创建实现Runnable接口的类的对象
MyRunnable mr = new MyRunnable();
// 开启三个线程售票
new Thread(mr).start();
new Thread(mr).start();
new Thread(mr).start();
}
}
class MyRunnable implements Runnable{
// 票数为5
private int count = 5;
// 创建锁对象
Object o =new Object();
// 实现抽象方法
@Override
public void run() {
while(true){
synchronized (o) {
if (count > 0) {
System.out.println("正在出票");
count--;
System.out.println(Thread.currentThread().getName()+"余票为" + count);
}
else return;
}
}
}
}
打印:
正在出票
Thread-0余票为4
正在出票
Thread-0余票为3
正在出票
Thread-0余票为2
正在出票
Thread-0余票为1
正在出票
Thread-0余票为0
(2)同步方法
在方法返回值类型前加synchronize修饰
public class test1 {
public static void main(String[] args) {
// 创建实现Runnable接口的类的对象
MyRunnable mr = new MyRunnable();
// 开启三个线程售票
new Thread(mr).start();
new Thread(mr).start();
new Thread(mr).start();
}
}
class MyRunnable implements Runnable{
// 票数为5
private int count = 5;
// 实现抽象方法
@Override
public void run() {
while(true) {
Boolean b = sale();
if(!b)
break;
}
}
public synchronized Boolean sale(){
if (count > 0) {
System.out.println("正在出票");
count--;
System.out.println(Thread.currentThread().getName()+"余票为" + count);
return true;
}
else return false;
}
}
打印:
正在出票
Thread-0余票为4
正在出票
Thread-0余票为3
正在出票
Thread-0余票为2
正在出票
Thread-0余票为1
正在出票
Thread-0余票为0
(3)显式锁
public class test1 {
public static void main(String[] args) {
// 创建实现Runnable接口的类的对象
MyRunnable mr = new MyRunnable();
// 开启三个线程售票
new Thread(mr).start();
new Thread(mr).start();
new Thread(mr).start();
}
}
class MyRunnable implements Runnable{
// 票数为5
private int count = 5;
// 创建显式锁
Lock l = new ReentrantLock();
// 实现抽象方法
@Override
public void run() {
while(true) {
l.lock();
if (count > 0) {
System.out.println("正在出票");
count--;
System.out.println(Thread.currentThread().getName()+"余票为" + count);
}
else break;
l.unlock();
}
}
}
(4)公平锁和非公平锁
公平锁,先到的线程先锁:
Lock l = new ReentrantLock(true);
(5)线程死锁
A线程调用B线程对象时,B对象未解锁,B对象又需要调用A对象后才能解锁;
public class Task1 {
public static void main(String[] args) {
// 创建警擦类、罪犯类
Police p = new Police();
Culprit c = new Culprit();
// 创建任务
MyRunnable mr = new MyRunnable(p,c);
// 创建线程
new Thread(mr).start();
// 主线程:警察喊话
p.say(c);
}
}
// 线程方法类
class MyRunnable implements Runnable{
// 属性:罪犯类和警察类
private Police p;
private Culprit c;
// 构造方法一:无参空构造
public MyRunnable() {}
// 构造方法二:赋值警察、罪犯类
public MyRunnable(Police p, Culprit c) {
this.p = p;
this.c = c;
}
@Override
public void run() {
c.say(p);
}
}
// 罪犯类
class Culprit{
// 属性:暂无
// 空构造方法
public Culprit() {}
// 方法一:喊话
public synchronized void say(Police p){
System.out.println("罪犯说:你放过我,我放了人质");
p.respond();
}
// 方法二:回应警察
public synchronized void respond(){
System.out.println("罪犯回应:罪犯放了人质,警察放了罪犯");
}
}
// 警察类
class Police{
// 属性:暂无
// 空构造方法
public Police() {}
// 方法一:喊话
public synchronized void say(Culprit c){
System.out.println("警察:你放过人质,我放过你");
c.respond();
}
// 方法二:回应
public synchronized void respond(){
System.out.println("警察回应:警察救了人质,但罪犯跑了");
}
}
打印:
警察:你放过人质,我放过你
罪犯说:你放过我,我放了人质
3.多线程通讯(生产者与消费者)
public class Test3 {
public static void main(String[] args) {
// 创建Food对象
Food f = new Food();
// 开始厨师和服务员对象
new Thread(new Chef(f)).start();
new Thread(new Waiter(f)).start();
}
}
// 厨师
class Chef implements Runnable{
// 属性:食物
private Food f;
// 构造方法:赋值f
public Chef(Food f) {
this.f = f;
}
@Override
public void run() {
// 设置食物属性
for(int i = 0;i<10;i++){
if(i%2 == 0){
f.setNameAndTaste("皮蛋瘦肉粥","皮蛋味");
}else{
f.setNameAndTaste("黑芝麻拌饭","芝麻味");
}
}
}
}
// 服务员
class Waiter implements Runnable{
// 属性:食物
private Food f;
// 构造方法:赋值f
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<10;i++){
f.getNameAndTaste();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 食物
class Food{
// 属性:食物名称和味道
private String name;
private String taste;
// 构造方法:无参空构造法
public Food() {}
// 方法一:设置食物属性方法
public void setNameAndTaste(String name,String taste){
this.name = name;
// 设置睡眠,错误更容易出现
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
}
// 方法二:获取食物名称和味道
public void getNameAndTaste(){
System.out.println(name+","+taste);
}
}
打印:
null,null
黑芝麻拌饭,皮蛋味
皮蛋瘦肉粥,芝麻味
黑芝麻拌饭,皮蛋味
皮蛋瘦肉粥,芝麻味
黑芝麻拌饭,皮蛋味
皮蛋瘦肉粥,芝麻味
黑芝麻拌饭,皮蛋味
皮蛋瘦肉粥,芝麻味
黑芝麻拌饭,皮蛋味
上述代码在执行时,出现食物名字和味道对不上的情况,因为在厨师线程设置完食物名称,还未设置味道时,服务员线程可能已经打印食物的属性了;
即便使用代码块锁,也只能保证在食物设置完属性后,服务员线程再打印;但不能保证厨师线程设置一份,服务员线程打印一份;
可使用以下线程等待唤醒的方法实现线程通讯(同时设置方法锁,保证两个线程不同时操作对象,进行排队):
public class Test3 {
public static void main(String[] args) {
// 创建Food对象
Food f = new Food();
// 开始厨师和服务员对象
new Thread(new Chef(f)).start();
new Thread(new Waiter(f)).start();
}
}
// 厨师
class Chef implements Runnable{
// 属性:食物
private Food f;
// 构造方法:赋值f
public Chef(Food f) {
this.f = f;
}
@Override
public void run() {
// 设置食物属性
for(int i = 0;i<10;i++){
if(i%2 == 0){
f.setNameAndTaste("皮蛋瘦肉粥","皮蛋味");
}else{
f.setNameAndTaste("黑芝麻拌饭","芝麻味");
}
}
}
}
// 服务员
class Waiter implements Runnable{
// 属性:食物
private Food f;
// 构造方法:赋值f
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<10;i++){
f.getNameAndTaste();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
// 食物
class Food{
// 属性:食物名称和味道
private String name;
private String taste;
// 属性:判断线程是否休眠
private boolean flag = true;
// 构造方法:无参空构造法
public Food() {}
// 方法一:设置食物属性方法
public synchronized void setNameAndTaste(String name,String taste){
// flag为true时,可设置属性
if (flag) {
this.name = name;
// 设置睡眠,错误更容易出现
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
// 设置完成后,flag设为false
flag = false;
// 唤醒所有线程
this.notifyAll();
// 厨师线程等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 方法二:获取食物名称和味道
public synchronized void getNameAndTaste(){
// flag为false时执行
if(!flag) {
System.out.println(name + "," + taste);
// 设置flag为true
flag = true;
// 唤醒所有线程
this.notifyAll();
// 当前服务员线程等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
打印:
皮蛋瘦肉粥,皮蛋味
黑芝麻拌饭,芝麻味
皮蛋瘦肉粥,皮蛋味
黑芝麻拌饭,芝麻味
皮蛋瘦肉粥,皮蛋味
黑芝麻拌饭,芝麻味
皮蛋瘦肉粥,皮蛋味
黑芝麻拌饭,芝麻味
皮蛋瘦肉粥,皮蛋味
黑芝麻拌饭,芝麻味
4.线程的六种状态
线程可以处于以下状态之一:
• NEW
尚未启动的线程处于此状态;
• RUNNABLE
在Java虚拟机中执行的线程处于此状态;
• BLOCKED
被阻塞等待监视器锁定的线程处于此状态;
• WAITING
无限期等待另一个线程执行特定操作的线程处于此状态;
• TIMED_WAITING
正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态;
• TERMINATED
已退出的线程处于此状态;
线程在给定时间点只能处于一种状态。 这些状态是虚拟机状态,不反映任何操作系统线程状态。
5.线程池
线程池就是一个容纳多个线程的容 器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
【1】缓存线程池
当有空闲线程时,使用空闲线程;没有空闲线程时,创建新的线程,并放入线程池中;
public class Test5 {
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());
}
});
// 休眠一秒,使线程空闲
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 此时使用缓存的空闲线程,而不创建新线程
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("当前线程:"+Thread.currentThread().getName());
}
});
}
}
打印:
当前线程:pool-1-thread-1
当前线程:pool-1-thread-2
当前线程:pool-1-thread-3
当前线程:pool-1-thread-3
【2】定长线程池
可设置线程池最大线程数,当创建的线程达到最大线程容量后,新的任务只能利用空闲线程或者等待空闲线程执行;
public class Test5 {
public static void main(String[] args) {
// 创建缓存线程池
ExecutorService service = Executors.newFixedThreadPool(2);
// 加入任务,创建新线程加入线程池
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("当前线程:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("当前线程:"+Thread.currentThread().getName());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("当前线程:"+Thread.currentThread().getName());
}
});
}
}
前两行打印后休眠三秒,此时没有空闲线程,故等待三秒后第三行才打印:
当前线程:pool-1-thread-2
当前线程:pool-1-thread-1
当前线程:pool-1-thread-1
【3】单线程线程池
单个线程,等待空闲使用,相当于定长线程池设置线程数为1
public class Test5 {
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());
}
});
}
}
打印:
当前线程:pool-1-thread-1
当前线程:pool-1-thread-1
当前线程:pool-1-thread-1
【4】周期定长线程池
XX时长后开始执行,XX时长执行一次
public class Test5 {
public static void main(String[] args) {
// 创建周期定长线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
// 加入任务,创建新线程加入线程池
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("当前线程:"+Thread.currentThread().getName());
}
},5,1, TimeUnit.SECONDS);
}
}
5S后执行第一次,之后每隔1S执行一次,打印:
当前线程:pool-1-thread-1
当前线程:pool-1-thread-1
当前线程:pool-1-thread-1
当前线程:pool-1-thread-2
当前线程:pool-1-thread-1
当前线程:pool-1-thread-2
当前线程:pool-1-thread-1
当前线程:pool-1-thread-2
当前线程:pool-1-thread-2
6.Lambda表达式
(方法形参)->{方法执行体}用此表达式代替匿名内部类创建对象
public class Test6 {
public static void main(String[] args) {
print((int x,int y) -> {
return x+y;
},10,100);
}
public static void print(Test7 t7,int x,int y){
System.out.println(t7.sum(x,y));
}
}
interface Test7{
int sum(int x,int y);
}
打印:
110