多线程知识点
多线程
- 线程就是独立的执行路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
- main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如cpu调度时间,并发控制开销。
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
注意:很多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
1.线程的实现
方式1:继承Thread类,重写run()方法
不建议使用:避免OOP单继承局限性
//创建线程方式1:继承Thread类,重写run()方法
//线程启动不一定立即执行,由CPU调度
public class TestThread01 extends Thread{
public static void main(String[] args) {
//main线程,主线程
//创建一个线程对象
TestThread01 testThread01=new TestThread01();
//调用start()方法开启线程
testThread01.start();
for (int i = 0; i < 1000; i++) {
System.out.println("玩游戏ing~~~~");
}
}
@Override
public void run() {
//run方法线程体
for (int i = 0; i <20 ; i++) {
System.out.println("学习~~~~");
}
}
}
- 多线程下载网络图片
//多线程下载图片
public class TestThread02 extends Thread{
private String name;
private String url;
//主线程
public static void main(String[] args) {
TestThread02 testThread02 = new TestThread02("1.png", "https://i0.hdslb.com/bfs/article/02db465212d3c374a43c60fa2625cc1caeaab796.png@progressive.png");
TestThread02 testThread021 = new TestThread02("2.png", "https://i0.hdslb.com/bfs/article/02db465212d3c374a43c60fa2625cc1caeaab796.png@progressive.png");
TestThread02 testThread022 = new TestThread02("3.png", "https://i0.hdslb.com/bfs/article/02db465212d3c374a43c60fa2625cc1caeaab796.png@progressive.png");
testThread02.start();
testThread021.start();
testThread022.start();
}
public TestThread02(String name, String url) {
this.name = name;
this.url = url;
}
@Override
public void run() {
try {
down(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(name+"已下载");
}
}
//下载器
class UrlDown {
public static void down(URL url, File file) throws IOException {
//连接到这个资源 HTTP
HttpURLConnection urlConnection= (HttpURLConnection) url.openConnection();
InputStream inputStream=urlConnection.getInputStream();
FileOutputStream fos=new FileOutputStream(file);
byte[] bytes=new byte[1024];
int len;
while((len=inputStream.read(bytes))!=-1){
fos.write(bytes,0,len);//写这个数据
}
fos.close();
inputStream.close();
urlConnection.disconnect();//断开连接
}
}
方式2:实现Runnable接口
推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
//创建线程方式2: 实现runnable接口,重写run方法,执行线程需要丢入runnable接口实现类,调用start方法
public class TestThread03 implements Runnable{
public static void main(String[] args) {
//main线程,主线程
//创建runnable接口的实现类对象
TestThread03 testThread03=new TestThread03();
//创建线程对象,通过线程对象来开启线程,代理
Thread thread=new Thread(testThread03);
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("玩游戏ing~~~~");
}
}
@Override
public void run() {
//run方法线程体
for (int i = 0; i <20 ; i++) {
System.out.println("学习~~~~");
}
}
}
方式3:实现Callable接口
//线程创建方式3 实现Callable接口,需要返回值类型
//重写call方法,需要抛出异常
//创建目标对象
//创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行:Future<Boolean> r1=ser.submit(t1);
//获取结果:boolean rs1=r1.get();
//关闭服务:ser.shutdownNow();
//多线程下载网络图片
public class TestCallable implements Callable<Boolean> {
private String name;
private String url;
//主线程
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable t1 = new TestCallable("1.png", "https://i0.hdslb.com/bfs/article/02db465212d3c374a43c60fa2625cc1caeaab796.png@progressive.png");
TestCallable t2 = new TestCallable("2.png", "https://i0.hdslb.com/bfs/article/02db465212d3c374a43c60fa2625cc1caeaab796.png@progressive.png");
TestCallable t3 = new TestCallable("3.png", "https://i0.hdslb.com/bfs/article/02db465212d3c374a43c60fa2625cc1caeaab796.png@progressive.png");
//创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1=ser.submit(t1);
Future<Boolean> r2=ser.submit(t2);
Future<Boolean> r3=ser.submit(t3);
//获取结果
boolean rs1=r1.get();
boolean rs2=r2.get();
boolean rs3=r3.get();
//关闭服务
ser.shutdownNow();
}
public TestCallable(String name, String url) {
this.name = name;
this.url = url;
}
@Override
public Boolean call() throws Exception {
try {
down(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
return false;
}
System.out.println(name + "已下载");
return true;
}
}
2.并发问题
//多个线程操作同一个对象
//线程不安全,数据紊乱
public class TestThread04 implements Runnable{
//票数
private int tickt=10;
public static void main(String[] args) {
TestThread04 t = new TestThread04();
new Thread(t,"小明").start();
new Thread(t,"小红").start();
new Thread(t,"黄牛党").start();
}
@Override
public void run() {
while (true){
if (tickt<=0)break;
try {//模拟延时
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+tickt--+"张票");
}
}
}
3.龟兔赛跑
//为什么其中一个结束后另一个也不跑了????
public class Race implements Runnable{
private String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//模拟兔子睡觉
if (Thread.currentThread().getName().equals("兔子")&&i%10==0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (gameOver(i)) {
break;
}
System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
}
}
public 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;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
4.静态代理
真实对象和代理对象都要实现同一个接口
代理对象要代理真实角色
优点:代理对象可以作很多真实对象做不了的事情,真实对象专注做自己的事
public class StaticProxy {
public static void main(String[] args) {
new WeddingCompany(new You()).HappyMarry();
//类似于Thread代理其他实现了Runnable接口的类!!!
}
}
interface Marry{
void HappyMarry();
}
//真实角色
class You implements Marry{
@Override
public void HappyMarry() {
System.out.println("结婚啦~~~");
}
}
//代理角色,婚庆公司
class WeddingCompany implements Marry{
private Marry target;//结婚的真实角色
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
before();
this.target.HappyMarry();
after();
}
private void after() {
System.out.println("进行婚后收尾工作");
}
private void before() {
System.out.println("进行婚前准备工作");
}
}
5.Lambda表达式
定义函数式接口:一个接口,只包含唯一一个抽象方法
对于函数式接口,可以通过Lambda表达式来创建该接口(实现类)的对象
public class TestLambda01 {
//3.静态内部类
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda2");
}
}
public static void main(String[] args) {
ILike like = new Like();
like.lambda();
like=new Like2();
like.lambda();
//4.局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda3");
}
}
like =new Like3();
like.lambda();
//5.匿名内部类
like =new ILike() {
@Override
public void lambda() {
System.out.println("I like lambda4");
}
};
like.lambda();
//6.用lambda简化
like =()->{
System.out.println("I like lambda5");
};
like.lambda();
}
}
//1.定义一个函数式接口
interface ILike{
void lambda();
}
//2.实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda");
}
}
当有一个参数传递时,Lambda表达式中”()“可以省略且参数类型可以省略,有多个参数时且类型相同,参数类型可以省略但"()"要加上,当实现方法只有一行代码时,花括号"{}"可以省略!!
6.线程状态
- NEW:尚未启动的线程处于此状态。
- RUNNABLE:在Java虚拟机中执行的线程处于此状态。
- BLOCKED:被阻塞等待监视器锁定的线程处于此状态。
- WAITING:正在等待另一个线程执行特定动作的线程处于此状态。
- TIMED_WAITING:正在等得另一个线程执行动作达到指定等待时间的线程处于此状态。
- TERMINATED:己退出的线程处于此状态。
停止线程
//建议线程正常停止===》利用次数,不建议使用死循环// 建议使用标志位//不要用stop或者destroy等过时或者JDK不建议使用的方法
public class TestStop implements Runnable{
//设置一个标志位
private boolean flag=true;
@Override
public void run() {
int i=0;
while(flag){
System.out.println("run ~~~Thread"+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 < 1000; i++) {
System.out.println("main:"+i);
if (i==900){
//调用stop方法,让线程停止
testStop.stop();
System.out.println("线程停止了");
}
}
}
}
线程休眠
- sleep(时间)指定当前线程阻塞的毫秒数;
- sleep存在异常InterruptedException;
- sleep时间达到后线程进入就绪状态;
- sleep可以模拟网络延时,倒计时等。
- 每一个对象都有一个锁,sleep不会释放锁;
线程礼让
- 礼让线程,让当前正在执行的线程暂停,但不阻塞
- 将线程从运行状态转为就绪状态
- 让cpu重新调度,礼让不一定成功!看CPU心情
public class Testyield {
public static void main(String[] args) {
MyYield myYield=new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();//礼让
System.out.println(Thread.currentThread().getName()+"线程结束执行");
}
}
Join
- Join合并线程,带此线程执行完成后,在执行其他线程,其他线程阻塞
- 可以想象成插队
public class TestJoin implements Runnable{
public static void main(String[] args) {
TestJoin testJoin=new TestJoin();
Thread thread=new Thread(testJoin);
thread.start();
//主线程
for (int i = 0; i < 1000; i++) {
if (i==20){
try {
thread.join();//插队!!!!!!!!!
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main:"+i);
}
}
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("VIP客户先行"+i);
}
}
}
线程优先级
-
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
-
线程的优先级用数字表示,范围从1~10.
- Thread.MIN_PRIORITY = 1;
- Thread.MAX_PRIORITY = 10;
- Thread.NORM_PRIORITY = 5;
-
使用以下方式改变或获取优先级
getPriority() . setPriority(int xxx) -
优先级低只是意味着获得调度的概率低.并不是优先级低就不会被调用了.这都是看CPU的调度
public class TestPriority {
public static void main(String[] args) {
//主线程默认优先级
System.out.println(Thread.currentThread().getName()+" "+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread thread1 = new Thread(myPriority, "a");
Thread thread2 = new Thread(myPriority, "b");
Thread thread3 = new Thread(myPriority, "c");
Thread thread4 =s new Thread(myPriority, "d");
Thread thread5 = new Thread(myPriority, "e");
Thread thread6 = new Thread(myPriority, "f");
//先设置优先级再启动
thread1.start();
thread2.setPriority(1);
thread2.start();
thread3.setPriority(6);
thread3.start();
thread4.setPriority(2);
thread4.start();
thread5.setPriority(8);
thread5.start();
thread6.setPriority(4);
thread6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" "+Thread.currentThread().getPriority());
}
}
守护线程
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等待..
- 用户线程停止后,守护线程不久就会停止!
public class TestDaemon {
public static void main(String[] args) {
God god=new God();
You1 you1=new You1();
Thread thread1=new Thread(god);
thread1.setDaemon(true);//默认是false表示是用户线程,正常线程都是用户线程
thread1.start();//守护线程启动
new Thread(you1).start();//用户线程
}
}
//上帝
class God implements Runnable{
@Override
public void run() {
while(true){
System.out.println("天佑");
}
}
}
//you
class You1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 365; i++) {
System.out.println("开心的活着ing~~");
}
System.out.println("=====Goodbye World!=====");
}
}
7.同步
-
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题﹐为了保证数据在方法中被访问时的正确性﹐在访问时加入锁机制 "synchronized ",当一个线程获得对象的排它锁﹐独占资源﹐其他线程必须等待,使用后释放锁即可.存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,加锁﹐释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置﹐引起性能问题.
-
只对需要修改数据的代码上锁,否则会浪费太多资源
买票
- 不安全
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket t = new BuyTicket();
new Thread(t, "小明").start();
new Thread(t, "小红").start();
new Thread(t, "黄牛党").start();
}
}
class BuyTicket implements Runnable{
private int tickt = 10;//票数
boolean flag = true;//外部停止方式
@Override
public void run() {
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void stop(){
flag=false;
}
private void buy() throws InterruptedException {
if (tickt<=0){//判断是否有票
flag=false;
return;
}
//模拟延时
Thread.sleep(100);
//买票
System.out.println(Thread.currentThread().getName()+"买到了第"+tickt--+"张票");
}
}
-
安全
对buy方法加上synchronized关键字
private synchronized void buy() throws InterruptedException { if (tickt<=0){//判断是否有票 flag=false; return; } //模拟延时 Thread.sleep(100); //买票 System.out.println(Thread.currentThread().getName()+"买到了第"+tickt--+"张票"); }
集合
-
不安全
public class UnsafeList { public static void main(String[] args) { List<String> list=new ArrayList<>(); for (int i = 0; i < 1000; i++) { new Thread(()->{ list.add(Thread.currentThread().getName()); }).start(); } try { Thread.sleep(100);//等待所有线程跑完 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(list.size()); } }
-
安全
for (int i = 0; i < 1000; i++) { new Thread(()->{ synchronized (list){ list.add(Thread.currentThread().getName()); } }).start(); }
同步块
synchronized(要锁住的对象){
修改操作;
}
- 同步块:synchronized (Obj ){}
- Obj称之为同步监视器
- Obj可以是任何对象﹐但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this ,就是这个对象本身﹐或者是class [反射中讲解]
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码.
- 第二个线程访问﹐发现同步监视器被锁定﹐无法访问.
- 第一个线程访问完毕﹐解锁同步监视器.
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问
8.死锁
多个线程各自占有一些共享资源﹐并且互相等待其他线程占有的资源才能运行﹐而导致两个或者多个线程都在等待对方释放资源﹐都停止执行的情形﹒某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题.
public class DeadLock {
public static void main(String[] args) {
Makeup zhangsan = new Makeup("zhangsan", 0);
Makeup lisi = new Makeup("lisi", 1);
zhangsan.start();
lisi.start();
}
}
//镜子
class Mirror{}
//口红
class Lipstick{}
class Makeup extends Thread{
//需要的资源只有一份,用static
static Lipstick lipstick=new Lipstick();
static Mirror mirror=new Mirror();
String name;
int choice;
public Makeup(String name, int choice) {
this.name = name;
this.choice = choice;
}
@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.name+"获得口红的锁");
Thread.sleep(1000);
synchronized (mirror){
//一秒钟后想获得镜子
System.out.println(this.name+"获得镜子的锁");
}
}
}else {//获得镜子的锁
synchronized (mirror){
System.out.println(this.name+"获得镜子的锁");
Thread.sleep(2000);
synchronized (lipstick){//2秒钟后想获得口红
System.out.println(this.name+"获得口红的锁");
}
}
}
}
}
解决:
private void makeup() throws InterruptedException {
if (choice==0){//获得口红的锁
synchronized (lipstick){
System.out.println(this.name+"获得口红的锁");
Thread.sleep(1000);
}
synchronized (mirror){//一秒钟后想获得镜子
System.out.println(this.name+"获得镜子的锁");
}
}else {//获得镜子的锁
synchronized (mirror){
System.out.println(this.name+"获得镜子的锁");
Thread.sleep(2000);
}
synchronized (lipstick){//2秒钟后想获得口红
System.out.println(this.name+"获得口红的锁");
}
}
}
9.锁
-
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
-
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
-
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
-
eg:
-
不安全买票
public class TestLock { public static void main(String[] args) { Buyticket buyticket = new Buyticket(); new Thread(buyticket).start(); new Thread(buyticket).start(); new Thread(buyticket).start(); }}class Buyticket implements Runnable{ int ticket=10; @Override public void run() { while(true){ if (ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ticket--); }else break; } } }
-
使用锁后安全买票
class TestLock02 implements Runnable{ int ticket=10; private final ReentrantLock reentrantLock= new ReentrantLock(); @Override public void run() { while(true){ try{ reentrantLock.lock();//加锁 if (ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(ticket--); }else break; }finally { reentrantLock.unlock();//解锁 } } } }
-
10.线程协作
管程法
//管程法:利用缓冲区解决消费者模型
public class TestPC {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();
new Productor(synContainer).start();
new Consumer(synContainer).start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//产品
class Chicken {
int id;
public Chicken(int id) {
this.id = id;
}
}
//生产者
class Productor extends Thread{
SynContainer synContainer;
public Productor(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
for (int i = 1; i < 101; i++) {
synContainer.push(new Chicken(i));
}
}
}
//消费者
class Consumer extends Thread{
SynContainer synContainer;
public Consumer(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
for (int i = 1; i < 101; i++) {
synContainer.pop();
}
}
}
//缓冲区
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++;
System.out.println("生产了"+chicken.id+"只鸡");
//可以通知消费者消费
this.notifyAll();
}
//消费者消费产品
public synchronized Chicken pop(){
//判断能否消费
if (count==0){
//等待生产者生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//能消费
count--;
Chicken chicken=chickens[count];
System.out.println("消费了第"+chicken.id+"只鸡");
//吃完通知生产者生产
this.notifyAll();
return chicken;
}
}
信号灯法
添加标志位,T表示能消费,生产者等待;F相反。
11.线程池
public class TestPC02 {
public static void main(String[] args) {
//创建服务,创建线程池
ExecutorService service= Executors.newFixedThreadPool(10);//参数为线程池大小
//执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
/*
* new Thread(new MyThread());
* new Thread(new MyThread());
* new Thread(new MyThread());
* new Thread(new MyThread());
* */
//关闭连接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!