开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第12天,点击查看活动详情
简介
看起来是多个任务都在做,其实本质上我们的大脑在同一时间依旧只做了一件时间。
充分利用道路,加入多个车道
在操作系统中运行的程序就是进程。一个进程可以有多个线程,如视频中同时听声音,看图像,看弹幕等等。
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。进程则是执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位。通常一个进程中可以包含若干个线程,一个进程中至少有一个线程(main线程)。线程是CPU调度和执行的单位。
很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果模拟出来的多线程,即在一个cpu的情况下,在同一时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
每个线程在自己的工作区间内交互,内存控制不当会造成数据不一致
线程创建
继承Thread类
线程不一定立即执行,CPU安排调度
执行顺序是主线程调用线程之后不一定立即执行,由CPU安排调度。安排调度后是两线程个随机交叉执行
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
//总结:线程开启后不一定立即执行,由CPU调度执行
public class TestThread01 extends Thread{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 200; i++) {
System.out.println("我在看代码------" + i);
}
}
public static void main(String[] args) {
//main线程,主线程
//创建一个线程对象
TestThread01 testThread01 = new TestThread01();
//调用start()方法开启线程
testThread01.start();
for (int i = 0; i < 1000; i++) {
System.out.println("我在学习多线程------" + i);
}
}
}
{% note success %} 实例下载网图 {% endnote %}
1.创建lib项目,导入jar包
2.编写代码
//练习Thread,实现多线程同步下载图片
public class TestThread02 extends Thread {
private String url;//网络图片地址
private String name;//保存的文件名
public TestThread02(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public void run() {
WebDownloader1 webDownloader1 = new WebDownloader1();
webDownloader1.downloader(url, name);
System.out.println("下载了文件名为" + name);
}
public static void main(String[] args) {
TestThread02 t1 = new TestThread02("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png", "1.png");
TestThread02 t2 = new TestThread02("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png", "2.png");
TestThread02 t3 = new TestThread02("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png", "3.png");
t1.start();
t2.start();
t3.start();
}
}
//下载器
class WebDownloader1 {
//下载方法
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}
实现Runnable接口
执行顺序是主线程调用线程之后不一定立即执行,由CPU安排调度。安排调度后是两个随机交叉执行
{% note success %} 实例抢票——并发问题 {% endnote %}
线程延迟增大了问题的发生概率
实现Callable接口
//线程创建方式三:实现callable接口
/**
* 1。可以定义返回值
* 2.可以抛出异常
*/
public class TestThreadCallable implements Callable<Boolean> {
private String url;//网络图片地址
private String name;//保存的文件名
public TestThreadCallable(String url, String name) {
this.url = url;
this.name = name;
}
@Override
public Boolean call() {
WebDownloader1 webDownloader1 = new WebDownloader1();
webDownloader1.downloader(url, name);
System.out.println("下载了文件名为" + name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestThreadCallable t1 = new TestThreadCallable("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png", "1.png");
TestThreadCallable t2 = new TestThreadCallable("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png", "2.png");
TestThreadCallable t3 = new TestThreadCallable("https://qiniu.codekylin.cn/github/img/img/%E5%AE%87%E8%88%AA%E5%91%98.png", "3.png");
//创建执行服务
ExecutorService es = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = es.submit(t1);
Future<Boolean> r2 = es.submit(t2);
Future<Boolean> r3 = es.submit(t3);
//获取结果
Boolean rs1 = r1.get();
Boolean rs2 = r2.get();
Boolean rs3 = r3.get();
//关闭服务
es.shutdown();
}
}
//下载器
class WebDownloader1 {
//下载方法
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现问题");
}
}
}
线程方法
| 方法 | 说明 |
|---|---|
| setPriority(int newPriority) | 更改线程的优先级 |
| static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
| void join() | 等待该线程终止 |
| static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
| void interrupt | 中断线程(不使用这个方式) |
| boolean isAlive() | 测试线程是否处于活动状态 |
线程停止
//测试线程停止
//1.建议线程正常停止--->利用次数,不建议死循环
//2.建议使用标志位--->设置一个标志位
//3.不要使用stop或者destroy等过时或者JDK不建议使用的方法
public class TestStop implements Runnable {
//1.设置一个标识位
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
System.out.println("run.....Thread" + i++);
}
}
//2.设置一个公开的方法停止线程,转换标志位
public void stop() {
this.flag = false;
}
public static void main(String[] args) {
TestStop testStop = new TestStop();
new Thread(testStop).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main"+i);
if (i == 900) {
testStop.stop();
System.out.println("线程已经停止了");
}
}
}
}
线程休眠
sleep不会释放锁
//模拟倒计时
public class TestSleep2 {
public static void main(String[] args) throws InterruptedException {
//systemDate();
tenDown();
}
//打印当前系统的时间
public static void systemDate(){
Date startTime = new Date(System.currentTimeMillis());//获取系统当前时间
while (true){
try {
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
Thread.sleep(1000);
startTime = new Date(System.currentTimeMillis());//更新系统当前时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//倒计时
public static void tenDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if (num<=0){
break;
}
}
}
}
线程礼让
//测试礼让线程,礼让不一定成功。看cpu调度心情
public class TestYield {
public static void main(String[] args) {
MyYield yield = new MyYield();
new Thread(yield,"后裔").start();
new Thread(yield,"嫦娥").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();//线程礼让
System.out.println(Thread.currentThread().getName()+"线程停止执行");
}
}
线程强制执行
线程状态
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。
下图显示了一个线程完整的生命周期。
-
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
-
就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
-
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
-
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
- 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
- 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
- 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
-
死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
线程状态观测
线程优先级
优先级低只是意味着获得调度的概率低,并不会一定在优先级高之后被CPU调度。
守护线程
用户线程定义:平时用到的普通线程均是用户线程,当在Java程序中创建一个线程,它就被称为用户线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕
线程同步
多个线程操作同一个资源线程不安全
线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。
不安全的买票案例
同票:每个线程都有自己的工作空间,都与主存交互。将主存中的数据拷贝到工作区之后,还没或者正要进行将对数据的修改返回给主存时,此时的cpu调度了另一个线程对其进行同样的操作。导致拿到了同一张票。线程切换带来的原子性问题
负票:在票的临界处也就是最后一张票的时候,A线程被CPU调度执行进行买票,A线程延迟100毫秒。在这100毫秒的时间间隔里,线程B被CPU调度执行进行买票。此时的票数还是为1
没有return出去。然后B线程延迟,A线程延迟过后票数-1此时为0票。B线程延迟过后买票,票数-1此时0-1等于-1票。于是出现了负票。
//不安全的买票。会拿到同一张票,或者拿到负数
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket ticket = new BuyTicket();
new Thread(ticket, "苦逼的我").start();
new Thread(ticket, "努力的你").start();
new Thread(ticket, "可恶的黄牛").start();
}
}
class BuyTicket implements Runnable {
//票
private int tickeNums = 10;
boolean flag = true;//外部停止方式
@Override
public void run() {
//买票
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//加上synchronized
private void buy() throws InterruptedException {
//判断是否有票
if (tickeNums <= 0) {
flag = false;
return;
}
//模拟延时
Thread.sleep(100);
//买到票
System.out.println(Thread.currentThread().getName() + "拿到" + tickeNums-- + "票");
}
}
不安全的银行取钱
public class UnsafeBank {
public static void main(String[] args) {
Account account = new Account(100, "积蓄");
Drawing you = new Drawing(account, 50, "你");
Drawing girlFriend = new Drawing(account, 100, "女朋友");
you.start();
girlFriend.start();
}
}
//账户
class Account{
int money;//余额
String name;//卡名
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行:模拟取款
class Drawing extends Thread{
Account account;//账户
//取了多少钱
int drawingMoney;
//现在手里有多少钱
int nowMoney;
public Drawing(Account account,int drawingMoney,String name){
super(name);
this.account = account;
this.drawingMoney= drawingMoney;
}
//取钱
@Override
public void run() {
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内余额 = 余额 - 你取的钱
account.money = account.money-drawingMoney;
//你手里的钱
nowMoney = nowMoney + drawingMoney;
System.out.println(account.name+"余额为:"+account.money);
// Thread.currentThread().getName() = this.name 继承了thread
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
案例:女朋友取款100,你取款50。银行卡余额100,却出现你两都取钱成功,而余额为负数的结果。
出现负数的解释同买票出现负数原因一样。在对余额进行操作时A线程延迟,此时B线程被CPU调度执行取钱,延迟。A线程取钱成功余额减去对应的数目(假设取钱50:100-50=50),B线程进行取钱成功余额减去对应的数目(假设取钱100:50-100=-50)。
线程不安全的集合
集合个数本来应该是10000的,出现了少了1个的原因是。当某个线程正在执行向集合添加元素的时候。同一时间CPU调度执行了另一个线程向集合添加元素,添加成功。此时刚才那个线程也向集合中添加元素,在list的同一位置,旧的元素值被新的覆盖。买票同票问题
同步方法
方法里面也有分只读和修改的代码块,这时候使用同步方法会浪费资源
改造不安全买票,使用synchronized
同步方法的同步监视器是默认是this
同步块
解决不安全的银行取钱案例。首先我们还是使用同步方法
说明我们并没有锁正确。原因是因为同步方法默认锁的是this对象,在这里也就是Drawing对象。而我们操控的是账户对象的值,所以导致并没有起作用。
使用同步块,对account对象进行锁定
还可以对当前账户的余额进行判断,从而优化我们的代码
解决线程不安全的集合。
同步是高开销的操作,因此尽量减少同步的内容。通常没有必要同步整个方法,同步部分代码块即可。 同步方法默认用this或者当前类class对象作为锁。 同步代码块可以选择以什么来加锁,比同步方法要更颗粒化,我们可以选择只同步会发生问题的部分代码而不是整个方法。
死锁
多个线程互相抱着对方需要的资源同时又请求对方的资源,然后形成僵持
程序一直在运行,僵持
如何解决死锁?很简单我们请求对方的资源的同时,放下手里的资源
//死锁:多个线程互相抱着对方需要的资源同时又请求对方的资源,然后形成僵持
public class TestDeadLock {
public static void main(String[] args) {
MakeUp g1 = new MakeUp(0, "灰姑凉");
MakeUp g2 = new MakeUp(1, "白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick {
}
//镜子
class Mirror {
}
class MakeUp extends Thread {
//需要的资源只有一份,用static来保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice; //选择
String girlName;//使用化妆品的人
MakeUp(int choice, String girlName) {
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void makeup() throws InterruptedException {
if (choice == 0) {
synchronized (lipstick) {//获得口红的锁
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(1000);
}
synchronized (mirror) {//一秒后想要镜子
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(1000);
}
} else {
synchronized (mirror) {//获得镜子的锁
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(1000);
}
synchronized (lipstick) {//一秒后想要口红的锁
System.out.println(this.girlName + "获得口红的锁");
}
}
}
}
Lock锁
public class TestLock {
public static void main(String[] args) {
TestLock2 testLock2 = new TestLock2();
new Thread(testLock2).start();
new Thread(testLock2).start();
new Thread(testLock2).start();
}
}
class TestLock2 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();//解锁
}
}
}
}
线程协作
| 方法名 | 作用 |
|---|---|
| wait() | 表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁 |
| wait(long timeout) | 指定等待的毫秒数 |
| notify() | 唤醒一个处于等待状态的线程 |
| notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
管程法
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据
//测试:生产者消费者模型-->利用缓冲区解决:管程法
//生产者,消费者,产品,缓冲区
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Product(container).start();
new Consumer(container).start();
}
}
//生产者
class Product extends Thread {
SynContainer container;
public Product (SynContainer container){
this.container=container;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了"+1+"只鸡");
}
}
}
//消费者
class Consumer extends Thread {
SynContainer container;
public Consumer (SynContainer container){
this.container=container;
}
//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
Chicken chicken = container.pop();
System.out.println("消费了"+1+"只鸡");
}
}
}
//产品
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 {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果没有满,我们就需要丢入产品
chickens[count] = chicken;
count++;
//可以通知消费者消费了
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop(){
//判断是否能消费,缓冲区为空
if (count==0){
//等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//可以消费,缓冲区不为空
count--;//count=10。数组下标最大为
Chicken chicken = chickens[count];
//吃完了,通知生产者生产
this.notifyAll();
return chicken;
}
}
信号灯法
通过标志位来判断进行操作和等待操作
//测试生产者消费者问题2 ,信号灯法。标志位
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者--->演员
class Player extends Thread {
TV tv;
public Player(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 Watcher extends Thread{
TV tv;
public Watcher(TV tv){
this.tv=tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//产品-->节目
class TV {
//演员表演,观众等待 T
//观众观看,演员等待 F
String voice;//表演界面
boolean flag = true;//标志位
//演员表演
public synchronized void play(String voice) {
if (!flag) {//flag为false
//等待观众观看
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("演员表演了:" + voice);
//通知观众观看
this.notifyAll();//通知唤醒
this.voice = voice;
this.flag = !this.flag;
}
//观众表演
public synchronized void watch() {
if (flag) {//flag为true
//等待演员表演
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("观看了" + voice);
//通知演员观看
this.notifyAll();//通知唤醒
this.flag = !this.flag;
}
}
线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//测试线程池
public class TestPool {
public static void main(String[] args) {
//1.创建服务,创建线程池
//newFixedThreadPool 参数为线程池的大小
ExecutorService service = Executors.newFixedThreadPool(10);
//2.执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
//3.关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}