Java基础__学习笔记__线程
-- 进程是执行中的一段程序,而一个进程中执行中的每个任务即为一个线程
-- 一个线程只可以属于一个进程,但一个进程能包含多个线程
-- 线程无地址空间,它包括在进程的地址空间里
线程和线程可以有依赖关系也可以没有,一个线程挂了不会影响另一个线程。主线程开启子线程,主线程挂了,子线程不一定会挂。
Thread实现了Runnable接口
一个类继承了Thread可以用这个类的实例化对象开启一个线程。这个类必须重写run方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package com.leehl.thread; public class MyThread3 { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); //main线程 Menglei a = new Menglei(); a.start(); } } class Menglei extends Thread{ int time; @Override public void run() { while ( true ) { try { Menglei.sleep( 1000 ); //可以使用Thread的Thread.currentThread().getName()方法获得线程名 System.out.println( "梦泪老师说扣1送地狱火" +(++time)+ "线程名:" +Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } if (time== 8 ){ break ; } } } } |
如果是a.run()那么程序会串行化执行,进程也都是main线程。
当执行start方法后,会在start中执行执行start0方法,start0方法底层是由JVM底层调用native方法,C/C++实现的
所以真正实现多线程效果的是start0()方法。
而start0()方法只是把这个线程变成了可运行状态,最终还是要看CPU的的调度算法,什么时候调用
public synchronized void start() {
start0();
}
start执行start方法后:
当一个类已经继承了一个父类后,没法继承Thread,我们可以实现Runnable接口来实现线性
package com.leehl.Threadproxy; public class MyThread4 { public static void main(String[] args) { dog d = new dog(); d.setCount(15); Thread t = new Thread(d); t.start(); for (int i = 0; i < 10; i++) { System.out.println("陈明旭和罗昊在狗叫卢毅"+Thread.currentThread().getName()); } //__________________________________________________________________________________________ //想让tiger走其他线程,但是tiger已经继承了animal类,无法通过继承Thread类来调用start()方法 //但是tiger又要走别的线程,所以就找了个代理类ProxyThread来帮tiger类找一下start()方法 tiger tiger = new tiger(); ThreadProxy threadProxy = new ThreadProxy(tiger); threadProxy.start(); } } class animal{} class tiger extends animal implements Runnable{ @Override public void run() { System.out.println("----老虎----"+Thread.currentThread().getName()); } } class ThreadProxy implements Runnable{ private Runnable target=null; //定义一个runnable来接 @Override public void run() { if(target != null){ target.run(); } } //调用start方法 实际上还是main线程 public ThreadProxy(Runnable target) { //线程代理,只要实现了Runnable就可以使用这个构造器 this.target = target; } public void start(){ start0(); } private void start0() { run(); } } //__________________________________________________________________________________________ class dog implements Runnable{ private int count; public int getCount() { return count; } public void setCount(int count) { this.count = count; } @Override public void run() { while (true){ try { Thread.sleep(1000); System.out.println("卢毅扣1送地狱火烧陈明旭和罗昊"+(++count)+Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } if(count==20){ break; } } } }
常见线程方法:
setName() 设置线程名称
getName() 返回线程名称
start 使线程开始执行,实际调用start0方法
run 调用线程对象run方法
setPriority 更改优先级
getPriority 获取线程的优先级
sleep() 让线程指定毫秒内休眠
interrupt 中断线程
isinterrupt 返回该线程是否被中断 Boolean类型
yield 线程礼让 但不一定会成功
join 线程插队,一旦插队成功,肯定会先完成插入的线程内的所有任务
线程名.setDaemon(true) 守护线程,当main线程结束后,守护线程也就自动接受了
线程中断方法 :调用Thread里面的interrupt方法来打断线程的运行(可以通过isinterrupt来判断这个线程是否被打断)
还是之前强调过的:Java里的线程最终的执行和调度都是由操作系统来决定的,JVM只是对操作系统层面的线程做了一层包装而已。
所以我们在Java里面通过start方法启动一个线程的时候,只是告诉操作系统这个线程可以被执行,但是最终交给CPU来执行是操作系统的调度算法来决定的。
package com.leehl.thread; public class MyThread5 { public static void main(String[] args) throws InterruptedException { T a1 = new T(); a1.start(); Thread.sleep(1000); System.out.println("111111111111地狱火"); a1.interrupt(); } } class T extends Thread{ @Override public void run() { while (true){ if (this.isInterrupted()) { System.out.println(Thread.currentThread().getName()+"线程被interrupt了"); break; } System.out.println("----"); } } }
售票问题:
package com.leehl.Sellticket; import java.io.BufferedReader; /** * 模拟三个窗口同时售票100张 */ public class ticket { public static void main(String[] args) { // sellticket01 sellticket01 = new sellticket01(); // sellticket01 sellticket02 = new sellticket01(); // sellticket01 sellticket03 = new sellticket01(); // sellticket01.start(); // sellticket02.start(); // sellticket03.start(); // //会出现超卖的现象:当ticketNum=1的时候,线程1通过判断if语句满足条件进入后,还没执行(--ticketNum), // //此时线程2也通过判断if语句也进入了,也没执行(--ticketNum), // //但此时票数只有一张,两个线程都会进行(--ticketNum),导致超卖。 sellticket02 sellticket02 = new sellticket02(); Thread a1 = new Thread(sellticket02); Thread a2 = new Thread(sellticket02); Thread a3 = new Thread(sellticket02); a1.start(); a2.start(); a3.start(); //还是会出现超卖,和上述问题一样 } } //使用Thread方式 class sellticket01 extends Thread{ private static int ticketNum = 100; //让多个线程共享这个ticketNum @Override public void run() { while (true) { if(ticketNum <= 0){ System.out.println("票卖完了"); break; } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口:"+Thread.currentThread().getName()+"卖出了一张票,剩余票数"+(--ticketNum)); } } } //通过继承Runnable的方式 class sellticket02 implements Runnable{ private int ticketNum = 100; //因为只有一个对象,多个线程共享这个对象,所以ticketNum可以不用static @Override public void run() { while (true) { if(ticketNum <= 0){ System.out.println("票卖完了"); break; } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口:"+Thread.currentThread().getName()+"卖出了一张票,剩余票数"+(--ticketNum)); } } }
上面两种方法都会出现票超卖的现象,问题在于我们去理解调用线程的这个过程
解决方法:
单独把sell方法拎出来,设置为synchronized,这样每个线程在执行sell方法的时候,会有一个互斥锁。拥有该锁的"钥匙"才能去执行sell方法,否则会被卡在等待区被执行
class sellticket03 implements Runnable{ private int ticketNum = 1000; //因为只有一个对象,多个线程共享这个对象,所以ticketNum可以不用static boolean loop = true; @Override public void run() { while (loop) { sell(); } } //public synchronized void sell{}就是一个同步方法 //此时锁在this对象 //也可以在代码块上写synchronized public synchronized void sell() { //同步方法,在同一时刻,只能有一个线程来执行run方法 if(ticketNum <= 0){ System.out.println("票卖完了"); loop = false; return; } try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口:"+Thread.currentThread().getName()+"卖出了一张票,剩余票数"+(--ticketNum)); } }
也可以把 sell方法里面的对象this用synchronize锁住
//通过实现Runnable接口,使用Synchronized实现线程同步 class sellticket03 implements Runnable{ private int ticketNum = 1000; //因为只有一个对象,多个线程共享这个对象,所以ticketNum可以不用static boolean loop = true; @Override public void run() { while (loop) { sell(); } } //public synchronized void sell{}就是一个同步方法 //此时锁在this对象 //也可以在代码块上写synchronized public void sell() { //同步方法,在同一时刻,只能有一个线程来执行run方法 synchronized(this){ if (ticketNum <= 0) { System.out.println("票卖完了"); loop = false; return; } try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口:" + Thread.currentThread().getName() + "卖出了一张票,剩余票数" + (--ticketNum)); } } }
或者:
//通过实现Runnable接口,使用Synchronized实现线程同步 class sellticket03 implements Runnable{ private int ticketNum = 1000; //因为只有一个对象,多个线程共享这个对象,所以ticketNum可以不用static boolean loop = true; Object o = new Object(); @Override public void run() { while (loop) { sell(); } } //public synchronized void sell{}就是一个同步方法 //此时锁在this对象 //也可以在代码块上写synchronized public void sell() { //同步方法,在同一时刻,只能有一个线程来执行run方法 synchronized(o){ if (ticketNum <= 0) { System.out.println("票卖完了"); loop = false; return; } try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口:" + Thread.currentThread().getName() + "卖出了一张票,剩余票数" + (--ticketNum)); } } }
sellticket03 sellticket03 = new sellticket03();
Thread a1 = new Thread(sellticket03);
Thread a2 = new Thread(sellticket03);
Thread a3 = new Thread(sellticket03);
a1.start();
a2.start();
a3.start();
三个线程其实都是操作同一个线程 object o ,所以用synchronized也可以锁住,这也是要求我们多个线程的锁对象是同一个即可,如果我们把上面的代码
synchronized(o) 变为 synchronized(new object),那么在红色代码这块,每个线程会创建一个新的object对象,不会上锁,锁的对象操作的不是一个。
如果这个sell方法是一个静态的static,那么这个锁就不在this上了,而是加在这个类class上的。
如果在静态方法中要实现一个同步代码块,要用这个class类本身上,不能用this,静态方法中本身就不能用this。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示