线程与进程
线程是什么?
- 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程
- 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程
进程是什么?
指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
线程调度
分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
抢占式调度
- 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java中默认使用的为抢占式调度
- CPU使用抢占式调度模式在多个线程间进行着告诉的切换。对于CPU的一个核心而言,只能够执行一个线程,而CPU的在多个线程间切换速度相对于我们的感觉要快很多,看上去就是在同一时刻运行。实际上,多线程程序并不能提高程序的运行速度,但是能够提高程序运行效率,让CPU的使用率更高。
同步与异步
同步:排队执行,效率低但安全。
异步:同时执行,效率高但不安全。
并发与并行
并发:指两个或多个事件在同一个时间段内发生
比如:一秒内的完成了5000次下单
并行:指两个或多个事件在同一个时刻发生(同时发生)
比如:在同一时刻打印了两句话
线程的创建方式
- 继承Thread类
- 实现Runnable类
继承Thread类方式
实现步骤:
- 创建一个类
- 继承Thread类
- 重写run()方法
代码实现
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
注意点:
开启线程是调用start()方法,而不是直接调用run方法
实现Runnable接口方式
实现步骤:
- 创建一个类实现Runnable接口
- 实现run()方法
代码
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
Thread类常用方法
-
public static Thread currentThread()
返回当前正在执行的线程对象的引用,返回的是一个Thread类型
-
public String getName()
返回当前线程的名称
-
public void interrupt()
中断线程:如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,并抛出
nterruptedException
异常。 -
public static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。
-
publilc void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。
-
public void setName(String name)
改变线程名称,使之与参数
name
相同。 -
public void setPriority(int newPriority)
更改线程的优先级。
优先级在1-10之间 MIN_PRIORITY:1 MAX_PRIORITY:10
线程安全问题
当两个或两个以上的线程去操作同一个变量时,就会出现不安全的问题
问题描述:
现在有A、B两个线程、变量num=100,当A线程拿到了num,并且此时num=100,但是这个时候,B线程来了,同时也拿到了num,此时num=100,但是B线程抢先一步A线程把num变成了99,而A拿到的num仍然是100,这个时候A拿这个值为100的num去操作就会出现数据不安全的问题,因为这个时候num应该为99,A线程应该拿num=99这个值取进行别的操作。
- 演示问题
public class SynchronizedDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(ticket).start();
new Thread(ticket).start();
new Thread(ticket).start();
}
}
public class Ticket implements Runnable{
public int num = 100;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (num>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println("剩余票数:"+num);
}
}
}
}
- 运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GxEOZzrq-1665656449414)(assets/image-20211120163717655.png)]
结果:出现重复值,理论上每个值都只出现一次,并且当票数等于0时,就不再继续打印
解决方法
- 同步代码块
- 同步方法
- 同步锁Lock
同步代码块
代码实现
public class Ticket implements Runnable{
/**
* 现在有一百张车票
*/
public int num = 100;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//在此添加
synchronized (this) {
if (num>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println(Thread.currentThread().getName()+"线程卖出一张票,剩余票数:"+num);
}
}
}
}
}
- 运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Qw2WoO1f-1665656449415)(assets/image-20211120164234845.png)]
发现没有重复被消费的问题
同步方法
在需要的方法上加synchronized修饰符即可
此处需要新建一个sale()方法,在此方法上加synchronized
如果在run在加那么整个卖票操作就会被一个线程执行完,无法起到多条线程同时出售的效果
public class Ticket implements Runnable{
public int num = 100;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
sale();
}
}
public synchronized void sale(){
if (num>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println(Thread.currentThread().getName()+"线程正在卖票,剩余票数:"+num);
}
}
}
同步锁Lock
此处为了规范,加入了try catch代码块,lock.lock()方法之后紧跟try代码块,lock.unlock()在finally中释放
获取Lock的方法,Lock lock = new ReentrantLock();
lock.lock()表示上锁
lock.unlock()表示释放锁
public class Ticket implements Runnable{
public int num = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
for (int i = 0; i < 100; i++) {
sale();
}
}
public void sale(){
lock.lock();
try {
if (num>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
num--;
System.out.println(Thread.currentThread().getName()+"线程正在卖票,剩余票数:"+num);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
公平锁与非公平锁
-
公平锁
依次排队执行,先到先得
实现方式:创建Lock时添加参数
true
Lock lock = new ReentrantLock(
true
); -
非公平锁
谁抢到谁就先执行,以上的三种解决安全问题的方法都是非公平锁
线程死锁
假如有A、B两个线程,线程A需要等待线程B回应才可执行,线程B也要等线程A回应才可执行,此时就僵持住了,也就造成了线程死锁
解决方式:不要使用可以带锁的线程,去调用另一个会带锁的线程
多线程通信问题
生产者与消费者案例
需求:生产者生产一份食物,消费者就消费一份食物。生产者生产时,消费者不能消费。消费者需要等生产者生产完毕之后才能消费。
有问题的代码
public class Demo {
public static void main(String[] args) {
Food food = new Food();
new Consumer(food).start();
new Product(food).start();
}
static class Consumer extends Thread{
private Food food;
public Consumer(Food food) {
this.food=food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
food.get();
}
}
}
static class Product extends Thread{
private Food food;
public Product(Food food) {
this.food=food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0) {
food.setNameAndTaste("小鸡炖蘑菇","甜甜");
} else {
food.setNameAndTaste("镇江牛肉面","香辣");
}
}
}
}
static class Food{
private String name;
private String taste;
public void setNameAndTaste(String name,String taste){
this.name=name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste=taste;
}
public void get(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者消费了:"+name+",味道为:"+taste);
}
}
}
- 运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5bX1Mbkm-1665656449416)(assets/image-20211120172029382.png)]
问题描述:
这种方式导致了生产时没生产完成,消费者就消费了。
即生产小鸡炖蘑菇时,上一份的镇江牛肉面还没生产完成,此时菜名name=“小鸡炖蘑菇”,味道taste=“香辣”,在这个瞬间,去调用get方法,就会出现以上运行的情况
解决了问题的代码
public class Demo {
public static void main(String[] args) {
Food food = new Food();
new Consumer(food).start();
new Product(food).start();
}
static class Consumer extends Thread{
private Food food;
public Consumer(Food food) {
this.food=food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
food.get();
}
}
}
static class Product extends Thread{
private Food food;
public Product(Food food) {
this.food=food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i%2==0) {
food.setNameAndTaste("小鸡炖蘑菇","甜甜");
} else {
food.setNameAndTaste("镇江牛肉面","香辣");
}
}
}
}
static class Food{
private String name;
private String taste;
//表示此时生产者可以生产
private boolean flag=true;
public synchronized void setNameAndTaste(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) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者消费了:"+name+",味道为:"+taste);
//此时产品已被消费,生产者可生产
flag=true;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
- 运行结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-milHTayt-1665656449417)(assets/image-20211120173647695.png)]
线程池
缓存线程池
使用 Executors.newCachedThreadPool() 创建一个线程池对象
使用execute方法添加任务并自动执行
public class ExecutorDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行了");
}
});
}
}
定长线程池
使用Executors.newFixedThreadPool()创建一个线程池对象
使用execute方法添加任务并自动执行
public class ExecutorDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行了");
}
});
}
}
单线程线程池
线程池中只有一个线程,每次执行都是相同的线程
Executors.newSingleThreadExecutor();
public class ExecutorDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行了");
}
});
}
}
周期定长线程池
public class ExecutorDemo {
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);
//定时执行一次
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println("执行了 只执行一次");
}
},1, TimeUnit.SECONDS);
//定时多久执行一次
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("执行了 每秒一次");
}
},2,1, TimeUnit.SECONDS);
}
}