java多线程基础学习[狂神说java-多线程笔记]
java多线程基础学习
- 一、线程简介
- 1.类比
- 2.程序进程线程
- 3.线程的核心概念
- 二、线程的实现(重点)
- 调用方法与调用多线程的区别
- Thread 类
- 1.thread使用方法
- 2. 代码实现
- 3.应用
- Runnable 接口
- 1.Runable使用方法
- 2. 代码实现
- 3.应用
- Callable接口
- 1.Callable使用方法
- 2.代码实现
- 三、线程的状态
- 线程的5个状态
- 线程方法
- 观察线程的状态
- 线程的优先级
- 守护线程(daemon)
- 四、线程同步
- 同步方法与同步块
- 死锁
- Lock(锁)
- 五、线程协作
- 生产者消费者模式
- 线程池
一、线程简介
所有的资料来源都是以【狂神说Java】多线程详解为基础
【狂神说Java】多线程详解
建议看视频,这篇博客是笔记
同时要结合目录看
1.类比
类比于现实中的的多任务,在同一个时间段,同时做几件事情
在程序中就是主线程与子线程同时进行任务
2.程序进程线程
在操作系统中的运行的程序就是进程,而进程通常采用多进程执行
程序
指令和数据的有序集合,本身没有任何运行的含义,是静态概念
进程
是执行程序的一次执行过程,是一个动态概念,是系统资源分配的单位
线程
一个进程通常包括多个线程,线程是CPU调度和执行的单位
3.线程的核心概念
二、线程的实现(重点)
调用方法与调用多线程的区别
run()方法是需要执行的方法体
start()方法是开启多线程的方法体
Thread 类
不建议使用:避免OOP单继承局限性
1.thread使用方法
- 自定义线程类继承Thread类
- 重写run方法,编写线程执行体
- 在主线程中创建线程对象,使用start方法启动线程
2. 代码实现
package cn.livorth;public class TestThread01 extends Thread{@Overridepublic void run() {//在run()方法中编写执行体for (int i = 0; i < 100; i++) {System.out.println("在子线程中执行--" + i);}}public static void main(String[] args) {//在主线程中创建线程对象,使用start方法启动线程TestThread01 testThread = new TestThread01();testThread.start();for (int i = 0; i < 100; i++) {System.out.println("在主线程中执行---" + i);}}
}
效果如下:
并行交替执行,轮流占用cpu
3.应用
样例来自《狂神说java-多线程详解》
代码(详解在注释):
package cn.livorth;import org.apache.commons.io.FileUtils;import java.io.File;
import java.io.IOException;
import java.net.URL;public class TestThread02 extends Thread{private String url;private String filename;//构造函数传入所需的url和filenamepublic TestThread02(String url, String filename) {this.url = url;this.filename = filename;}@Overridepublic void run() {//调用downLoader来获取文件webDownLoader webDownLoader = new webDownLoader();webDownLoader.downLoader(url, filename);System.out.println(filename + "下载完成");}public static void main(String[] args) {//传入三张图图片的urlTestThread02 t1 = new TestThread02("https://i0.hdslb.com/bfs/album/" +"481ba607ba31e9932b90e383f3698fec4c1d9577.jpg@518w_1e_1c.jpg", "pic01.jpg");TestThread02 t2 = new TestThread02("https://i0.hdslb.com/bfs/album/" +"98d71302c030aa86258eb17a5db084bfadf8ff39.jpg@518w_1e_1c.jpg", "pic02.jpg");TestThread02 t3 = new TestThread02("https://i0.hdslb.com/bfs/album/" +"70849f611883c3e7feffc730c4f1e7b7173c9695.jpg@518w_1e_1c.jpg", "pic03.jpg");//开始多线程下载t1.start();t2.start();t3.start();}
}class webDownLoader
{/*** 通过url获取文件内容,并保存* @param url* @param name*/public void downLoader(String url, String name){try {//调用Commons_io包里面的方法copyURLToFileFileUtils.copyURLToFile(new URL(url), new File(name));} catch (IOException e) {e.printStackTrace();System.out.println("IO异常,downLoader出现问题");}}
}
结果:
这只是一个简单的对多线程的小实践,事实上通过这样获取图片还是有诸多问题
Runnable 接口
推荐使用:避免单继承的局限性,方便同一个对象被多个进程调用
1.Runable使用方法
- 自定义MyRunable类实现Runable接口
- 重写run方法,编写线程执行体
- 在主线程中创建线程对象,使用start方法启动线程
2. 代码实现
package cn.livorth;public class TestThread03 implements Runnable{@Overridepublic void run() {//在run()方法中编写执行体for (int i = 0; i < 100; i++) {System.out.println("在子线程中执行--" + i);}}public static void main(String[] args) {//创建Runable接口的实现类对象TestThread03 testThread = new TestThread03();//创建线程对象,通过线程对象来开启线程,也就是代理Thread thread = new Thread(testThread);thread.start();//也可以缩写为://new Thread(testThread).start();for (int i = 0; i < 100; i++) {System.out.println("在主线程中执行---" + i);}}
}
效果如下:
在实现的效果上是没有区别的:
3.应用
按照狂神说的那个样例来的话,会出现问题
对个线程操作同一个资源的时候,线程不安全,数据会紊乱
所以不写了
Callable接口
仅作了解即可
1.Callable使用方法
通过服务提交线程
- 实现Callable接口,需要返回值类型
- 重写call方法, 需要抛出异常
- 创建目标对象
- 创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);
- 提交执行: Future result1 = ser.submit(t1);
- 获取结果: boolean r1 = result1.get()
- 关闭服务: ser.shutdownNow();
2.代码实现
改自Thread中的应用
《狂神说java-多线程详解》-P8
package cn.livorth;import java.util.concurrent.*;public class TestCallable01 implements Callable<Boolean> {private String url;private String filename;//构造函数传入所需的url和filenamepublic TestCallable01(String url, String filename) {this.url = url;this.filename = filename;}@Overridepublic Boolean call() {//调用downLoader来获取文件webDownLoader webDownLoader = new webDownLoader();webDownLoader.downLoader(url, filename);System.out.println(filename + "下载完成");return true;}public static void main(String[] args) {//传入三张图图片的urlTestCallable01 t1 = new TestCallable01("https://i0.hdslb.com/bfs/album/" +"481ba607ba31e9932b90e383f3698fec4c1d9577.jpg@518w_1e_1c.jpg", "pic01.jpg");TestCallable01 t2 = new TestCallable01("https://i0.hdslb.com/bfs/album/" +"98d71302c030aa86258eb17a5db084bfadf8ff39.jpg@518w_1e_1c.jpg", "pic02.jpg");TestCallable01 t3 = new TestCallable01("https://i0.hdslb.com/bfs/album/" +"70849f611883c3e7feffc730c4f1e7b7173c9695.jpg@518w_1e_1c.jpg", "pic03.jpg");//创建执行服务(参数为线程池的大小)ExecutorService service = Executors.newFixedThreadPool(3);//提交执行Future<Boolean> r1 = service.submit(t1);Future<Boolean> r2 = service.submit(t2);Future<Boolean> r3 = service.submit(t3);//获取结果(不写应该也是可以的,这里应该是作为一种检验参数)try {boolean rs1 = r1.get();boolean rs2 = r2.get();boolean rs3 = r3.get();System.out.println(rs1);System.out.println(rs2);System.out.println(rs3);} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}//关闭服务service.shutdown();}
}
结果:
三、线程的状态
线程的5个状态
- 创建状态:Thread t = new Thread();
- 就绪状态:调用start方法
- 运行状态:被cpu调度执行
- 阻塞状态:调用sleep、wait或同步锁定时,进入阻塞状态
- 死亡状态:线程中断或者结束,一旦死亡就不再启动
线程方法
方法 | 说明 |
---|---|
setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | 在指定的毫秒数内让当前正在执行的线程休眠 |
void join() | 等待该线程终止 |
static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程,别用这个方式 |
boolean isAlive() | 测试线程是否处于活动状态 |
停止线程
不推荐使用JDK提供的stop()、destroy()。
推荐让线程自己停下来,但也不建议使用死循环
建议使用一个标志位作为终止变量,当flag==false时,线程终止运行
package cn.livorth.state;public class TestStop implements Runnable{private boolean flag = true;@Overridepublic void run() {int i = 0;while(flag){System.out.println("子进程执行中---" + i++);}}//设置个公开方法,利用标志位停止线程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 < 100; i++) {System.out.println("主进程执行中---" + i);if(i == 90){testStop.stop();System.out.println("子进程结束运行");}}} }
线程休眠
sleep()方法的单位是毫秒
sleep()存在异常InterruptedException
sleep完了线程进入就绪状态,不会直接进入执行态
可以用于模拟网络延时,倒计时等
每个对象都有一个锁,sleep不释放锁
try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}
模拟网络延时可以放大问题的发生性
线程礼让
- 礼让不一定成功,yield方法是当前正在执行的进程进入就绪态,等待cpu的调度
public void run() {System.out.println(Thread.currentThread().getName()+"线程开始执行");Thread.yield();//礼让System.out.println(Thread.currentThread().getName()+"线程停止执行"); }
Join
join合并线程,只能是当前线程执行完之后才能执行其他线程,对其他线程造成阻塞
package cn.livorth.state;public class TestJoin implements Runnable{@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("子线程执行---" + i);}}public static void main(String[] args) {TestJoin testJoin = new TestJoin();Thread thread = new Thread(testJoin);thread.start();for (int i = 0; i < 500; i++) {if(i == 200){try {//阻塞主线程,使子线程优先执行完thread.join();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("主线程执行---" + i);}} }
观察线程的状态
代码样例:
package cn.livorth.state;public class TestState {public static void main(String[] args) {Thread thread = new Thread(()->{for (int i = 0; i < 5; i++) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("******");}});//启动前Thread.State state = thread.getState();System.out.println(state);//NEW//启动后thread.start();state = thread.getState();System.out.println(state);//RUNABLE//阻塞时与结束时while(state != Thread.State.TERMINATED){try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}state = thread.getState();//TIMED_WAITING或者TERMINATEDSystem.out.println(state);}}
}
结果样例:
线程的优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程的优先级用数字表示,范围从1~10.
- Thread.MIN_ PRIORITY= 1;
- Thread.MAX PRIORITY = 10;
- Thread.NORM_ PRIORITY= 5;
使用以下方式获取或改变优先级
- getPriority() ,setPriority(int xxx)
注意:
先设置优先级再启动
main方法的默认优先级为5
理论上来说优先级越高的越先执行,哪怕他start更晚
守护线程(daemon)
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕.
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等待…
代码示例:
package cn.livorth.state;public class TestDaemon {public static void main(String[] args) {God god = new God();You you = new You();Thread thread = new Thread(god);//将上帝设置为守护线程,在false的时候为用户线程thread.setDaemon(true);thread.start();//开启一般的用户线程new Thread(you).start();}
}class God implements Runnable{@Overridepublic void run() {//上帝永生,无限循环while (true)System.out.println("上帝在你身旁");}
}class You implements Runnable{@Overridepublic void run() {//人生有限,100年后撒手人寰for (int i = 0; i < 100; i++) {System.out.println("happy everyday");}System.out.println("goodbye world");}
}
结果:
上帝明明是个死循环,但是进程还是结束了。
因为这里将上帝线程设置为了守护线程,虚拟机不加以考虑
四、线程同步
并发:同一个对象被多个线程同时操作,也就是不同线程同时操作同一个资源地址,造成数据紊乱
同步:多个需要同时访问资源的线程进入对象的等待池,等待前面线程使用完毕
锁:每个对象都有把锁,当获取对象时,独占资源,其他线程必须等待,使用结束后才释放
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题
同步方法与同步块
synchronized关键字
synchronized默认锁的是他自身的对象,要是跨对象,通常使用同步块,即锁共享资源所在的对象
同步方法
public synchronized void method(int args){}
因为每个对象对应一把锁,使用加了关键字之后,方法一旦执行就独占该锁
缺陷:若将一个大的方法申明为synchronized将会影响效率
同步块
synchronized(Obj){}
- Obj称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this ,就是这个对象本身或者是class [反射中讲解]
- 同步监视器的执行过程:
- 第一个线程访问,锁定同步监视器,执行其中代码.
- 第二个线程访问 ,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕 ,解锁同步监视器.
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
死锁
多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形. 某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题.
我的理解:
张三占用着A资源,等着B资源,李四占用着B资源,等着A资源
张三同时有AB资源才能完成任务,李四也一样,但是两个人谁都不会先放手
然后就进入了死锁
产生死锁的四个必要条件:
- 互斥条件: 一个资源每次只能被一个进程使用
- 请求与保持条件: 一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件: 进程已获得的资源,在末使用完之前,不能强行剥夺
- 循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系
只要破除任意一个就能避免死锁
Lock(锁)
通过显示定义同步锁对象(Lock)来实现同步
ReentrantLock类实现了Lock接口,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
演示代码:
package cn.livorth.gaoji;import java.util.concurrent.locks.ReentrantLock;public class TestLock {public static void main(String[] args) {TestLock2 testLock = new TestLock2();new Thread(testLock, "A").start();new Thread(testLock, "B").start();new Thread(testLock, "C").start();}}class TestLock2 implements Runnable{int count = 1000;//定义lock锁private final ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}while(true){try {//进入加锁状态lock.lock();if(count > 0)System.out.println(Thread.currentThread().getName() + "---" +count--);elsebreak;}finally {//解锁lock.unlock();}}}
}
说明:
在不加锁的情况下,ABC可能会同时操作到count,导致数据紊乱
在加了锁之后,ABC排队操作
五、线程协作
生产者消费者模式
有一说一这个我们上个学期的操作系统讲的很详细了
生产者与消费者共享一个资源,同时生产者与消费者相互依赖互为条件
- 生产者访问仓库,往里面放;消费者也要访问,但是只拿
主要是用以下方法达到通信的效果:
- wait():先释放资源,并开始等待
- wait(long timeout):释放资源等待timeout秒
- notify():唤醒一个处于等待状态的线程
- notifyAll():唤醒同一个对象上所有调用了wait()方法的线程,优先级高的优先调度
解决方式:
管程法
设置一个缓冲区,用于暂存数据
代码示例:
package cn.livorth.gaoji;import java.util.LinkedList; import java.util.Queue;//管程法解决 public class TestPC {public static void main(String[] args) {//创建缓冲区SynContainer container = new SynContainer();//双线程new Producer(container).start();new Consumer(container).start();} }//生产者 class Producer extends Thread{SynContainer container;public Producer(SynContainer container) {this.container = container;}@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("生产了第" + i + "件产品");container.push(new Products(i));}} }//消费者 class Consumer extends Thread{SynContainer container;public Consumer(SynContainer container) {this.container = container;}@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("消费了第" + container.pop().id + "件产品");}} }//产品 class Products{int id;public Products(int id) {this.id = id;} }//缓冲区 class SynContainer{Queue<Products> queue = new LinkedList<Products>();int count = 0;int size = 10;//生产者放入产品public synchronized void push(Products product){//容器满,等待消费者消费if(count == size){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//没满,我们则需要存入成品,并唤醒消费者count++;queue.offer(product);this.notifyAll();}//消费者消费产品public synchronized Products pop(){//容器为空,等待生产者生产if (count == 0) {try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}//容器有剩,取出物品,并告诉生产者可以生产了count--;Products productPoll = queue.poll();this.notifyAll();return productPoll;} }
信号灯法
设置一个标记位(类似于容量为1的管程法)
package cn.livorth.gaoji;//信号灯法解决 public class TestPC2 {public static void main(String[] args) {//创建缓冲区TheProduct theProduct = new TheProduct();//双线程new Producers(theProduct).start();new Consumers(theProduct).start();} }//生产者 class Producers extends Thread{TheProduct theProduct;public Producers(TheProduct theProduct) {this.theProduct = theProduct;}@Overridepublic void run() {for (int i = 0; i < 10; i++) {theProduct.push("产品" + i);}} }//消费者 class Consumers extends Thread{TheProduct theProduct;public Consumers(TheProduct container) {this.theProduct = container;}@Overridepublic void run() {for (int i = 0; i < 10; i++) {theProduct.pop();}} }//产品 class TheProduct{boolean flag = false;String product;//生产者生产,消费者等待public synchronized void push(String product){if(flag){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("生产者生产了" + product);this.product = product;this.flag = !this.flag;this.notifyAll();}//消费者消费,生产者等待public synchronized void pop(){if(!flag){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("消费者消费了" + product);this.flag = !this.flag;this.notifyAll();} }
线程池
- 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
- 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具
- 好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize: 最大线程数
- keepAliveTime: 线程没有任务时最多保持多长时间后会终止
简单的使用参照Callable的使用
并发进阶以后再学
java多线程基础学习[狂神说java-多线程笔记]相关推荐
- Java零基础学习难吗
java编程是入行互联网的小伙伴们大多数的选择,那么对于零基础的小伙伴来说Java零基础学习难吗?如果你是初学者,你可以很好的理解java编程语言.并不困难.如果你的学习能力比较高,那么你对Java的 ...
- 【J2ME 2D 游戏开发系列】◣HIMI游戏开发启蒙教程◢JAVA零基础学习J2ME游戏开发全过程!...
本站文章均为 李华明Himi 原创,转载务必在明显处注明: 转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/j2me-2/774.html Himi从写 ...
- Java零基础学习全套视频笔记
Java零基础学习全套视频笔记 一.Java基础 1.注释 注释并不会被执行,是给我们写代码的人看的,防止项目结构代码太多忘记代码相关功能. 书写注释是一个非常好的习惯,平时写代码也一定要注意规范. ...
- 【Java】Java零基础学习笔记
文章目录 前言 思维导图 前期准备 卸载JDK 安装JDK Hello,world 可能遇到情况 java程序运行机制 IDEA的安装 java基础部分 基础语法 运算符 包机制 javaDoc文档手 ...
- Java零基础学习Java编程语言基础知…
很多Java编程初学者在刚接触Java语言程序的时候,不知道该学习掌握哪些必要的基础知识.下面就说说Java零基础学习Java编程语言基础知识的几个要点.希望能够对Java编程基础入门学习的新手有帮助 ...
- java入门基础学习(三)
文章目录 (一)有返回值的方法 (二)方法重载 习题 (一)有返回值的方法 格式:public static 返回值数据类型 方法名(参数){方法体return 数据;} 注意:1.返回值数据类型非v ...
- java编程基础学习需要多久的时间
Java是当前世界颇为流行的编程语言之一,很多想学习java的人都会听过一句话,先学好java基础,在考虑是自学还是培训学习,同时新的问题出现了,java基础包括什么,需要学习多久呢,对于小白来说,想 ...
- Java零基础好学吗?Java该怎么学?
Java零基础好学吗?Java该怎么学?在IT行业中,Java开发工程师是一个很吃香的职业,薪资水平也是几乎过万,许多人想转行Java开发,但又担心零基础能不能学会,学起来有多难,如果是零基础自学Ja ...
- 尚硅谷Java零基础极速入门七天版笔记
Java零基础极速入门 文章目录 Java零基础极速入门 1 Java快速入门 1.1计算机语言 1.2 Java语言 1.3 JVM 1.4 环境配置 2 基础语法 2.1 变量 2.2 标识符 2 ...
最新文章
- Packet Tracer 5.0配置cisco路由器详细说明
- c语言两个数组比较大小函数,输入两个数组,调用large函数比较,计数,输出统计结果...
- Java Web应用的生命周期
- c# 后台 添加datable 数据
- vue-codemirror基本用法:实现搜索功能、代码折叠功能、获取编辑器值及时验证
- 新概念英语(1-59)Is that all
- 深度学习工程师能力评估标准
- Win10彻底关闭Antimalware Service Executable占用内存过高问题
- C#一个FTP操作封装类FTPHelper
- SQL重复记录处理(查找,过滤,删除)
- 登陆模块防止恶意用户SQL注入攻击
- oracle数据库创建表空间和表临时空间
- php制作多媒体课件,网络自主学习型多媒体课件设计与制作PPT.ppt
- mysql手册05_存储过程和存储函数
- 合宙Air724UG二次开发(2):资料描述
- Laravel 下使用 FFmpeg 处理多媒体文件
- 软件相貌测试准确吗,测另一半的相貌超准软件 提前了解对象的外貌
- python等比例压缩图片_python使用pil进行图像处理(等比例压缩、裁剪)实例代码
- Git Re-Basin: Merging Models modulo Permutation Symmetries解读
- 食物也疯狂!KOOCAN盘点因为食物毁掉的中国电视剧