多线程技术概述
1.线程与进程
- 进程:内存中运行的应用程序,每个进程都拥有一个独立的内存空间。
- 线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换、并发执行,一个进程最少有一个线程
- 线程实际数是在进程基础之上的进一步划分,一个进程启动之后,进程之中的若干执行路劲又可以划分成若干个线程
2.线程的调度
- 分时调度:所有线程轮流使用CPU的使用权,平均分配时间
- 抢占式调度:让优先级高的线程先使用,如果优先级相同,则随机选择,Java为抢占式调度,**多线程不能提高程序的运行速度,但能够提高程序运行效率。
3.同步及异步
- 同步:排队进行,效率低但安全
- 异步:同时执行,效率高但数据不安全
4.并发与并行
并发:两个或多个事件在同一时间段内发生。
并行:两个或多个事件同一时刻发生
Java中实现多线程
1.继承Thread
继承Thread类之后,需要重写run()方法,并在run(){中写需要执行的代码},在使用时需要初始化类,同时调用类的start()方法。在实现Thread时,可以直接在main方法中直接new Thread(){继承方法}.start()执行。使用.setPriority()设置线程优先级。使用sleep()(静态方法,可直接调用Thread.sleep())休眠/阻塞(消耗时间的操作)线程。
public class MyThread extends Thread {
@Override
public void run() {
//执行的代码块
for (int i = 0; i <10 ; i++) {
System.out.println("多线程"+i);
}
}
}
public class Demo {
public static void main(String[] args) {
/*new Thread(){
@Override
public void run(){}
}.start();*/
MyThread myThread=new MyThread();
myThread.start();
for (int i = 0; i <10 ; i++) {
System.out.println("线程"+i);
}
}
}
执行结果如下:由于是抢占式运行,故运行结果有可能不同。
2.使用Runnable类
与Thread类使用方法一致,但Runnable为接口;且Runnable为任务,需要使用Thread类(线程)执行任务。
/**
* 用于给线程进行执行的任务
*/
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println("Runnable多线程"+i);
}
}
}
public class Demo {
public static void main(String[] args) {
//创建任务对象
MyRunnable myRunnable = new MyRunnable();
//创建线程用于执行任务
Thread thread = new Thread(myRunnable);
//执行任务
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println("Runnable多线程" + i);
}
}
}
效果如下:
3.Runnable比之Thread的优势
1.Runnable通过创建任务,分配个线程后执行,更适合多线程同时执行相同任务的情况。
2.可以避免单继承带来的局限性。
3.任务与线程分离,提高程序的健壮性。
4.线程池技术只接受Runnable类型的任务,不接受Thread类型的线程。
4.线程的中断
一个线程是一个独立的执行路径,应该由其本身决定,若需要中断线程,需要作出标记-》使用Thread.interrupt()方法标记,由于Runnable无法抛出异常,需要用try{}catch(){}抓取异常。
public class StopThread {
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(new StopRunnable());
thread.start();
for (int i = 0; i <5 ; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+":"+i);
}
//对线程进行标记,发现后,进入异常,提醒线程该结束了
thread.interrupt();
}
static class StopRunnable implements Runnable{
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"i");
}
} catch (InterruptedException e) {
System.out.println("发现标记,结束线程");
//结束线程
return;
}
}
}
}
5.守护线程及用户线程
- 用户线程:当进程不包含任何存活的用户线程是,进行结束
- 守护线程:守护用户线程,当最后一个用户线程结束时,守护线程自动结束。
public static void main(String[] args) throws InterruptedException {
Thread thread=new Thread(new StopRunnable());
//设置线程为守护线程
thread.setDaemon(true );
thread.start();
for (int i = 0; i <5 ; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
static class StopRunnable implements Runnable{
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"i");
}
} catch (InterruptedException e) {
}
}
}
}
6.线程安全问题
线程不安全运行时,可能导致多线程同时进入任务,导致任务未按要求进行
public class TicketDemo {
public static void main(String[] args) {
Runnable runnable=new Ticket();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
static 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);
}
}
}
}
线程安全运行:
- 同步代码块(隐式锁),排队执行,使用synchronized(“锁对象”){}锁对象必须是同一个
public class TicketDemo {
public static void main(String[] args) {
Runnable runnable = new Ticket();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
static class Ticket 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("开始卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"余票" + count);
}else{break;}
}
}
}
}
}
- 同步方法(隐式锁),在方法类型前加上synchronized、方法内部自动调用this调用对象,静态时必须使用类名.class,
public class TicketDemo {
public static void main(String[] args) {
Runnable runnable = new Ticket();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
static class Ticket implements Runnable {
private int count = 10;
//锁对象
private Object o = new Object();
@Override
public void run() {
while (true) {
boolean sale = sale();
if (!sale){
break;
}
}
}
public synchronized boolean sale() {
synchronized (o) {
if (count > 0) {
System.out.println("开始卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName() + "余票" + count);
}
return false;
}
}
}
}
3. 显式锁Lock,需要使用子类ReentrantLock,更适合面向对象
1. 初始化锁:Lock l=new ReentrantLock();
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo2 {
public static void main(String[] args) {
Runnable runnable = new Ticket();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
static class Ticket implements Runnable {
private int count = 10;
//创建显式锁
private Lock lock=new ReentrantLock();
@Override
public void run() {
while (true) {
//锁住代码块
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);
}else{
break;
}
//解锁
lock.unlock();
}
}
}
7.公平锁与非公平锁
- 公平锁:先来后到,执行程序,在显式锁中,初始化时采用有参构造方法即Lock l=newReentrantLock(“fire”:true);代表公平锁
- 非公平锁:抢占式执行,上述三种方法均为非公平锁。
8.多线程通信问题
类似于生产者与消费者,当生产者未生产商品时,消费者无法执行下一步操作,同理生产者也是。--》先执行一个线程,执行完毕之后睡眠,当另一个线程执行完毕之后唤醒先前执行的线程后进行睡眠,依次交替;
public class Demo3 {
public static void main(String[] args) {
Food food=new Food();
new Cook(food).start();
new Waiter(food).start();
}
static class Cook extends Thread {
private Food food;
public Cook(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
food.setNameTaste("鸡蛋", "辣的");
} else {
food.setNameTaste("面条", "咸的");
}
}
}
}
static class Waiter extends Thread {
private Food food;
public Waiter(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
food.get();
}
}
}
static class Food {
private String name;
private String taste;
//判断是否生产完毕或者消费完毕
private boolean flag = true;
private synchronized void setNameTaste(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();
}
}
}
}
}
9.线程的状态
- NEW–线程被建立,单位启动
- Runnable–执行状态
- Blocked–被阻塞等待监视器锁定
- Waiting–无线等待状态,直到被唤醒
- TimedWaiting–指定时间等待
- TERMINATED–线程已退出
11.特殊创建线程的方法–带返回值Callable
与Runnable相同点:1.都是接口,2.都可以编写多线程,3.都采用Thread.start()启动线程。
与Runnable不同的:1.Runnable无返回值,Callable可返回执行结果,2.Callable的call()方法允许跑出异常,Runnable的run()方法不允许。
1.编写类实现Callable接口,实现call方法
class XXX implements Callable<T>{
@Override
public <T> call() throws Exception{
return T;
}
}
2.创建FutureTask对象,并传入编写的Callable类对象
FutureTask<Integer> f=new FutureTask<>(callable);
3.通过Thread,启动线程
new Thread(f).start();
12.线程池–降低资源消耗、提高响应速度、提高线程可管理性
线程池是一个容纳多个线程的容器,池中的线程可以反复使用,可以节省大量创建线程对象的时间和资源
13、常见的4中线程池
- 缓存线程池
/**
* 缓存线程池.
* (长度无限制)
* 执行流程:
* 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());
}
});
2.定长线程池
/**
* 定长线程池.
* (长度是指定的数值)
* 执行流程:
3. 单线程线程池
4. 周期性任务定长线程池
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
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());
}
})
3.单线程线程池
/**
* 定长线程池.
* (长度是指定的数值)
* 执行流程:
3. 单线程线程池
4. 周期性任务定长线程池
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
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());
}
})
4.周期性任务定长线程池
public static void main(String[] args) {
/**
* 周期任务 定长线程池.
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*
* 周期性任务执行时:
* 定时执行, 当某个时机触发时, 自动执行某任务 .
*/
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 定时执行
* 参数1. runnable类型的任务
* 参数2. 时长数字
* 参数3. 时长数字的单位
*/
/*service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("定时执行");
}
},5,TimeUnit.SECONDS);
*/
/**
* 周期执行
* 参数1. runnable类型的任务
* 参数2. 时长数字(延迟执行的时长)
* 参数3. 周期时长(每次执行的间隔时间)
* 参数4. 时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("周期执行");
}
},5,2,TimeUnit.SECONDS);
}
14.Lambda表达式–函数式编程思想
接口必须只有一个抽象方法
Thread t=new Thread(()->{内容})
public class Demo{
publci static void main(String[] args){
//使用lambda表达式实现方法的调用
print(int sum(int x,int y)->{
return x+y;
}100,200);
}
public static void print(MyMath m,int x,int y){
int num=m.sum(x,y);
System.out.println(num);
}
static interface MyMath{
int sun(int x, int y);
}
}