JAVA 进程线程详解
线程和进程
一、进程
- 进程是指运行中的程序,比如我们使用QQ,就启动该进程分配内存空间.
- 进程是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自升的产生,存在和消亡的过程
二、线程
- 线程是由进程创建的,是进程的一个实体
- 一个进程可以拥有多个线程
- 一个想线程还可以创建它的子线程
三、其他概念
单线程:同时允许执行一个线程
多线程:同一个时刻,可以执行多个线程
- 比如:QQ可以打开多个聊天窗口,一个迅雷进程,可以同时下载多个文件
并发:同一个时刻,多个任务交替执行,造成"貌似同时"的错觉,简单的说,单核CPU实现的多任务就是并发
并行:同一个时刻,多个任务同时执行。多核CPU可以同时执行
- 也可能出现:并行和并发,并存在的情况
四、线程的基本使用
在Java中线程来使用有两种方法
基础Thread类,重写run方法
实现Runnable接口,重写run方法
案例一
![image-20220327105831422](!
)
package com.hspedu.threaduse;
/**
* @author DL5O
* @version 1.0
* 通过继承Thread 类创建线程
*/
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
//创建Cat对象,可以当做线程使用
Cat cat = new Cat();
//启动线程,调用start的时候还调用run方法
cat.start();//最终执行 -> cat的run方法
//cat.run();
//如果这样写,run方法就是一个普通的方法,
// 是由主线程调用了,并没有真正的开线程,
// 会阻塞在这里,执行完毕后才会继续执行下面的代码
//说明:当main线程启动一个子线程thread-0,主线程不会阻塞,
// 即不会等待cat.start执行完毕后再往下执行
//主线程中如果后面还有代码的话,还会继续执行
//这时我们的主线程和主线程 是交替执行的
System.out.println("继续执行~~,"+Thread.currentThread().getName());
for (int i = 0; i < 60; i++) {
System.out.println("主线程 i=" + i);
//休眠1秒
Thread. sleep(1000);
}
}
}
//1.当一个类继承了Thread 类,该类就可以当做一个线程使用
//2.我们会重写run方法,写上自己的业务逻辑
//3.run方法在Thread也是实现了Runnable 接口的run方法
class Cat extends Thread {
int times = 0;
@Override
public void run() {//重写run方法,写上自己的业务逻辑
while (true) {
//每隔一秒钟,输出"喵喵,我是小猫咪"
System.out.println("喵喵,我是小猫咪" + (++times)
+ ",线程名称=" + Thread.currentThread().getName());
//让线程休眠1秒钟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 80) {
break;//当times 等于80就退出这个while循环,这时线程就退出了
}
}
}
}
- 当运行这个程序代码的时候,会创建一个进程并给他分配空间
- 会先执行main方法,开启一个main线程
- 当执行到
car.start()
方法时会开启一个新的线程,这个线程是mian线程的子线程- 注意:
- 这个
car.start()
是非阻塞的,即不会等到这个方法执行完后才继续往下执行 car.start()
线程开启后,会去调用run方法!!- 但是如果直接写car.run(),这个时候则是用的主线程main线程去调用的,并没有开启新线程,执行玩这个方法后才会往下继续执行
- 这个
- 注意:
执行过程
(1)
public synchronized void start() {
start0();
}
(2)
//start0() 是本地方法,是JVM调用,底层是c/c++实现的
//真正实现多线程的是start0(),而不是run方法
private native void start0();
案例二
实现runnable接口
说明:
- java是单继承的,在某些情况下已经继承了某个父类,这时继承Thread类方法来创建线程,显示是不可能的
- java设计者们提供了另外一个方式创建线程,就是通过实现
Runnalb
接口来创建线程
package com.hspedu.threaduse;
/**
* @author DL5O
* @version 1.0
* 通过实现接口Runnable 来开发线程
*/
public class Thread02 {
public static void main(String[] args){
Dog dog = new Dog();
//dog.start(); 这里不能调用start
//创建了Thread对象,把 dog对象,实现了Runnable,放入thread
Thread thread = new Thread(dog);
thread.start();
/*Tiger tiger = new Tiger();//实现了runnable接口
ThreadProxy threadProxy = new ThreadProxy(tiger);
threadProxy.start();
System.out.println("哈哈哈哈");*/
System.out.println("哈哈哈");
}
}
class Animal{}
class Tiger extends Animal implements Runnable{
@Override
public void run() {
System.out.println("老虎嗷嗷叫...");
}
}
//模拟了最简的Thread类
class ThreadProxy implements Runnable{//可以当做ThreadProxy ,线程代理
private Runnable target = null;//属性,类型是Runnable
@Override
public void run() {
if(target != null){
target.run();//进行动态绑定 运行类型是tiger,即实现了Runnable的类
}
}
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start(){
start0();//这个方法是真正实现多线程的方法
}
private void start0() {
run();
}
}
class Dog implements Runnable{
int count = 0;
@Override
public void run() {//普通方法,并没有启动线程
while(true){
System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
//休眠一秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(count == 10){
break;
}
}
}
}
- 把实现了runnable接口的对象传入到thread类,进行静态代理,调用start方法时,会去调用start0方法,start0方法还会调用对应该类的run方法,run方法又会去调用传入的对象的run方法,进行动态绑定
案例三
package com.hspedu.threaduse;
/**
* @author DL5O
* @version 1.0
* main 线程启动两个子线程
*/
public class Thread03 {
public static void main(String[] args) {
T1 t1 = new T1();
T2 t2 = new T2();
Thread thread1 = new Thread(t1);//创建t1线程,用于输出hello,world
Thread thread2 = new Thread(t2);//创建t2线程,用于输出hi
thread1.start();
thread2.start();
}
}
class T1 implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
System.out.println("hello,world " + (++count) + " "+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
class T2 implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
System.out.println("hi " + (++count) + " " + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
break;
}
}
}
}
- 若还有线程没有执行完毕,那么主进程就不会退出
五、继承Thread vs 实现Runnable有什么区别
- 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本
质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口 - 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了
单继承的限制,建议使用Runnable
package com.hspedu.ticket;
/**
* @author DL5O
* @version 1.0
* 使用多线程,模拟三个窗口同时售票
* 总票数 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();*/
System.out.println("===使用接口的方式来售票===");
SellTicket02 sellTicket02 = new SellTicket02();
Thread thread1 = new Thread(sellTicket02);
Thread thread2 = new Thread(sellTicket02);
Thread thread3 = new Thread(sellTicket02);
thread1.start();//第一个线程-窗口
thread2.start();//第二个线程-窗口
thread3.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));
}
}
}
//实现接口的方式
class SellTicket02 implements Runnable{
private int ticketNum = 100;
@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));
}
}
}
- 会出现超卖,和票数对不上的情况,这时候程序就会存在很大的问题=> 引出互斥等概念
六、线程的退出
基本说明:
- 当线程完成任务后,会自动退出
- 还可以通过变量来控制run方法退出的方式停止线程,即通知方式
package com.hspedu.exit_;
/**
* @author DL5O
* @version 1.0
*/
public class ThreadExitTest_ {
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
test.start();
Thread.sleep(10*1000);
test.setLoop(false);
}
}
class Test extends Thread{
private boolean loop = true;
private int cnt = 0;
@Override
public void run() {
while(loop){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Test 执行中" + (++cnt));
}
System.out.println("Test 运行结束");
}
public boolean isLoop() {
return loop;
}
public void setLoop(boolean loop) {
this.loop = loop;
}
public int getCnt() {
return cnt;
}
public void setCnt(int cnt) {
this.cnt = cnt;
}
}
七、线程常用方法
常用方法第一组
- setName:给线程设置名称
- getName:返回线程的名称
- start:使线程开始执行
- run:线程调用的run方法
- setPriority:设置线程的优先级
- getPriority:获取线程的优先级
- sleep:让正在执行的线程休眠几秒
- interrupt:中断线程
- start底层会创建新的线程,会调用run,run本身不会启动新线程,不会启动新线程
- 线程优先级的范围
- interrupt,中断线程,但并没有真正结束线程。所以一般用于中断正在休眠线程
- sleep:线程的静态方法,使当前线程休眠
案例:
ThreadMethod01
package com.hspedu.method;
/**
* @author DL5O
* @version 1.0
*/
public class ThreadMethod01 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.setName("大龙");
t.setPriority(Thread.MIN_PRIORITY);//设置最小的优先级
t.start();//启动子线程
//主线程打印5 个hi, 然后我就中断 子线程的休眠
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println("hi " + (i+1));
}
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++) {
System.out.println(Thread.currentThread().getName() + " 吃包子~~~" + (i + 1));
}
try {
System.out.println("大龙休息中~~");
Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
//InterruptedException 是捕获到一个终端异常
System.out.println(Thread.currentThread().getName() + "被 interrupt了");
}
}
}
}
常用方法第二组
注意:
- 在某一个线程后使用了类名.join方法,那么会cpu会全部去执行 这个插队的线程,并且当这个线程全部执行完后,才会继续执行原来的线程
package com.hspedu.method;
/**
* @author DL5O
* @version 1.0
*/
public class ThreadMethod02 {
public static void main(String[] args) throws InterruptedException {
int count = 0;
Test test = new Test();
test.start();
while (true){
System.out.println("hello " + (++count));
Thread.sleep(1000);
if(count == 5){
System.out.println("让test 线程先执行");
// test.join();//线程插队一定会成功
Thread.yield();//线程礼让
//让test 线程先执行,执行后可以看到,当执行join后,主线程就不再执行,
// 当这个test线程执行完毕后,主线程才继续执行
}
if(count == 20){
break;
}
}
}
}
class Test extends Thread{
private int count = 0;
@Override
public void run() {
while(true){
System.out.println("hi " + (++count));
try {
Thread.sleep( 1000);//休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 20){
break;
}
}
}
}
课堂练习
package com.hspedu.method;
/**
* @author DL5O
* @version 1.0
*/
public class ThreadMethodExercise {
public static void main(String[] args) throws InterruptedException {
T01 t01 = new T01();
for (int i = 1; i <= 10; i++) {
System.out.println("hi " + i);
if (i == 5) {
Thread thread = new Thread(t01);
thread.start();
thread.join();
System.out.println("子线程结束...");
}
Thread.sleep(1000);
}
System.out.println("主线程结束...");
}
}
class T01 implements Runnable{
@Override
public void run() {
for (int i = 1; i <= 10 ; i++) {
System.out.println("hello " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
八、用户线程和守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
- 常见的守护线程:垃圾回收机制
package com.hspedu.method;
/**
* @author DL5O
* @version 1.0
* 守护线程的设置
*/
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread myDaemonThread = new MyDaemonThread();
Test01 test01 = new Test01();
myDaemonThread.setDaemon(true);
//如果我们希望当main线程结束后,子线程可以自动退出
//只需将子线程设为守护线程即可
myDaemonThread.start();
test01.start();
for (int i = 1; i <= 10; i++) {
System.out.println("我是主线程 " + i);
Thread.sleep(1000);
}
}
}
class MyDaemonThread extends Thread {
@Override
public void run() {
for (int i = 1; ; i++ ) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是MyDaemonThread 守护线程 " + i);
}
}
}
class Test01 extends Thread {
@Override
public void run() {
for (int i = 1; i <= 15 ; i++ ) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是Test01 用户线程 " + i);
}
}
}
总结:
- 当所有的用户线程退出后,守护线程就会自动结束
- 设置守护线程的方式:
对象名.setDaemon(true)
- 如果不进行设置,线程默认为用户线程
九、 线程的生命周期
线程状态转换图:
九、线程同步机制
关键字:Synchronized
- 在多线程编程,一些敏感数据不允许被多少个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
- 也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
同步具体方法-Synchronized
同步代码块
synchronized(对象){//获得对象的锁,才能操作同步代码
//需要被同步代码;
}
synchronized还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m(String name){
//需要被同步的代码
}
如何理解:
- 就好像某个人要上厕所,上厕所前需要把门关上(上锁),完事之后再出来(解锁),那么其他小伙伴就可以在使用厕所了
使用
synchronized
解决售票
package com.hspedu.syn;
/**
* @author DL5O
* @version 1.0
* 使用多线程,模拟三个窗口同时售票
* 总票数 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();*/
/*System.out.println("===使用接口的方式来售票===");
SellTicket02 sellTicket02 = new SellTicket02();
Thread thread1 = new Thread(sellTicket02);
Thread thread2 = new Thread(sellTicket02);
Thread thread3 = new Thread(sellTicket02);
thread1.start();//第一个线程-窗口
thread2.start();//第二个线程-窗口
thread3.start();//第三个线程-窗口*/
//测试
SellTicket03 sellTicket03 = new SellTicket03();
Thread thread1 = new Thread(sellTicket03);
Thread thread2 = new Thread(sellTicket03);
Thread thread3 = new Thread(sellTicket03);
thread1.start();//第一个线程-窗口
thread2.start();//第二个线程-窗口
thread3.start();//第三个线程-窗口
}
}
//实现接口的方式,使用synchronized 实现线程同步
class SellTicket03 implements Runnable {
private int ticketNum = 100;
private boolean loop = true;
public synchronized void sell() {
//同步方法,在同一个时刻只能有一个线程来执行我们的run方法
if (ticketNum <= 0) {
loop = false;
System.out.println("票已售空,售票结束..");
return;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName()
+ " 售出一张票," + "剩余票数=" + (--ticketNum));
}
@Override
public void run() {
while (loop) {
sell();
}
}
}
//使用第一种继承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));
}
}
}
//实现接口的方式
class SellTicket02 implements Runnable {
private int ticketNum = 100;
@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));
}
}
}
十、互斥锁
基本介绍:
Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键字
synchronized
来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问同步的局限性:导致程序的执行效率要降低
同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
- 非静态的这个锁是加在当前对象的
同步方法(静态的)的锁为当前类本身。
- 静态方法的是加载当前类的
注意事项:
- 同步方法如果没有使用static修饰:默认锁对象是:this
- 如果方法使用了static修饰:默认是锁对象是:当前类.class
- 实现的落地步骤:
- 需要先分析上锁的代码
- 选择同步代码块或同步方法
- 要求多个线程的锁对象为同一个即可!!!!
- 即共享的资源上
- 线程同步只会发生在共享同一个资源时
package com.hspedu.syn;
/**
* @author DL5O
* @version 1.0
* 使用多线程,模拟三个窗口同时售票
* 总票数 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();*/
/*System.out.println("===使用接口的方式来售票===");
SellTicket02 sellTicket02 = new SellTicket02();
Thread thread1 = new Thread(sellTicket02);
Thread thread2 = new Thread(sellTicket02);
Thread thread3 = new Thread(sellTicket02);
thread1.start();//第一个线程-窗口
thread2.start();//第二个线程-窗口
thread3.start();//第三个线程-窗口*/
//测试
SellTicket03 sellTicket03 = new SellTicket03();
Thread thread1 = new Thread(sellTicket03);
Thread thread2 = new Thread(sellTicket03);
Thread thread3 = new Thread(sellTicket03);
thread1.start();//第一个线程-窗口
thread2.start();//第二个线程-窗口
thread3.start();//第三个线程-窗口
}
}
//实现接口的方式,使用synchronized 实现线程同步
class SellTicket03 implements Runnable {
private int ticketNum = 100;
private boolean loop = true;
Object obj =new Object();//都是同一个对象
//同步方法(静态的) 的锁为当前类
//1.他的锁是加载我们这个类上的 SellTicket03.Class 上
//2.如果要在静态方法中实现一个同步代码块.
// synchronized (SellTicket03.class)
public synchronized static void m1(){
}
public static void m2(){
synchronized (SellTicket03.class){
System.out.println("m2");
}
}
//是在方法上加的锁
//说明
//1.public synchronized void sell()就是一个同步方法
//2.这时锁是在this对象
//3.也可以在代码块上写 synchronized,同步代码块,互斥锁还是在this对象
public /*synchronized*/ void sell() {//同步方法,在同一个时刻只能有一个线程来执行我们的run方法
synchronized (/*this*/obj){
if (ticketNum <= 0) {
loop = false;
System.out.println("票已售空,售票结束..");
return;
}
//休眠50毫秒
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName()
+ " 售出一张票," + "剩余票数=" + (--ticketNum));
}
}
@Override
public void run() {
while (loop) {
sell();
}
}
}
//使用第一种继承thread的方式
//new SellTicket01().start()
//new SellTicket01().start() this只对当前的对象有效
class SellTicket01 extends Thread {
private static int ticketNum = 100;//让多个线程共享 ticketNum
//锁一般用于共享的只有
public void m1(){
synchronized (this){
System.out.println("hello");
}
}
@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));
}
}
}
//实现接口的方式
class SellTicket02 implements Runnable {
private int ticketNum = 100;
@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));
}
}
}
十一、线程的死锁
基本介绍:
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程时一定要避免死锁的发生
package com.hspedu.syn;
/**
* @author DL5O
* @version 1.0
* 模拟线程死锁
*/
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 Object o2 = new Object();
boolean flag;
public DeadLockDemo(boolean flag){
this.flag = flag;
}
@Override
public void run() {
//1.如果flag为T,线程就会先得到/持有 o1的对象锁,然后会尝试去得到o2的对先生
// 当持有到 o2的对象锁时,才会继续往下执行
//2.如果线程A 得不到o2的对象锁,就会Blocked
//3.如果flag 为 false,线程会去得到o2的对象锁,
//4.如果线程B 拿不到o1的对象锁,就会block
if(flag){
synchronized(o1){//对象互斥锁
System.out.println(Thread.currentThread().getName()+"进入1");
//拿到o1的锁之后会阻塞在这里,直到拿到o2的锁,
// 此时第二个线程thread2进入,拿到o2的锁,进入堵塞状态,因为一直拿不到o1的锁,
// o1又因为拿不到o2锁,释放不了,故成为了死锁
synchronized (o2){
System.out.println(Thread.currentThread().getName()+"进行2");
}
}
}else{
synchronized (o2){
//第二个线程 o1的锁已经被拿了,
//这时,又想去拿o1的锁,但是o1在第36行阻塞到了,没有拿到o2的锁,没有释放,故这里也会被阻塞
//那么这时就会产生一种死锁的情况,要避免
System.out.println(Thread.currentThread().getName()+"进入3");
synchronized (o1){
System.out.println(Thread.currentThread().getName()+"进行4");
}
}
}
}
}
十二、释放锁
下面操作会释放锁
- 当线程的同步方法、同步代码块执行结束
- 当前线程在同步代码块、同步方法中遇到break、return
- 当前线程在同步代码块、同步方法中出现了未处理的error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,也会释放锁
下面操作不会释放锁
线程执行同步代码块或同步方法时,程序调用
Thread.sleep()
、Thread.yield()
暂停当前线程的执行,不会释放锁线程执行同步代码块时,其他线程调用了该线程的
suspend()
方法将会被挂起,该现场不会释放锁提示:应尽量避免使用
suspend()
和resume()
来控制线程,方法不再推荐使用了
作业
package com.hspedu.homework;
import java.util.Random;
import java.util.Scanner;
/**
* @author DL5O
* @version 1.0
*/
public class Homework01 {
public static void main(String[] args) {
A a = new A();
B b = new B(a);
a.setName("A");
b.setName("B");
// a.setDaemon(true);
a.start();
b.start();
}
}
class A extends Thread {
private int num;
static boolean loop = true;
private Random random;
public A() {
random = new Random();
}
public void showNum() {
//nextInt(int bound)
//返回伪随机的,均匀分布 int值介于0(含)和指定值(不包括),从该随机数生成器的序列绘制。
num = random.nextInt(101);//生成0~100的随机数
//num = (int)Math.random()*(100-0+1)+0;
System.out.println("随机数:" + num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (loop){
showNum();
}
}
}
//用来控制A线程退出的线程B
class B extends Thread {
private String str;
private A a;
Scanner scanner;
public B(A a) {
this.a = a;
scanner = new Scanner(System.in);
}
@Override
public void run() {
synchronized (B.class) {
while (true) {
str = scanner.next();
if (str.equals("Q")) {
a.loop = false;
break;
}
}
}
}
}
package com.hspedu.homework;
/**
* @author DL5O
* @version 1.0
*/
public class Homework02 {
public static void main(String[] args) {
Withdraw withdraw = new Withdraw();
Thread A = new Thread(withdraw);
Thread B = new Thread(withdraw);
A.setName("A");
B.setName("B");
A.start();
B.start();
}
}
class Withdraw implements Runnable {
private int money = 10000;
private boolean loop = true;
public void deal() {
//1.这里使用了synchronized 实现了线程同步
//2.当多个线程执行到这里的时候,就回去争夺 this对象锁
//3.那个线程执行到 this对象锁,就执行这个代码块,执行玩后,会释放这个锁,准备继续争夺
//4.争夺不到就阻塞到这里,就blocked
//5.this 是一个非公平锁
synchronized (/*Withdraw.class*/this){
if (money <= 0) {
loop = false;
System.out.println("余额不足");
return;
}
money -= 1000;
System.out.println(Thread.currentThread().getName() + "取走了1000,剩余" + money);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while (loop) {
deal();
}
}
}
JAVA 进程线程详解的更多相关文章
- “全栈2019”Java多线程第二十五章:生产者与消费者线程详解
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...
- IIS:连接数、并发连接数、最大并发工作线程数、应用程序池的队列长度、应用程序池的最大工作进程数详解
Internet Information Services(IIS,互联网信息服务),是由微软公司提供的基于运行Microsoft Windows的互联网基本服务.最初是Windows NT版本的可选 ...
- IIS连接数、并发连接数、最大并发工作线程数、应用程序池的队列长度、应用程序池的最大工作进程数详解
IIS:连接数.并发连接数.最大并发工作线程数.应用程序池的队列长度.应用程序池的最大工作进程数详解 iis性能指标的各种概念:连接数.并发连接数.最大并发工作线程数.应用程序池的队列长度.应用程序池 ...
- Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量
Python进阶----线程基础,开启线程的方式(类和函数),线程VS进程,线程的方法,守护线程,详解互斥锁,递归锁,信号量 一丶线程的理论知识 什么是线程: 1.线程是一堆指令,是操作系统调度 ...
- Java synchronized 关键字详解
Java synchronized 关键字详解 前置技能点 进程和线程的概念 线程创建方式 线程的状态状态转换 线程安全的概念 synchronized 关键字的几种用法 修饰非静态成员方法 sync ...
- JAVA IO 类库详解
JAVA IO类库详解 一.InputStream类 1.表示字节输入流的所有类的超类,是一个抽象类. 2.类的方法 方法 参数 功能详述 InputStream 构造方法 available 如果用 ...
- 转:Java HashMap实现详解
Java HashMap实现详解 转:http://beyond99.blog.51cto.com/1469451/429789 1. HashMap概述: HashMap是基于哈希表的M ...
- 通用线程:POSIX 线程详解,第 3 部分 条件互斥量(pthread_cond_t)
使用条件变量提高效率 本文是 POSIX 线程三部曲系列的最后一部分,Daniel 将详细讨论如何使用条件变量.条件变量是 POSIX 线程结构,可以让您在遇到某些条件时“唤醒”线程.可以将它们看作是 ...
- Java类加载器详解
title: Java类加载器详解date: 2015-10-20 18:16:52tags: JVM--- ## JVM三种类型的类加载器- 我们首先看一下JVM预定义的三种类型类加载器,当一个 J ...
随机推荐
- ArcMap操作随记(3)
1.地图四要素: 图名.图例.比例尺.指北针 2.[栅格计算器].[加权叠加]和[加权总和]的不同 [栅格计算器]的结果是浮点型小数 [加权叠加]工具,输入栅格必须为整型.若成本栅格涉及重分类,最好用 ...
- Floyd算法 解决多元汇最短路问题
接下来是图论问题求解最短路问题的最后一个,求解多元汇最短路问题 我们之前一般都是问1-n的最短路径,这里我们要能随便去问i到j的最短路径: 这里介绍一下Floyd算法:我们只有一个d[maxn][ma ...
- JVM基本概念
JVM基础概念 什么是JVM JVM:Java virtual machine,Java虚拟机,它是一种规范.是虚构出来的一台计算机.它可以将二进制字节码根据不同的操作系统转为当前操作系统识别的的字节 ...
- centos根目录扩容
一.扫描磁盘 若已在虚拟机增加了磁盘且没看到磁盘 运行下面的命令来查找系统中所有的主机总线编号: # ls /sys/class/scsi_host 得到主机总线编号后,运行以下命令来发现新的磁盘: ...
- 使用python自动发放员工工资条到个人邮箱
人力资源部每个月发工资条,以前靠人工手动一条一条的复制,200多号员工,差不多需要耗费一天时间. 用python写个脚本,分分钟的事情就可以全部发送完. 我这边工资表如下图 姓名在C列,邮箱在B列. ...
- ES6-ES12部分简单知识点总结,希望对大家有用~
ES6-ES12简单知识点总结 1.ES6相关知识点 1.1.对象字面量的增强 ES6中对对象字面量的写法进行了增强,主要包含以下三个方面的增强: 属性的简写:当给对象设置属性时,如果希望变量名和属性 ...
- loj3161「NOI2019」I 君的探险(随机化,整体二分)
loj3161「NOI2019」I 君的探险(随机化,整体二分) loj Luogu 题解时间 对于 $ N \le 500 $ 的点,毫无疑问可以直接 $ O(n^2) $ 暴力询问解决. 考虑看起 ...
- 序列化和反序列化&持久化
java序列化与反序列化全讲解 之前一知半解的,对于序列化的概念,为啥用,哪里用也不清楚,现在深入了解协议,先把序列化这个这个概念和和使用场景搞清楚
- ubuntu16 和ubuntu18安装及设置静态ip
1.准备ubuntu16镜像2.安装:https://zhuanlan.zhihu.com/p/1447048653.安装ubuntu后,sudo passwd root这个命令建立root用户的密码 ...
- uWSGI+django+nginx的工作原理流程与部署
二.必要的前提 2.1 准备知识 django 一个基于python的开源web框架,请确保自己熟悉它的框架目录结构. uWSGI 一个基于自有的uwsgi协议.wsgi协议和http服务协议的web ...