1、基本概念
1.1进程与线程
一个应用程序(软件)一般由1个或多个进程运行
1个进程又是由多个线程来进行分配任务处理
当多个线程抢占资源来完成任务时,这就涉及到线程调度问题了。
1.2 同步与异步
同步:多个线程排队执行,一次只能执行一个线程,synchronized关键字修饰。
异步:多个线程抢占式同时进行,一次执行多个线程。
1.3 并发与并行
并发:一个时间段内,多个线程同时进行
并行:同一个时刻内,多个线程同时进行
2.Java中的线程调度
2.1.抢占式调度:
指的是每条线程执行的时间、线程的切换都由系统控制。系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。
2.2.协同式调度:
指某一线程执行完后主动通知系统切换到另一线程上执行。线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步问题,但它有一个致命缺点:如果一个线程编写有问题,运行到一半就一直阻塞,那么可能导致整个系统崩溃。
2.3.JVM的线程调度的实现(抢占式调度):
Java使用的线程调度使用抢占式调度,Java中线程会按优先级分配CPU时间片运行,且优先级越高越优先执行,但优先级高并不代表能独自占用执行时间片,可能是优先级高得到越多的执行时间片,反之,优先级低的分到的执行时间少但不会分配不到执行时间。
2.4.线程让出CPU的情况:
1)当前运行线程主动放弃CPU,JVM暂时放弃CPU操作(基于时间片轮转调度的JVM操作系统不会让线程永久放弃CPU,或者说放弃本次时间片的执行权),例如调用yield方法。
2)当前运行线程因为某些原因进入阻塞状态,例如阻塞在IO上。
3)当前运行线程结束,即运行完run方法里面的任务。
3.Java中的三种多线程实现方式
3.1 第一种:继承Thead
public class Demo1 {
public static void main(String[] args) {
MyDemo1 myDemo1 = new MyDemo1();
myDemo1.start();
System.out.println(Thread.currentThread().getName() + " : 11111111111");;
}
}
class MyDemo1 extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " : 22222222222");;
}
}
3.2 第二种:实现Runnable
public class Demo2 {
public static void main(String[] args) {
Thread t = new Thread(new MyDemo2());
t.start();
System.out.println(Thread.currentThread().getName() + " : 11111111111");
}
}
class MyDemo2 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " : 22222222222");;
}
}
3.3 实现Runnable与继承Thread两种方式的比较
实现Runnable与继承Thread相比有如下优势
1.通过创建任务,然后给线程分配任务的方式实现多线程,更适合多个线程同时执行任务的情况
2,可以避免单继承所带来的局限性
3,任务与线程是分离的,提高了程序的健壮性
4,后期学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程
Thread.currentThread().getName() 获取当前线程名称
Thread.currentThread().setName() 设置当前线程名称
3.4 第三种:带返回值的Callable接口
Callable接口类似于Runnable ,因为它们都是为其实例可能由另一个线程执行的类而设计的。 但是, Runnable不会返回结果,也不能抛出已检查的异常。
该Executors类包含的实用方法,从其他普通形式转换为Callable类。
方法 | 描述 |
---|---|
call() | 计算结果,如果无法执行,则抛出异常 |
使用步骤
- 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T; } }
- 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
- 通过Thread,启动线程
new Thread(future).start();
Runnable 与 Callable的相同点
- 都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动线程
Runnable 与 Callable的不同点
- Runnable没有返回值;
- Callable可以返回执行结果
- Callable接口的call()允许抛出异常;
- Runnable的run()不能抛出
4.Thead类的常用方法
构造方法 | 描述 |
---|---|
Thread() | 分配新的 Thread对象 |
Thread(Runnable target) | 分配新的 Thread对象 |
Thread(Runnable target, String name) | 分配新的 Thread对象 |
常用字段 | 描述 |
---|---|
int MAX_PRIORITY | 线程可以拥有的最大优先级 |
int MIN_PRIORITY | 线程可以拥有的最低优先级 |
int NORM_PRIORITY | 分配给线程的默认优先级 |
常用方法 | 描述 |
---|---|
long getId() | 返回此Thread的标识符 |
String getName() | 线程可以拥有的最低优先级 |
Thread.State getState() | 返回此线程的状态 |
interrupt() | 中断此线程 |
setPriority(int newPriority) | 更改此线程的优先级 |
sleep(long millis, int nanos) | 线程休眠,指定的毫秒数加上指定的纳秒数 |
static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
5. 线程死亡
5.1 线程中断
interrupt()方法中断此线程
以前的stop()方法已经弃用,现在让线程死亡的方式:通过控制线程中断,然后抓取中断信号,然后return,自己直接结束。如下例:
public class Demo2 {
public static void main(String[] args) {
Thread t = new Thread(new MyDemo2());
t.start();
System.out.println(Thread.currentThread().getName() + " : 11111111111");
t.interrupt();//中断信号
}
}
class MyDemo2 implements Runnable{
@Override
public void run() {
Thread.currentThread().setName("123");
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " : 22222222222");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {//获取中断信号
System.out.println("中断标记!自己中断,线程死亡");
return;//线程自己结束
}
}
}
}
5.2 用户线程&&守护线程
用户线程:当无一个存活的用户线程时,程序结束(主人)
守护线程:当所有用户线程挂了,所有守护线程也就挂了(宠物)
设置守护线程
在线程启动之前,设置setDaemon(true)
Thread t = new Thread(new MyDemo2());
t.setDaemon(true);
t.start();
6.线程安全
6.1同步代码块
private Object o = new Obect();
在重写Run()方法里面加上synchronize(o){}
控制代码,使得线程同步
6.2 代码方法
方法前面使用synchronize修饰
使得线程每次独自运行一个方法
6.3 显式Lock锁
private Lock l = new ReentrantLock()
l.lock();
6.4 公平锁 && 非公平锁
公平锁:所有线程依着先来后到的抢占资源(公平,先来后到)
非公平锁:所有线程自由抢占资源(不公平,自由争抢,不管先来先到)
6.5 线程死锁
两个或多个线程互相争夺资源,然后由于死锁的缘故导致互相卡死在当前状态,退出不了当前资源,也获取不了新资源
典型的案例:生产者与消费者的案例
package com.java.demo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo4 {
/**
* 多线程通信问题, 生产者与消费者问题
* @param args
*/
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.setNameAndSaste("老干妈小米粥","香辣味");
}else{
f.setNameAndSaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
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 表示可以生产
private boolean flag = true;
public synchronized void setNameAndSaste(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();
}
}
}
}
}
7.Lambda表达式
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
语法
(参数) -> 表达式
或
(参数) ->{ 表达式; }
lambda表达式的重要特征:
- 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
- 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
- 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
- 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
8.线程池
8.1 缓存线程池(非定长线程池)
public class CachTheadPoolDemo {
public static void main(String[] args) {
//创建缓存线程池
ExecutorService service = Executors.newCachedThreadPool();
//添加任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("11111");
}
});
}
}
8.2 定长线程池
public class FixedTheadPool {
public static void main(String[] args) {
//创建定长线程池
ExecutorService service = Executors.newFixedThreadPool(2);
//添加任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("11111");
}
});
}
}
8.3 单线程线程池
public class SingleTheadPool {
public static void main(String[] args) {
//创建单线程线程池
ExecutorService service = Executors.newSingleThreadExecutor();
//添加任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("11111");
}
});
}
}
8.2 周期定长线程池
public class SchedledTheadPoolDemo {
public static void main(String[] args) {
//创建周期性定长线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 1.定时执行一次
* 参数1:定时执行的任务
* 参数2:时长数字
* 参数3:时长数字的单位,TimeUnit的常数指定
*/
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("1111111");
}
},5, TimeUnit.MICROSECONDS)
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("22222222");
}
},1,TimeUnit.MINUTES);
}
}