多线程
程序:为完成特定任务,用某种语言编写的一组指令的的集合。即一段静态的代码,静态对象
进程:是程序的一次执行过程,或者正在运行的程序。是一个动态的过程,是资源的分配单,
线程:是一个程序内部的一条执行路径,线程是CPU调度和执行的单位,独立的执行路径
若一个进程同一时间并行执行多个线程,就是支持多线程的
单核CPU 多核CPU
并行:多个CPU同时执行多个任务
并发:一个CPU同时执行多个任务
多线程:
优点:
- 提高应用程序的响应
- 提高计算机系统CPU的利用率
- 改善程序结构
线程的优先级:
- MAX_PRIORITY:10
- MIN _PRIORITY:1
- NORM_PRIORITY:5 -->默认优先级
获取和设置当前线程的优先级:
- getPriority():获取线程的优先级
- setPriority(int p):设置线程的优先级
说明:高优先级的线程要抢占低优先级线程CPU的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只当高优先级的线程执行完以后,低优先级的线程才执行。
线程通信:wait() / notify() / notifyAll() :此三个方法定义在Object类中的
线程创建
方式一:继承Tread类
Java虚拟机允许应用程序同时执行多个执行线程,通过java.lang.Thread
类实现
- 创建一个继承与Tread类的子类
- 重写Tread中的run()方法
- 创建Tread类的子类的对象
- 通过此对象调用start()
例如:
//打印100以内的偶数
public class Main {
public static void main(String[] args) {
MyTread myTread = new MyTread();
myTread.start();
}
}
class MyTread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++){
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
网图下载
方式二:实现Runnable接口(开发中优先选择)
- 创建一个实现Runnable接口的类
- 实现run()方法
- 创建实现类的对象
- 将此对象作为参数传递到Tread类的构造器中,创建Tread类的对象
- 调用start()方法启动线程
public class Main {
public static void main(String[] args) {
MyTread myTread = new MyTread();
Thread thread = new Thread(myTread);
thread.start();
}
}
class MyTread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++){
if (i % 2 == 0){
System.out.println(i);
}
}
}
}
例如:
//龟兔比赛
public class Main {
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"rabbit").start();
new Thread(race,"tortoise").start();
}
}
class Race implements Runnable{
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++){
//模拟兔子睡觉
if (Thread.currentThread().getName().equals("rabbit") && i%10==0){
try {
Thread.sleep(200);
}catch (InterruptedException e){
e.printStackTrace();
}
}
//判断比赛是否结束
boolean flag = gameover(i);
//比赛结束,停止程序
if (flag){
break;
}
System.out.println(Thread.currentThread().getName() + "跑了" + i);
}
}
//判断是否完成比赛
private boolean gameover(int steps){
//判断是否有胜利者
if (winner != null){
return true;
}{
if (steps >= 100){
winner = Thread.currentThread().getName();
System.out.println("winner is " + winner);
return true;
}
}
return false;
}
}
方式三:实现Callable接口
- 实现Callable接口,需要返回值类型
- 重写call方法,需要抛出异常
- 创建目标对象:
- 创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(1)
- 提交执行:
Future<Boolean> result11 = ser.submit(t1)
- 获取结果:
boolean r1 = result1.get()
- 关闭服务:
set.shutdownNow()
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) {
MyTread myTread = new MyTread();
FutureTask futureTask = new FutureTask(myTread);
new Thread(futureTask).start();
try {
//get()返回值为FutureTask构造参数Callable实现类重写的call()的返回值
Object sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyTread implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
好处:
- 可以定义返回值
- 可以抛出异常
- 支持泛类的的返回值
- 借助Future Task类
方式四:线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程对性能影响很大
解决方案:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用
好处:
-
提高响应速度(减少创建新线程的时间)
-
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
-
便于线程管理
corePoolSize:核心池的大小 maximumPoolSize:最大线程数 keepAliveTime:线程没有任务时最多保持的时间
实现方法:
- 提供指定线程数量的线程池
- 执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
- 关闭连接池
线程池相关的 API:ExecutorService
和Excutors
-
ExecutorService
void execute(Runnable command):执行任务/命令,没有返回值 <T>Future<T>submit(Callable<T>task):执行任务,有返回值 void shutdown():关闭连接池
-
Excutors
:工具类、线程池的工厂类,用于创建和返回不同类型的线程池Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池 Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池 Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池 Executors.newScheduledThreadPool(n):创建一个线程池,它可以安排在给定延迟后运行命令或定期执行
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool参数为池子大小
ExecutorService service = Executors.newFixedThreadPool(10);
//执行
service.execute(new MyTread());
service.execute(new MyTread());
service.execute(new MyTread());
//2.关闭连接
service.shutdown();
}
}
class MyTread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
代理模式
静态代理模式:真实对象和代理对象要实现同一个接口,代理对象要代理成真实角色
Lamda表达式
避免匿名内部类定义过多
new Thread(()->System.out.println("多线程")).start();
函数式接口:任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
public interface Runnable{
public default void run();
}
对于函数式接口,可以通过lambda表达式来创建该接口的对象
public class Main {
public static void main(String[] args) {
ILove love = null;
love = a-> System.out.println("i love you--->" + a);
love.Love(520);
}
}
interface ILove{
void Love(int a);
}
线程的生命周期
新建,就绪,运行,阻塞,死亡
void start();//启动线程,并执行对象的run方法
run();//线程在被调用时的操作
String getName();//返回线程的名字
void setName(String name);//设置该线程的名字
static Tread current Tread();//返回当前线程,在Tread子类中就是this,通常用于主线程和Runnable实现类
static yield();//线程让步,释放当前CPU执行
join();//
static void sleep(long millitime);//让当前线程“睡眠”指定的millitime毫秒,抛出InterruptedException异常
stop();//强制线程生命周期结束
boolean isAlive();//返回boolean,判断线程是否活着
守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕,不用等待守护线程执行完毕
线程的同步
多个线程执行的不确定性引起结果不确定性,多个线程对账本的共享,会造成操作的不完全性,会破坏数据
synchronized关键字,包括synchronized方法和synchronized块
方法一:同步代码块
synchronized(同步监视器){
//需要被同步的代码(操作共享数据的代码,共享数据:多个线程共同操作的变量)
}
Obj称为同步监视器
同步监视器俗称锁,任何一个类的对象都可以充当锁,多个线程公用一把锁
方法二:同步方法
public synchronized void method(int args){}
synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则会阻塞,方法执行,占用该锁,方法返回释放锁
非静态的同步方法,同步监视器是this,静态的同步方法,同步监视器是当前类本身
死锁
死锁产生的四个必要条件:
- 互斥条件
- 请求与保持条件
- 不剥夺条件
- 循环等条件
Lock(锁)
通过显示定义同步锁对象来实现同步,同步锁使用Lock对象充当
java.util.concurrent.locks.Lock
接口是控制多个线程对共享资源进行访问的工具,每个只能有一个线程对Lock对象加锁,线程开始访问共享资源前应该先获得Lock对象
ReentrantLock
实现了Lock,有与synchronized相同的并发性和内存语义,可以显示加锁,释放锁
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args) {
TestLock testLock = new TestLock();
new Thread(testLock).start();
new Thread(testLock).start();
new Thread(testLock).start();
}
}
class TestLock implements Runnable{
int ticketNums = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();
if (ticketNums > 0){
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(ticketNums--);
}else {
break;
}
}finally {
//解锁
lock.unlock();
}
}
}
}
Lock是显示锁(手动开启和关闭锁),synchronized是隐式锁,出了作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM调线程花费时间少,有更多的扩展性
优先使用顺序:Lock>同步代码块>同步方法
线程的通信
wait():一旦执行该方法,当前线程进入阻塞状态,并释放同步监视器
wait(long timeout):指等待的毫秒数
notify():一旦执行该方法,就会唤起被wait的一个线程,如果有多个线程,唤醒优先级高的
notifyAll():一旦执行该方法,就会唤醒所有wait的线程
- 只能出现在同步代码块或同步方法中
- 必须是同步代码块或同步方法中的同步监视器,否则会出现
IllegalMonitorStateException
异常 - 定义在
java.lang.Object
类中
sleep()和wait()的异同:
- 相同点:一旦执行该方法,都可以使当前线程进入阻塞状态
- 不同点:
- 声明位置不同:Tread中声明sleep,Object中声明wait
- 调用范围(要求)不同:sleep可以在任何需要的场景下调用,wait只能出现在同步代码块或同步方法中
- 关于释放同步监视器:若均使用在同步代码块或同步方法中,sleep不会释放锁,wait会释放锁
生产消费者问题
线程同步问题,生产者和消费者共享一个资源,互相依赖互为条件
synchronized可阻止并发更新同一个共享资源,实现了同步;不能用来实现不同线程之间的消息传递(通信)
解决方法:
-
管程法
生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
缓存区:消费者不能直接使用生产者的数据,存在“缓冲区”
生产者将生产好的数据放入缓冲区,消费者从缓冲区取出数据
public class Main { public static void main(String[] args) { SynContainer container = new SynContainer(); new Productor(container).start(); new Consumor(container).start(); } } //生产者 class Productor extends Thread{ SynContainer container; public Productor(SynContainer container){ this.container = container; } //生产 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("生产了:" + i); container.push(new Chicken(i)); } } } //消费者 class Consumor extends Thread{ SynContainer container; public Consumor(SynContainer container){ this.container = container; } //消费 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("消费了:" + container.pop().id); } } } //产品 class Chicken{ int id;//产品编号 public Chicken(int id){ this.id = id; } } //缓冲区 class SynContainer{ //容器大小 Chicken[] chickens = new Chicken[10]; //容器计数器 int count = 0; //生产者放入产品 public synchronized void push(Chicken chicken){ //容器满了 if (count == chickens.length){ //通知消费者消费,生产等待 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //容器没有满 chickens[count] = chicken; count++; //可以通知消费者消费 this.notifyAll(); } //消费者消费 public synchronized Chicken pop(){ //判断是否可以消费 if (count == 0){ //等待生产者生产,消费者等待 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //若可以消费 count--; Chicken chicken = chickens[count]; //通知生产者生产 this.notifyAll(); return chicken; } }
-
信号灯法
通过标志位解决
public class Main { public static void main(String[] args) { TV tv = new TV(); new Actor(tv).start(); new Audience(tv).start(); } } //生产者-->演员 class Actor extends Thread{ TV tv = new TV(); public Actor(TV tv){ this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { if (i % 2 == 0){ this.tv.play("抖音"); }else { this.tv.play("快手"); } } } } //消费者-->观众 class Audience extends Thread{ TV tv = new TV(); public Audience(TV tv){ this.tv = tv; } @Override public void run() { for (int i = 0; i < 20; i++) { tv.watch(); } } } //产品-->节目 class TV{ //演员表演,观众等待 //观众观看,演员等待 String name;//节目名 boolean flag = true; //表演 public synchronized void play(String name){ if (flag != true){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("演员表演:" + name); //通知观众观看 this.notifyAll(); this.name = name; this.flag = !this.flag; } //观看 public synchronized void watch(){ if (flag == true){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("观众观看:" + name); //通知演员表演 this.notifyAll(); this.flag = !this.flag; } }