//*****************************************************580***********************************************************************//
//*********** 进程 ***************//
//*******************************************************************************************************************************//
进程是程序的一次执行过程,或是正在运行的一个程序。
是动态过程:有它自身的产生、存在和消亡的过程
//****************************************************580************************************************************************//
//************* 线程 **************//
//*******************************************************************************************************************************//
1.线程由进程创建的,是进程的一个实体
2.一个进程可以拥有多个线程
举例: 迅雷下载多个文件 迅雷整体:进程 每个下载任务:对应一个线程线程
也就是说:一个进程----> 可以拥有多个线程
//*******************************************************************************************************************************//
//***********************************************************581****************************************************************//
//*******************************************************************************************************************************//
1.单线程: 同一个时刻,只允许执行一个线程
2.多线程: 同一个时刻,可以执行多个线程
比如: 一个qq进程,可以同时打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
3.并发: 同一个时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说:单核cpu实现的多任务就是并发。
4.并行: 同一个时刻,多个任务同时执行。多核cpu可以实现并行。 并发和并行
//*************************************************************************************************************************//
//******************************************************581**************************************************************//
//*************************************************************************************************************************//
public class CpuNum {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
//Runtime.getRuntime() 返回一个 Runtime 对象,该对象代表当前 JVM 实例。
// 可以使用这个对象来执行各种操作,例如获取虚拟机参数、启动和停止 JVM 实例、获取平台信息等等。
//获取当前电脑的cpu数量/核心数
int cpuNums = runtime.availableProcessors();
System.out.println("当前有cpu 个数=" + cpuNums);
}
}
//*************************************************************************************************************************//
//*************************************************************************************************************************//
//*************************************************************************************************************************//
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//创建Cat对象,可以当做线程使用
Cat cat = new Cat();
//老韩读源码
/*
(1)
public synchronized void start() {
start0();
}
(2)
//start0() 是本地方法,是JVM调用, 底层是c/c++实现
//真正实现多线程的效果, 是start0(), 而不是 run。
你可以理解成,JVM机通过 start0 底层调用 run !! 而不是直接调用run
*/
private native void start0();
*/
cat.start();//启动线程-> 最终会执行cat的run方法
cat.run();//run方法就是一个普通的方法, 没有真正的启动一个线程,就会把run方法执行完毕,才向下执行
//所以,我们采用:cat.start(); 让Cat自己单独运行
//说明: 当main线程启动一个子线程 Thread-0, 主线程不会阻塞, 会继续执行
//这时 主线程和子线程是交替执行..
System.out.println("主线程继续执行" + Thread.currentThread().getName());//名字main
for(int i = 0; i < 60; i++) {
System.out.println("主线程 i=" + i);
//让主线程休眠
Thread.sleep(1000);
}
也就是一个进程结束了,并不意味着它其中的线程结束,一个线程结束也不意味着它其中的子线程结束
}
}
//老韩说明
//1. 当一个类继承了 Thread 类, 该类就可以当做线程使用
//2. 我们会重写 run方法,写上自己的业务代码
//3. run Thread 类 实现了 Runnable 接口的run方法
/*
@Override
public void run() {
if (target != null) {
target.run();
}
}
*/
class Cat extends Thread {
int times = 0;
@Override
public void run() {//重写run方法,写上自己的业务逻辑
while (true) {
//该线程每隔1秒。在控制台输出 “喵喵, 我是小猫咪”
System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
//让该线程休眠1秒 ctrl+alt+t
try { //try-catch是保证 :该线程在sleep时还是能感知响应,能够响应中断,不会睡死
Thread.sleep(1000); //执行完,有可能线程中断!!!导致程序退出。所以执行try-catch 异常捕捉
} catch (InterruptedException e) {
e.printStackTrace();
}
if(times == 80) {
break;//当times 到80, 退出while, 这时线程也就退出..
}
}
}
}
//*************************************************************************************************************************//
//*****************************************************585****************************************************************//
//*************************************************************************************************************************//
线程基本使用
线程应用案例2-实现Runnable接口
说明:
1.java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能了
2.iava设计者们提供了另外一个方式创建线程,就是通过实现Runnable接口来创建线程
Thread (类)-------> Runnable (接口) ----------> FunctionalInterface
要么,直接继承 Thread,用它的 start方法。
要么,实现接口 Runnable,用它的。。。。。 Runnable 只有 run !!但是,不能直接用。不然就是普通方法了
Thread a = new Thread(dog); //多写一个~~ 利用静态代理实现。
a.start();
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.start(); //这里不能调用start !!! 因为Runnable 只有 run 方法!!没有start 方法!!!
//创建了Thread对象,把 dog对象(实现Runnable),放入Thread
Thread thread = new Thread(dog);
thread.start();
// 模拟 【静态代理模式】
Tiger tiger = new Tiger();//实现了 Runnable
ThreadProxy threadProxy = new ThreadProxy(tiger);
threadProxy.start();
}
}
class Animal {
}
class Tiger extends Animal implements Runnable {
@Override
public void run() {
System.out.println("老虎嗷嗷叫....");
}
}
//线程代理类 , 模拟了一个极简的Thread类
class ThreadProxy implements Runnable {//你可以把Proxy类当做 ThreadProxy
private Runnable target = null;//属性,类型是 Runnable
@Override
public void run() {
if (target != null) {
target.run();//动态绑定(运行类型Tiger)
}
}
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start() {
start0(); //这个方法时真正实现多线程方法
}
public void start0() { //start0() 才是真正调用 run()方法
run();
}
} 没有 start0 ,无法执行 run() !!!
class Dog implements Runnable { //通过实现Runnable接口,开发线程
int count = 0;
@Override
public void run() { //普通方法
while (true) {
System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
//休眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
//*************************************************************************************************************************//
//*************************************************************586*****************************************************//
//*************************************************************************************************************************//
public class Thread03 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();//启动第1个线程
thread2.start();//启动第2个线程
//...
}
}
class T1 implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
//每隔1秒输出 “hello,world”,输出10次
System.out.println("hello,world " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count == 60) {
break;
}
}
}
}
class T2 implements Runnable {
int count = 0;
@Override
public void run() {
//每隔1秒输出 “hi”,输出5次
while (true) {
System.out.println("hi " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count == 50) {
break;
}
}
}
}
//*************************************************************************************************************************//
//*************************************************************************************************************************//
//*************************************************************************************************************************//
继承Thread vs 实现Runnable的区别
1)从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从idk帮助文档我们可以看到Thread类本身就实现了Runnable接口
2)实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制
class T3 implements Runnable{}
T3 t3 = new T3("hello ");
Thread thread01 = new Thread(t3); // 把T3给两个线程。
Thread threado2 = new Thread(t3);
threado1.start(); //同时启动两次 ----> 多个线程共享一个资源
threado2.start();
System.out,printIn("主线程完毕”);
class T3 extends Thread{}
T3 t3 = new T3("hello ");
t3.start(); //通过类实现的话,只能一个线程独立进行!!!
t3.start(); //会报错!!!
System.out,printIn("主线程完毕”);
//*************************************************************************************************************************//
//******************************************************587****************************************************************//
//*************************************************************************************************************************//
* 使用多线程,模拟三个窗口同时售票100张 *
public class SellTicket {
public static void main(String[] args) {
测试
SellTicket01 sellTicket01 = new SellTicket01();
SellTicket01 sellTicket02 = new SellTicket01();
SellTicket01 sellTicket03 = new SellTicket01();
//这里我们会出现超卖..
sellTicket01.start();//启动售票线程
sellTicket02.start();//启动售票线程
sellTicket03.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;
}
//休眠50毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));
}
}
}
//************************
* 使用多线程,模拟三个窗口同时售票100张
*/
public class SellTicket {
public static void main(String[] args) {
System.out.println("===使用实现接口方式来售票=====");
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start();//第1个线程-窗口 线程共享 一个对象给三个线程进行
new Thread(sellTicket02).start();//第2个线程-窗口
new Thread(sellTicket02).start();//第3个线程-窗口
}
}
实现接口方式
class SellTicket02 implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum 一个对象给三个线程进行,都是同一个对象,不用 static
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束...");
break;
}
//休眠50毫秒, 模拟
try {
Thread.sleep(50); //如果把休眠时间长一点---> 不会-1 休眠时间:特别小,容易-1
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2
}
}
}
总结:出现 -1 或者别的超票行为的原因: 先判断,( 如果>=0那就-- 记下来 ) ,然后睡眠,然后执行。
如果是单线程,你的判断和行为是一一对应! 但是,如果是多线程的话,当你第一次判断后,睡眠起来时,可能当初的值变了(被那些先起来的线程-- 了)。
所以,导致对不上。但是,你还是按照当初的值来判断。管他呢结果现在是 0/-1 ,都会 -- 。
还有一种可能:现在票数:1 三个线程进来判断是1,>=0,那就 -- 。然后睡眠,先醒来的进行--,自己结束。
但是别人还没结束,后面醒来的还是以为票数是1,进行--,就会出现 -1,-2 情况。
无论你通过接口共享一个对象,还是通过类创还能多个对象改变static值,都会存在以上的问题。
怎么解决呢?
有个方法:当一个线程进行完后,是0,就立刻终止所有线程。别让他们再--了。
//*************************************************************************************************************************//
//**************************** 线程终止 *********588*****************************************//
//*************************************************************************************************************************//
线程终止
基本说明:
1)当线程完成任务后,会自动退出
2)还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
public class ThreadExit_ {
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
t1.start();
//如果希望main线程去控制t1 线程的终止, 必须可以修改 loop
//让t1 退出run方法,从而终止 t1线程 -> 通知方式
//让主线程休眠 10 秒,再通知 t1线程退出
System.out.println("main线程休眠10s...");
Thread.sleep(10 * 1000);
t1.setLoop(false);
}
}
class T extends Thread {
private int count = 0;
//设置一个控制变量
private boolean loop = true;
@Override
public void run() {
while (loop) {
try {
Thread.sleep(50);// 让当前线程休眠50ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T 运行中...." + (++count));
}
}
public void setLoop(boolean loop) {
this.loop = loop;
}
}
//*************************************************************************************************************************//
//*************************************************************************************************************************//
//*************************************************************************************************************************//
线程常用方法
常用方法第一组:
1.setName //设置线程名称,使之与参数 name 相同
2.getName //返回该线程的名称
3.start //使该线程开始执行: Java 虚拟机底层调用该线程的 start0 方法
4.run //调用线程对象 run 方法
5.setPriority //更改线程的优先级
6.getPriority //获取线程的优先级
7.sleep // 在指定的毫秒数内让当前正在执行的线程休眠 (暂停执行)
8. interrupt //中断线程 不等于:终止!!
注意事项和细节
1.start 底层会创建新的线程,通过 start0来调用run。 run 就是一个简单的方法调用,不会启动新线程
2.线程优先级的范围
3.interrupt,中断线程但并没有真正的结束线程。所以一般用于: 中断正在休眠线程
4.sleep:线程的静态方法,使当前线程休眠
public final static int MIN_PRIORITY = 1; // 线程可以具有的最低优先级。
public final static int NORM_PRIORITY = 5; // 分配给线程的默认优先级。
public final static int MAX_PRIORITY = 10; // 线程可以具有的最大优先级。
//*************************************************************************************************************************//
//*******************************************************589**************************************************************//
//*************************************************************************************************************************//
public class ThreadMethod01 {
public static void main(String[] args) throws InterruptedException {
//测试相关的方法
T t = new T();
t.setName("老韩");
t.setPriority(Thread.MIN_PRIORITY);//1 优先级最低
t.start();//启动子线程
//主线程打印5 hi ,然后我就中断 子线程的休眠
for(int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi " + i);
}
System.out.println(t.getName() + " 线程的优先级 =" + t.getPriority());//1
t.interrupt();//当执行到这里,就会中断 t线程的休眠.
}
}
class T extends Thread { //自定义的线程类
@Override
public void run() {
while (true) {
for (int i = 0; i < 100; i++) {
//Thread.currentThread().getName() 获取当前线程的名称
System.out.println(Thread.currentThread().getName() + " 吃包子~~~~" + i);
}
try {
System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
Thread.sleep(2000);//20秒
} catch (InterruptedException e) { //InterruptedException 是捕获到一个中断异常.
//当该线程执行到一个interrupt 方法时,就会catch 一个 异常, 可以加入自己的业务代码
System.out.println(Thread.currentThread().getName() + "被 interrupt了");
}
}
}
}
// 老韩 吃包子~~~~18
// 老韩 吃包子~~~~19
// 老韩 休眠中~~~ 这里你可以把 try{ Thread.sleep(200000); } 这个时间弄得特别大,你会发现,第一次时候,直接被t.interrupt();
// 下次开始,你就等候很长时间~~
// hi 0
// hi 1
// 老韩 线程的优先级 =10
// 老韩被 interrupt了
// 继续执行
// 老韩 吃包子~~~~0
// 老韩 吃包子~~~~1
//*************************************************************************************************************************//
//*********************************************************590*************************************************************//
//*************************************************************************************************************************//
常用方法第二组
1.yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
2.join: 线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
案例: main线程创建一个子线程,每隔1s 输出 hello,输出 20次,主线程每隔1秒,输出 hi,输出 20次.
要求: 两个线程同时执行,当主线程输出 5次后,就让子线程运行完毕,主线程再继续
补充细节:yield:线程的礼让 当CPU 并发执行线程1 和 线程2 的时候,使用 yield,执行礼让:线程1说:CPU你要不执行以下线程1?
如果线程2并不好费时间,CPU来得及切换,CPU说:问题不大,我来得及切换,没事,我继续执行你,再执行线程2
----> 不会礼让(不会执行线程2),继续执行完线程1,再执行线程2,再执行线程1....
相反,线程2非常耗费时间,那CPU就不能很快切换,来不及切换,那就进行礼让:执行线程2,再继续“执行线程1的后半部分” !!!
总结:不一定礼让! 就由CPU决定!
join:线程插队:就是强制执行!
还是上面的情况,当CPU执行线程1时候,用了t2.join ,那么立即执行线程2,执行完线程2,再继续“执行线程1的后半部分” !!!
//*************************************************************************************************************************//
//*********************************************************590************************************************************//
//*************************************************************************************************************************//
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
T2 t2 = new T2();
t2.start();
for(int i = 1; i <= 20; i++) {
Thread.sleep(1000); 都是 : 1000
System.out.println("主线程(小弟) 吃了 " + i + " 包子");
if(i == 5) {
System.out.println("A:主线程(小弟) 让 子线程(老大) 先吃");
//join, 线程插队
//t2.join();// 这里相当于让t2 线程先执行完毕
Thread.yield();//礼让,不一定成功..
System.out.println("B:线程(老大) 吃完了 主线程(小弟) 接着吃..");
}
}
}
}
class T2 extends Thread {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
try {
Thread.sleep(100); 1.100 2.100000 3.1000
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程(老大) 吃了 " + i + " 包子");
}
}
}
分几种 sleep 情况讨论一下:
1.(1000,100) 刚开始让大哥运行,大哥睡100,小弟1000. 相当于,大哥在小弟睡1000ms时,可以输出10次!
结果就是: 大哥:1,2,3,4,5,6,7,8,9 第10次:小弟:1 大哥:10,11,12,13,14,15,16,17,18,19 第20次:小弟:2
大哥:20 大哥已经输出完了,只能小弟慢慢的输出:3,4,5 然后,(A....)就剩小弟了,(B.....),小弟又继续输出:6,7,8,...20
Thread.yield(); 这种情况,不会礼让。因为CPU完全可以忙得过来。不会礼让~ (A....)(B....)
t2.join();大哥:1 /大哥:2 /大哥:3 /大哥:4 .../大哥:9 /小弟:1
/大哥:10 /大哥:11 /...大哥:18 /小弟:2/大哥:19 /大哥:20 /小弟:3 /小弟:4 /小弟:5 /(A....)(B....)/小弟:6 /...小弟:20
2.(1000,1000) 刚开始让大哥运行,大哥睡1000,小弟1000. 睡眠时间一样。那就:小弟:1 大哥:1 小弟:2 大哥:2 小弟:3 大哥:3 ....
你可能有个疑问:不是先让大哥运行了?怎么会小弟在前面?
因为,main 抢CPU的速度比较快!!同样的情况下,mian里面的小弟先执行,然后大哥。
Thread.yield(); 这种情况,不会礼让。因为CPU完全可以忙得过来。不会礼让~ (A....)(B....)
t2.join(); 小弟:1 / 大哥:1 / 小弟:2 / 大哥:2 / 小弟:3 /大哥:3/小弟:4 /
大哥:4 / 小弟:5 / 大哥:5 /(A.先输出大哥...)/ 大哥:6 / 大哥:7 / 大哥:8 / ..... 大哥:20 /小弟:6 /小弟:7 /小弟:8 /...小弟:20
3.(1000,10000) 刚开始让大哥运行,大哥睡10000,小弟1000. 相当于,小弟在大哥睡10000ms时,可以输出10次!
结果就是: 小弟:1,2,3,4,5,6,7,8,9 第10次:大哥:1 小弟:10,11,12,13,14,15,16,17,18 第20次:大哥:2 小弟:19,20 大哥:3,4,5...20
t2.join();小弟:1 /小弟:2 /小弟:3 /小弟:4 /小弟:5 /(A.先输出大哥...)/大哥:1 /
大哥:2 /大哥:3 /...大哥:20 /(B.大哥输出完了...)/小弟:6 /小弟:7 /...小弟:20
Thread.yield(); 这种情况,不会礼让。因为CPU完全可以忙得过来。不会礼让~ (A....)(B....)
我草!!!!我都被晕了,妈的!哈哈哈哈哈 保证上面都是对的哈!
我用了 (10,1000) (1000,10) 用不用 Thread.yield(); ,效果都一样!!!!
//*************************************************************************************************************************//
//*******************************************************591***************************************************************//
//*************************************************************************************************************************//
1.主线程每隔1s,输出 hi,一共 10次
2.当输出到 hi 5 时,启动一个子线程(要求实现Runnable),每隔1s 输出 hello,等该线程输出10次 hello后 ,退出
3.主线程继续输出 hi,直到主线程退出4.如图,完成代码
其实翻译过来就是:线程插队..
public class ThreadMethodExercise {
public static void main(String[] args) throws InterruptedException {
Thread t3 = new Thread(new T3());//创建子线程
for (int i = 1; i <= 10; i++) {
System.out.println("hi " + i);
if(i == 5) {//说明主线程输出了5次 hi
t3.start();//启动子线程 输出 hello...
t3.join();//立即将t3子线程,插入到main线程,让t3先执行
}
Thread.sleep(1000);//输出一次 hi, 让main线程也休眠1s
}
}
}
class T3 implements Runnable {
private int count = 0;
@Override
public void run() {
while (true) {
System.out.println("hello " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
//*************************************************************************************************************************//
//************************************************************592**********************************************************//
//*************************************************************************************************************************//
用户线程和守护线程
1.用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
2.守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
3.常见的守护线程: 垃圾回收机制
//垃圾回收机制就是用户线程结束后,才执行~~
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
//如果我们希望当main线程结束后,子线程自动结束,只需将子线程设为守护线程即可
myDaemonThread.setDaemon(true); //设置成守护线程
myDaemonThread.start(); //如果不设置“守护线程”的话,子线程会一直进行下去!!!
// 细节:必须先设置成守护线程,再start!!不然会报异常
for( int i = 1; i <= 10; i++) {//main线程
System.out.println("宝强在辛苦的工作...");
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread {
public void run() {
for (; ; ) {//无限循环
try {
Thread.sleep(1000);//休眠1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~");
}
}
}
//*************************************************************************************************************************//
//**********************************************************593************************************************************//
//*************************************************************************************************************************//
线程的生命周期
线程状态:线程可以处于以下状态之一:
NEW 尚未启动的线程处于此状态。
RUNNABLE 在Java虚拟机中执行的线程处于此状态。
BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
TIMED WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED 已退出的线程处于此状态
线程状态转化图
起点---->new (状态)----start()---> Runnable(状态) {Ready(状态)-----> Running(状态)} ---> Teminated(状态)---->结束
Runnable(状态)---> 等待进入同步代码快的锁-----> Blocked(状态)-------获得锁------>Runnable(状态)
Runnable(状态)---> o.wait() / t.join()-----> Waiting(状态)-----o.notify()/o.notifyAll()------>Runnable(状态)
Runnable(状态)---> t.join(time)/o.wait(time)/Thread.sleep(time)-----> TimedWaiting(状态)-------时间结束----->Runnable(状态)
................线程被挂起............
↓ ↑
Ready(状态)----线程被调度器选中执行----> Running(状态)
↑ ↓
...............Thread.yeild.......... //通过这里也能很清晰的看到:yeild由系统决定
补充一个细节:站在 main 线程角度,看子线程:如果此时,子线程在睡觉,那就:TIMED_WAITING
如果此时,子线程还没start,那就 NEW
如果此时,子线程结束了,那就TERMINATED
如果此时,子线程睡眠时间短一点的话,会出现RUNNABLE状态!CPU决定
public class ThreadState_ {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getName() + "第一次状态 " + t.getState()); //状态 NEW
t.start();
while (Thread.State.TERMINATED != t.getState()) {
System.out.println(t.getName() + " while 状态 " + t.getState());
//第一次:状态 RUNNABLE 后面都是:TIMED_WAITING 因为线程睡眠时间是main主线程的100倍,比较明显
//如果你把线程的睡眠时间调小一点,会出现 RUNNABLE TIMED_WAITING 叠加状态!! 都是由CPU决定
Thread.sleep(100);
}
System.out.println(t.getName() + " 最后结束 状态 " + t.getState());
// Thread-0 最后结束 状态 TERMINATED
}
}
class T extends Thread {
@Override
public void run() {
while (true) {
for (int i = 0; i < 10; i++) {
System.out.println("hi " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
break;
}
}
}
//*************************************************************************************************************************//
//*********************************************************594**********************************************************//
//*************************************************************************************************************************//
Synchronized
线程同步机制
1.在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性
2.也可以这里理解: 线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,
其他线程才能对该内存地址进行操作。
同步具体方法-Synchronized
1.同步代码块
synchronized (对象){ //得到对象的锁,才能操作同步代码
//需要被同步代码;
}
2.synchronized还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m (String name){
//需要被同步的代码
}
3.如何理解:就好像多个小雪人上厕所前,先把门关上(上锁),完事后再出来(解锁),那么其它小伙伴就可以一个一个使用厕所了。
不然,很有可能多个人一起上,互相拉在身上了(有点变态~~)
//*************************************************************************************************************************//
//********************************************************595*************************************************************//
//*************************************************************************************************************************//
public class SellTicket {
public static void main(String[] args) {
//测试
// SellTicket01 sellTicket01 = new SellTicket01();
// SellTicket01 sellTicket02 = new SellTicket01();
// SellTicket01 sellTicket03 = new SellTicket01();
//
// //这里我们会出现超卖..
// sellTicket01.start();//启动售票线程
// sellTicket02.start();//启动售票线程
// sellTicket03.start();//启动售票线程
System.out.println("===使用实现接口方式来售票=====");
SellTicket02 sellTicket02 = new SellTicket02();
new Thread(sellTicket02).start();//第1个线程-窗口
new Thread(sellTicket02).start();//第2个线程-窗口
new Thread(sellTicket02).start();//第3个线程-窗口
}
}
//实现接口方式
class SellTicket02 implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
@Override
public synchronized void run() { 我就在这里多加了一个:synchronized
while (true) { 第一个先执行。大师这是个死循环,会导致第一个先进来的,通过死循环,把所有都执行完,在退出
if (ticketNum <= 0) {
System.out.println("售票结束...");
break;
}
//休眠50毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2
}
}
}
输出结果:
窗口 Thread-0 售出一张票 剩余票数=99
窗口 Thread-0 售出一张票 剩余票数=98
窗口 Thread-0 售出一张票 剩余票数=97
............
窗口 Thread-0 售出一张票 剩余票数=2
窗口 Thread-0 售出一张票 剩余票数=1
窗口 Thread-0 售出一张票 剩余票数=0
售票结束... //第一个线程进来,直接都干完了!!
售票结束... //第二个线程进来,发现ticketNum == 0 ,直接退出
售票结束... //第三个线程进来,发现ticketNum == 0 ,直接退出
//*************************************************************************************************************************//
为什么多次一举用run调用m方法,因为只有start才能开子线程.start只能调用run方法
public class SellTicket {
public static void main(String[] args) {
//测试一把
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();//第1个线程-窗口
new Thread(sellTicket03).start();//第2个线程-窗口
new Thread(sellTicket03).start();//第3个线程-窗口
}
}
//实现接口方式, 使用synchronized实现线程同步
class SellTicket03 implements Runnable {
private int ticketNum = 100;//让多个线程共享 ticketNum
private boolean loop = true;//控制run方法变量
public synchronized void sell() { //同步方法, 在同一时刻, 只能有一个线程来执行sell方法
if (ticketNum <= 0) {
System.out.println("售票结束...");
loop = false;
return;
}
//休眠50毫秒, 模拟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + " 售出一张票"
+ " 剩余票数=" + (--ticketNum));//1 - 0 - -1 - -2
}
@Override
public void run() {
while (loop) {
sell();//sell方法是一共同步方法
}
}
}
输出结果:
窗口 Thread-1 售出一张票 剩余票数=9
窗口 Thread-1 售出一张票 剩余票数=8
窗口 Thread-1 售出一张票 剩余票数=7
窗口 Thread-2 售出一张票 剩余票数=6
窗口 Thread-2 售出一张票 剩余票数=5
窗口 Thread-2 售出一张票 剩余票数=4
窗口 Thread-2 售出一张票 剩余票数=3
窗口 Thread-2 售出一张票 剩余票数=2
窗口 Thread-2 售出一张票 剩余票数=1
窗口 Thread-2 售出一张票 剩余票数=0
售票结束...
售票结束...
售票结束...
//*************************************************************************************************************************//
//*********************************************************595************************************************************//
//*************************************************************************************************************************//
互斥锁
基本介绍:
1.Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
2.每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
3.关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时表明该对象在任一时刻只能由一个线程访问
4.同步的局限性:导致程序的执行效率要降低
5.同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
6.同步方法(静态的)的锁为当前类本身
this是针对对象来说的(因为,创建对象,采用this) static是针对类的(因为,类加载就static加载)
static 是在类加载时候 就有的 而对象 是类加载后根据类构造方法创建的 ,所有对象都有static 方法 ,故this不了
1. public synchronized void sell() { 就是一个同步方法,这时锁在 this对象
//代码块
}
2. 也可以在代码块上写 synchronize ,同步代码块, 互斥锁还是在this对象
public void sell() {
synchronized (this) {
//代码块
}
}
3. class SellTicket03 implements Runnable {
Object object = new Object();
public void sell() {
synchronized (object) {
//代码块
}
} }
细节:
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();//第1个线程-窗口
new Thread(sellTicket03).start();//第2个线程-窗口
new Thread(sellTicket03).start();//第3个线程-窗口
都是同一个 sellTicket03 对象!!!!所以,操作的Object 都是同一个Object !!!!
4.
同步方法(静态的)的锁为当前类本身
1. public synchronized static void m1() {} 锁是加在 SellTicket03.class
public synchronized static void m1() {}
2. 如果在静态方法中,实现一个同步代码块.
public static void m2() {
synchronized (SellTicket03.class) { //不能用this !!!!
System.out.println("m2");
}
}
static 是在类加载时候 就有的 而对象 是类加载后根据类构造方法创建的 ,所有对象都有static 方法 ,故this不了
就是说,任何一个对象都有互斥锁,而Synchronized是为了调对象的互斥锁,所以括号里this也好,
其他对象也好都可以满足,只要是个对象就行;而静态方法没有对象也就没有互斥锁所以直接调用类的方式
互斥锁。
注意事项和细节
1.同步方法如果没有使用static修饰: 默认锁对象为this
2.如果方法使用static修饰,默认锁对象:当前类.class
3.实现的落地步骤:
需要先分析上锁的代码
选择同步代码块或同步方法
要求多个线程的锁对象为同一个即可
要求多个线程的锁对象为同一个即可:
new SellTicket01.start();
new SellTicket01.start();
class SellTicket01 extends Thread{
private static int ticketNum = 100; //让多个线程共享 ticketNum
public void m1{
synchronized (this){
System.out.println("hello");
}
}
}
这么做的话,m1是锁不住的!!因为,你每个线程都是new出来的。所以每个线程的锁都不同。
人3
门3 人3 人3 人3 人3 人2
门2 人2 人2 人2 人2 --------> 人1
马桶:门1 人1 人1 人1 人1 马桶
如果:
SellTicket03 sellTicket03 = new SellTicket03();
new Thread(sellTicket03).start();//第1个线程-窗口
new Thread(sellTicket03).start();//第2个线程-窗口
new Thread(sellTicket03).start();//第3个线程-窗口
三个线程,都是通过一个对象弄出来的。所以,门是一样的!!
人3 人3 人3 人3
人2 人2 人2 人2
马桶:门 人1 人1 人1 人1
3. class SellTicket03 implements Runnable {
Object object = new Object();
public void sell() {
synchronized (new Object()) { //相当于:一人一个门。但是,坑位只有一个!!还是不行!!超卖更加严重~~
//代码块
}
}
//*************************************************************************************************************************//
//*********************************************************596*************************************************************//
//*************************************************************************************************************************//
线程的死锁
基本介绍:
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生
应用案例:
妈妈:你先完成作业,才让你玩手机
小明: 你先让我玩手机,我才完成作业
死锁:你拿着卫生纸,我占着坑位,谁都别想上厕所!!
//*************************************************************************************************************************//
//*******************************************************596**************************************************************//
//*************************************************************************************************************************//
这里可以这样理解, 一个对象拿到了o1,而另一个对象拿到了o2,而拿到o1的对象还需要拿到o2,但是o2已经被另一个对象拿到了,所以就产生了死锁
模拟线程死锁
public class DeadLock_ {
public static void main(String[] args) {
//模拟死锁现象
DeadLockDemo A = new DeadLockDemo(true);
A.setName("A线程");
DeadLockDemo B = new DeadLockDemo(false);
B.setName("B线程");
A.start();
B.start();
}
}
//线程
class DeadLockDemo extends Thread {
static Object o1 = new Object();// 保证多线程,共享一个对象,这里使用static
static Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag) {//构造器
this.flag = flag;
}
@Override
public void run() {
//下面业务逻辑的分析
//1. 如果flag 为 T, 线程A 就会先得到/持有 o1 对象锁, 然后尝试去获取 o2 对象锁
//如果线程A 得不到 o2 对象锁,就会Blocked
//3. 如果flag 为 F, 线程B 就会先得到/持有 o2 对象锁, 然后尝试去获取 o1 对象锁
//如果线程B 得不到 o1 对象锁,就会Blocked
if (flag) {
synchronized (o1) {//对象互斥锁, 下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入1");
synchronized (o2) { // 这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入2");
}
}
} else {
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入3");
synchronized (o1) { // 这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入4");
}
}
}
}
}
这挺有意思的:一个一个解释一下:
synchronized (o1) 说明,拿到了 o1 的锁。如果o1对象还有别的线程,就进不来! 当执行完代码块程序,再释放锁!
synchronized (o2) 说明,拿到了 o2 的锁。如果o2对象还有别的线程,就进不来! 当执行完代码块程序,再释放锁!
到这里,应该没问题吧?没问题!很合理。
但是,
DeadLockDemo A = new DeadLockDemo(true);
DeadLockDemo B = new DeadLockDemo(false);
这么做的话,A进来,true,就会拿到o1的锁(别的o1进不来),接着想拿o2的锁。但是,由于B,o2进不来!拿不到锁
此时,B进来,false,就会拿到o2的锁(别的o2进不来),接着想拿o1的锁。但是,由于A,o1进不来!拿不到锁
核心冲突:A 和 B 同时拿到了对方重要部分!!导致对方都走不了。
再核心一下:同时!!!
方法一:如果,我让对方睡一会儿,另一方会趁着它睡觉的时间,拿到重要部分,就不会出现问题了。
让A睡一会儿,再拿锁o1,o2. 这时候,B已经拿o2,o1 锁了。正常进行!
if (flag) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (o1) {//对象互斥锁, 下面就是同步代码
System.out.println(Thread.currentThread().getName() + " 进入1");
synchronized (o2) { // 这里获得li对象的监视权
System.out.println(Thread.currentThread().getName() + " 进入2");
}
}
}else......
方法二:双方可以同时抢。但是,让对方抢各自的!也就是说: 顺序:A:o1,o2 B:o2,o1
Object o1 = new Object();
Object o2 = new Object();
状况:A:o1 B:o1 A:o2 B:o2
双方拿到的都是各自的o1,o2 A: o1,o2 B: o1,o2 不会有任何冲突!随便抢!双方都独自走出来了
方法三:双方可以同时抢。但是,让对方抢各自的!也就是说: 顺序:A:o1,o2 B:o2,o1
static Object o1 = new Object();
Object o2 = new Object();
状况:o1 A:o2 B:o2
A: o1 B: o2 ,但是,只有一个o1,B没法立刻拿o1,因为此刻o1被A锁着。但是,A可以拿o2.所以,等A结束之后,o2释放了,B再可以进行!
所以顺序: A: o1 B: o2 B等A, A:o2 ,A结束,再 B;o1 (A1,B2,A2,B1)
方法三:双方可以同时抢。但是,让对方抢各自的!也就是说: 顺序:A:o1,o2 B:o2,o1
Object o1 = new Object();
static Object o2 = new Object();
状况:o2 A:o1 B:o1
(A1,B2,A没法拿o2,只能等B走完,B1,A2)
总结:三种方法解决死锁:①让某一方在拿对方重要锁的之前,睡一会儿,让对方先执行完 (这个睡眠时间很重要!!)
②都非satic,每个对象独立分配锁
③让其中一个属性非static,导致某一方先执行完。
你看,同样一个视频,老师就说:避免锁。而我,想到了四种方法解决锁。就问你细不细???哈哈~~
//*************************************************************************************************************************//
//***************************************************597******************************************************************//
//*************************************************************************************************************************//
用yield就是,你先上,我就是不开门,你先上,快点上,哈哈哈哈,门就是不开
释放锁
下面操作会释放锁:
1.当前线程的同步方法、同步代码块执行结束
案例:上厕所,完事出来
2.当前线程在同步代码块、同步方法中遇到 break、return
案例:没有正常的完事经理叫他修改bug,不得已出来
3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
案例:发现忘带纸,不得已出来
4.当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁
案例:没有正常的完事,觉得需要酝酿下,所以出来等会再进去
下面操作不会释放锁:
1.线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
案例:上厕所,太困了,在坑位上眯了一会
案例:用yield就是,你先上,我就是不开门,你先上,快点上,哈哈哈哈,门就是不开
2.线程执行同步代码块时,其他线程调用了该线程的suspend0方法将该线程挂起该线程不会释放锁
提示: 应尽量避免使用suspend()和resume()来控制线程,方法不再推荐使用
//*************************************************************************************************************************//
//*****************************************************598****************************************************************//
//*************************************************************************************************************************//
题目描述:A线程:随机输出1~100的数字,至到B线程中输入:Q,让A线程停止
public class Homework01 {
public static void main(String[] args) {
A a = new A();
B b = new B(a);//一定要注意.
a.start();
b.start();
}
}
//创建A线程类
class A extends Thread {
private boolean loop = true;
@Override
public void run() {
//输出1-100数字
while (loop) {
System.out.println((int)(Math.random() * 100 + 1));
//休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("a线程退出...");
}
public void setLoop(boolean loop) {//可以修改loop变量
this.loop = loop;
}
}
//直到第2个线程从键盘读取了“Q”命令
class B extends Thread {
private A a;
private Scanner scanner = new Scanner(System.in);
public B(A a) {//构造器中,直接传入A类对象
this.a = a;
}
@Override
public void run() {
while (true) {
//接收到用户的输入
System.out.println("请输入你指令(Q)表示退出:");
char key = scanner.next().toUpperCase().charAt(0);
if(key == 'Q') {
//以通知的方式结束a线程
a.setLoop(false);
System.out.println("b线程退出.");
break;
}
}
}
}
此题,能不能通过“守护线程”来做?
不行。因为,守护线程是指所有线程都结束了,它才结束。但是,main刚来就结束了。A正在进行。
别人都停了,你再停,这时可以用守护线程。但是,你通知别人,不能用守护线程!!
温馨提示:这里输入Q时要在1秒内迅速按下回车键,否则会一直输出数字!!别的线程会被霸占!
还有一个细节: B b = new B(a); b停了,a就停!!!
//*************************************************************************************************************************//
//******************************************************599***************************************************************//
//*************************************************************************************************************************//
而放锁外面的休眠,会使上一个进锁的对象休眠,锁会给剩下的对象抢夺,会排除上一个对象抢锁的概率,直到下一个对象进入休眠重置概率
原理:
t1 ------> Blocked
? 获取锁----执行---结束后,释放锁-- 抢完锁之后,执行完,又去抢锁!!
t2 ------> Blocked ↓
↑------------------------------------------------↓
可能会出现一种情况:t1 t2 ,如果t1牛逼,抢的比较快,那就t1先抢到,执行代码。执行完之后又去抢! 导致,t1执行很多次。
但是,这对t2来说不公平啊~~我们希望的是,第一次,行,t1牛逼,你先执行。但是后面应该是t2.顺序执行。但是,我们这个不会这么做。
所以,synchronized 也叫“非公平锁
还有一个小办法:如果是两个线程进行,如果你想实现顺序执行,可以在 synchronized{}之后,睡一会儿。另一个线程就会趁这个机会,进行。哈哈哈哈
就会达到顺序执行~~ 细节,一定在 synchronized{}外面,但是这个线程一定经过的地方(run里面)。不要放在 synchronized{}里面。不然,这个线程睡觉,别的线程也只能干等着~~
当然,如果有两个以上的线程,无论你怎么睡,都没用哈哈。剩下线程趁你在睡觉的时候,都会抢。还是会不公平~~
//***********************************************
public class Homework02 {
public static void main(String[] args) {
T t = new T();
Thread thread1 = new Thread(t);
thread1.setName("t1");
Thread thread2 = new Thread(t);
thread2.setName("t2");
thread1.start();
thread2.start();
}
}
//编程取款的线程
//1.因为这里涉及到多个线程共享资源,所以我们使用实现Runnable方式
//2. 每次取出 1000
class T implements Runnable {
private int money = 10000;
@Override
public void run() {
while (true) {
解读
1. 这里使用 synchronized 实现了线程同步
2. 当多个线程执行到这里时,就会去争夺 this对象锁
3. 哪个线程争夺到(获取)this对象锁,就执行 synchronized 代码块, 执行完后,会释放this对象锁
4. 争夺不到this对象锁,就blocked ,准备继续争夺
5. this对象锁是非公平锁.
synchronized (this) {//
//判断余额是否够
if (money < 1000) {
System.out.println("余额不足");
break;
}
money -= 1000;
System.out.println(Thread.currentThread().getName() + " 取出了1000 当前余额=" + money);
}
让这个线程睡一会儿。另一个线程就会趁这个机会进行。达到顺序执行。
//休眠1s
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
如果你不睡眠,直接进行,很有可能出现,只有t1在执行。
为哈呢?
因为这个数目太少了。t1一个人直接干完了。
如果你把数字调大一点:10000改成10000000 会出现 t1 ,t2一起干的情况~
接着搞坦克大战去啦~~
Java 线程
最新推荐文章于 2024-08-18 21:36:31 发布