程序、进程、线程
总结:
1.程序、进程和线程的区分: 程序(program):为完成特定任务,用某种语言编写的`一组指令的集合`。即指一段静态的代码。 进程(process):程序的一次执行过程,或是正在内存中运行的应用程序。程序是静态的,进程是动态的。 进程作为操作系统调度和分配资源的最小单位。 线程(thread):进程可进一步细化为线程,是程序内部的一条执行路径线程.作为CPU调度和执行的最小单位 2.线程调度策略 分时调度:所有线程轮流使用`CPU`的使用权,并且平均分配每个线程占用CPU的时间。抢占式调度:让`优先级高`的线程以`较大的概率`优先使用 CPU。如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。3.了解 > 单核CPU与多核CPU > 并行与并发
相关代码:
单线程
public class SingleThread {public void method1(String str){System.out.println(str);}public void method2(String str){method1(str);}public static void main(String[] args) {SingleThread s = new SingleThread();s.method2("hello!");}
}
线程的创建和使用
总结:
1.线程的创建方式一:继承Thread类 1.1 步骤: ① 创建一个继承于Thread类的子类 ② 重写Thread类的run()--->将此线程要执行的操作,声明在此方法体中 ③ 创建当前Thread的子类的对象 ④ 通过对象调用start(): 1.启动线程 2.调用当前线程的run()1.2 例题:创建一个分线程1,用于遍历100以内的偶数 【拓展】再创建一个分线程2,用于遍历100以内的偶数2.线程的创建方式二: 实现Runnable接口 2.1 步骤: ① 创建一个实现Runnable接口的类 ② 实现接口中的run()-->将此线程要执行的操作,声明在此方法体中 ③ 创建当前实现类的对象 ④ 将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例 ⑤ Thread类的实例调用start():1.启动线程 2.调用当前线程的run()2.2 例题:创建分线程遍历100以内的偶数3.对比两种方式?共同点: ① 启动线程,使用的都是Thread类中定义的start()② 创建的线程对象,都是Thread类或其子类的实例。不同点: 一个是类的继承,一个是接口的实现。建议:建议使用实现Runnable接口的方式。Runnable方式的好处:① 实现的方式,避免的类的单继承的局限性② 更适合处理有共享数据的问题③ 实现了代码和数据的分离。联系:public class Thread implements Runnable (代理模式)
线程的创建方式一:继承Thread类
相关代码1:
package com.atguigu01.create.thread;/*** ClassName: EvenNumberTest* Package: com.atguigu01.create.thread* Description:* 例题:创建一个分线程1,用于遍历100以内的偶数* @Author: lwfstart* @Create 2025-03-27 20:27* @Version: 1.0*/
//① 创建一个继承于Thread类的子类
class PrintNumber extends Thread {private double money = 1000;//② 重写Thread类的run()--->将此线程要执行的操作,声明在此方法体中public void run(){for (int i = 1; i <= 100; i++){if(i%2 == 0){System.out.println(Thread.currentThread().getName() + ":" + i);}}}
}
public class EvenNumberTest {public static void main(String[] args) {//③ 创建当前Thread的子类的对象PrintNumber t1 = new PrintNumber();//④ 通过对象调用start()t1.start();/*** 问题1:能否使用t1.run()替换 t1.start()的调用,实现分线程的创建和调用?不能!*/
// t1.run();/*** 问题2:在提供一个分线程,用于100以内的偶数的遍历** 注意:不能让已经start()的线程,再次执行start(),否则报异常IllegalThreadStateException*/
// t1.start();PrintNumber t2 = new PrintNumber();//④ 通过对象调用start()t2.start();//main方法所在的线程执行的操作for (int i = 1; i <= 100; i++){if(i%2 == 0){System.out.println(Thread.currentThread().getName() + ":" + i + "***********************");}}}
}
线程的创建方式二: 实现Runnable接口
相关代码2:
package com.atguigu01.create.runnable;/*** ClassName: EvenNumberTest* Package: com.atguigu01.create.runnable* Description:* ① 创建一个实现Runnable接口的类* ② 实现接口中的run()-->将此线程要执行的操作,声明在此方法体中* ③ 创建当前实现类的对象* ④ 将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例* ⑤ Thread类的实例调用start():1.启动线程 2.调用当前线程的run()* @Author: lwfstart* @Create 2025-03-27 21:36* @Version: 1.0*/
//① 创建一个实现Runnable接口的类
class EvenNumberPrint implements Runnable {private double money = 1000;
// ② 实现接口中的run()-->将此线程要执行的操作,声明在此方法体中@Overridepublic void run() {for (int i = 1; i <= 100; i++){System.out.println(Thread.currentThread().getName() + ":" + i);}}
}
public class EvenNumberTest {public static void main(String[] args) {
// ③ 创建当前实现类的对象EvenNumberPrint p = new EvenNumberPrint();
// ④ 将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例Thread t1 = new Thread(p);
// ⑤ Thread类的实例调用start():1.启动线程 2.调用当前线程的run()t1.start();//main()方法对应的主线程执行的操作for (int i = 1; i <= 100; i++){System.out.println(Thread.currentThread().getName() + ":" + i);}/*** 拓展:在创建一个线程,用于遍历100以内的偶数*/Thread t2 = new Thread(p);t2.start();}
}
练习题1:
练习:创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数
相关代码:
class EvenNumberPrint extends Thread{ //用于打印偶数@Overridepublic void run() {for(int i = 1; i <= 100; i++){if(i%2 == 0){System.out.println(Thread.currentThread().getName() + ":" + i);}}}
}
class OddNumberPrint extends Thread{ //用于打印奇数@Overridepublic void run() {for(int i = 1; i <= 100; i++){if(i%2 != 0){System.out.println(Thread.currentThread().getName() + "--->" + i);}}}
}
public class PrintNumberTest {//打印偶数public static void main(String[] args) {//方式1:
// EvenNumberPrint t1 = new EvenNumberPrint();
// OddNumberPrint t2 = new OddNumberPrint();
// t1.start();
// t2.start();//方式2:创建Thread类的匿名子类的匿名对象
// new Thread(){
// public void run() {
// for(int i = 1; i <= 100; i++){
// if(i%2 == 0){
// System.out.println(Thread.currentThread().getName() + ":" + i);
// }
// }
// }
// }.start();
// new Thread(){
// public void run() {
// for(int i = 1; i <= 100; i++){
// if(i%2 != 0){
// System.out.println(Thread.currentThread().getName() + "--->" + i);
// }
// }
// }
// }.start();//方式3:使用Runnable接口的方式:(提供了Runnable接口匿名实现类的匿名对象)new Thread(new Runnable(){public void run(){for(int i = 1; i <= 100; i++){if(i%2 == 0){System.out.println(Thread.currentThread().getName() + ":" + i);}}}}).start();new Thread(new Runnable(){public void run() {for(int i = 1; i <= 100; i++){if(i%2 != 0){System.out.println(Thread.currentThread().getName() + "--->" + i);}}}}).start();}
}
思考题:判断各自调用的是哪个run
Exer
/*** ClassName: Exer* Package: com.atguigu01.create.exer2* Description:* 思考题:判断各自调用的是哪个run()?* @Author: lwfstart* @Create 2025-03-27 22:22* @Version: 1.0*/
public class Exer {public static void main(String[] args) {A a = new A();a.start(); //① 启动线程 ② 调用Thread类的run()B b = new B(a);b.start();}
}
//创建线程类A
class A extends Thread {@Overridepublic void run() {System.out.println("线程A的run()...");}
}
//创建线程类B
class B extends Thread {private A a;
// public B(A a){ //构造器中,直接传入A类对象
// this.a = a;
// }public B(A a){super(a);}/*** 注释前:* 线程A的run()...* 线程B的run()...* 注释后:* 线程A的run()...* 线程A的run()...*/@Overridepublic void run() {System.out.println("线程B的run()...");
// a.run(); //不用这种写法来表示代理模式}
}Exer_1 另外一个练习题
public class Exer_1 {public static void main(String[] args) {// CC 执行的是继承于Thread子类的run方法,如果匿名子类当中没有方法的话,那就执行BB当中的run方法
// BB b = new BB();
// new Thread(b){
// public void run(){
// System.out.println("CC");
// }
// }.start();new Thread(){}.start();}
}
class AA extends Thread {@Overridepublic void run() {System.out.println("AA");}
}
class BB extends Thread {@Overridepublic void run() {System.out.println("BB");}
}
线程中的常用方法
线程的调度
线程的优先级
线程的分类
if(i % 20 == 0){Thread.yield(); }
线程的生命周期
线程的生命周期,这个图会更好,面试回答的话
总结:
一、线程的常用结构 1.线程中的构造器 > Thread():创建新的Thread对象 > Thread(String threadname):创建线程并指定线程实例名 > Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法 > Thread(Runnable target, String name):创建新的Thread对象,分配一个带有指定目标新的线程对象并指定名字。 2.线程中的常用方法: > start():①启动线程 ②调用线程的run() > run():将线程要执行的操作,声明在run()中 > currentThread():获取当前执行代码对应的线程。 > getName():获取线程名 > setName():设置线程名 > sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数 > yield():静态方法,一旦执行此方法,就释放CPU的执行权 > join():在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行 > isAlive():判断当前线程是否存活 过时方法: > stop():强行结束一个线程的执行,直接进入死亡状态。不建议使用 > void suspend()/void resume():可能造成死锁,所以也不建议使用 3.线程的优先级: getPriority():获取线程的优先级 setPriority():设置线程的优先级。范围[1,10]Thread类内部声明的三个常量: - MAX_PRIORITY(10):最高优先级 - MIN_PRIORITY(1):最低优先级 - NORM_PRIORITY(5):普通优先级,默认情况下main线程具有普通优先级。二、线程的生命周期
class PrintNumber extends Thread {public PrintNumber() {}public PrintNumber(String name) {super(name);}public void run(){for (int i = 1; i <= 100; i++){
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }if(i%2 == 0){System.out.println(Thread.currentThread().getName() +":" + Thread.currentThread().getPriority() +":" + i);}if(i % 20 == 0){Thread.yield();}}}
}
public class EvenNumberTest {public static void main(String[] args) {PrintNumber t1 = new PrintNumber("线程1");t1.setName("子线程1");t1.setPriority(Thread.MIN_PRIORITY);t1.start();Thread.currentThread().setName("主线程");Thread.currentThread().setPriority(Thread.MAX_PRIORITY);for (int i = 1; i <= 100; i++){if(i%2 == 0){System.out.println(Thread.currentThread().getName() +":" + Thread.currentThread().getPriority() +":" + i + "***********************");}if(i == 20){try {t1.join();} catch (InterruptedException e) {throw new RuntimeException(e);}}}System.out.println("子线程1是否存活?" + t1.isAlive()); //子线程1是否存活?false}
}
练习题:
新年倒计时 模拟新年倒计时,每隔1秒输出一个数字,依次输出10,9,8....1,最后输出:新年快乐!
代码:
public class HappyNewYear {public static void main(String[] args) {for (int i = 10; i >= 0; i--) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}if(i > 0) {System.out.println(i);}else {System.out.println("Happy New Year!");}}}
}
面试题:
package com.atguigu02.method_lifecycle.interview;/*** ClassName: ThreadTest* Package: com.atguigu02.method_lifecycle.interview* Description:* 关于Thread.sleep()方法的一个面试题:如下的代码中sleep()执行后,到底是哪个线程进入阻塞状态了呢?* @Author: lwfstart* @Create 2025-03-28 10:47* @Version: 1.0*/
public class ThreadTest {public static void main(String[] args) {//创建线程对象MyThread t = new MyThread();t.setName("线程1");t.start();//调用sleep方法try {t.sleep(1000*5);} catch (InterruptedException e) {throw new RuntimeException(e);}//5秒之后这里才会执行System.out.println("hello World!");}
}
class MyThread extends Thread {public void run(){for(int i = 0; i < 10000; i++){System.out.println(Thread.currentThread().getName() + "--->" + i);}}
}
线程安全问题和解决
总结:
线程的安全问题与线程的同步机制 1.多线程卖票,出现的问题:出现了重票和错票。 2.什么原因导致的? 线程1操作ticket的过程中,尚未结束的情况下,其他线程也参与进来,对ticket进行操作。 3.如何解决?必须保证一个线程a在操作ticket的过程中,其它线程必须等待,直到线程a操作ticket结束以后,其它线程才可以进来继续操作ticket。 4.Java是如何解决线程的安全问题的? 使用线程的同步机制。 方式1: 同步代码块 synchronized(同步监视器){//需要被同步的代码 } 说明: > 需要被同步的代码,即为操作共享数据的代码。 > 共享数据:即多个线程多需要操作的数据。比如:ticket > 需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其它线程必须等待。 > 同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码。 > 同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器, 注意:在实现Runnable接口的方式中,同步监视器可以考虑使用:this。在继承Thread类的方式中,同步监视器要慎用this。可以考虑使用当前类.class方式2: 同步方法说明: > 如果操作共享数据的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。 > 非静态的同步方法,默认同步监视器是this静态的同步方法,默认同步监视器是当前类本身。5.synchronized好处:解决了线程的安全问题。弊端:在操作共享数据时,多线程其实是串行执行的,意味着性能低。
存在安全问题时:
/*** ClassName: WindowTest1* Package: com.atguigu03.threadsafe.notsafe* Description:* 实现继承Thread类的方式,实现卖票* 使用同步代码块的方式解决线程安全问题** @Author: lwfstart* @Create 2025-03-28 08:18* @Version: 1.0*/
class Window extends Thread {static int ticket = 100;@Overridepublic void run() {while (true) {if (ticket > 0) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);ticket--;} else {break;}}}
}public class WindowTest1 {public static void main(String[] args) {Window w1 = new Window();Window w2 = new Window();Window w3 = new Window();w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");w1.start();w2.start();w3.start();}
}/*** ClassName: WindowTest1* Package: com.atguigu03.threadsafe.notsafe* Description:* 实现继承Thread类的方式,实现卖票* 使用同步代码块的方式解决线程安全问题** @Author: lwfstart* @Create 2025-03-28 08:18* @Version: 1.0*/
class Window extends Thread {static int ticket = 100;@Overridepublic void run() {while (true) {if (ticket > 0) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);ticket--;} else {break;}}}
}public class WindowTest1 {public static void main(String[] args) {Window w1 = new Window();Window w2 = new Window();Window w3 = new Window();w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");w1.start();w2.start();w3.start();}
}
线程的同步
线程安全问题的解决:
runnable实现接口方式
同步代码块
/*** ClassName: WindowTest* Package: com.atguigu03.threadsafe.runnablesafe* Description:** @Author: lwfstart* @Create 2025-03-28 09:29* @Version: 1.0*/
class SaleTicket implements Runnable {int ticket = 100;Object obj = new Object();Dog dog = new Dog();public void run() {
// synchronized(this){ //这样线程不安全while (true) {try {Thread.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}
// synchronized(obj){ //obj:是唯一的?yes
// synchronized(dog){ //dog:是唯一的?yes
// synchronized(this){ //this:是唯一的?yes,就是题目中的对象ssynchronized(SaleTicket.class){ //结构:Class clz = Window.class 是唯一的if(ticket > 0){try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);ticket--;}else {break;}}}}
}
public class WindowTest {public static void main(String[] args) {SaleTicket s = new SaleTicket();Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
class Dog{}同步方法
/*** ClassName: WindowTest* Package: com.atguigu03.threadsafe.runnablesafe* Description:* 使用同步方法结局实现Runnable接口的线程安全问题** @Author: lwfstart* @Create 2025-03-28 09:29* @Version: 1.0*/
class SaleTicket1 implements Runnable {int ticket = 100;boolean isFlag = true;public void run() {while (isFlag) {show();}}public synchronized void show() { // 此时的同步监视器就是:this,此题目是this,是唯一的if (ticket > 0) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);ticket--;} else {isFlag = false;}}
}public class WindowTest1 {public static void main(String[] args) {SaleTicket1 s = new SaleTicket1();Thread t1 = new Thread(s);Thread t2 = new Thread(s);Thread t3 = new Thread(s);t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
继承Thread类的方式
同步代码块
/*** ClassName: WindowTest* Package: com.atguigu03.threadsafe.threadsafe* Description:** @Author: lwfstart* @Create 2025-03-28 09:36* @Version: 1.0*/
class Window extends Thread {static int ticket = 100;static Object obj = new Object();@Overridepublic void run() {while (true) {
// synchronized (this) { //this:此时表示w1,w2,w3,不能保证锁的唯一性
// synchronized (obj) { //obj:使用static修饰以后,就能保证其唯一性synchronized (Window.class) { //结构:Class clz = Window.class 是唯一的if(ticket > 0){try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);ticket--;}else {break;}}}}
}
public class WindowTest {public static void main(String[] args) {Window w1 = new Window();Window w2 = new Window();Window w3 = new Window();w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");w1.start();w2.start();w3.start();}
}同步方法
/*** ClassName: WindowTest1* Package: com.atguigu03.threadsafe.threadsafe* Description:* 使用同步方法解决继承Thread类中的线程安全问题* @Author: lwfstart* @Create 2025-03-28 10:02* @Version: 1.0*/
class Window1 extends Thread {static int ticket = 100;static boolean isFlag = true;@Overridepublic void run() {while (isFlag) {show();}}// public synchronized void show(){ //此时同步监视器:this。此题目中this:w1,w2,w3,仍然是线程不安全的public static synchronized void show() { //此时同步监视器:当前类本身,即为Window1.class,是唯一的if (ticket > 0) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);ticket--;} else {isFlag = false;}}
}public class WindowTest1 {public static void main(String[] args) {Window1 w1 = new Window1();Window1 w2 = new Window1();Window1 w3 = new Window1();w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");w1.start();w2.start();w3.start();}
}
练习题:
银行有一个账户。 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。 问题:该程序是否有安全问题,如果有,如何解决? 【提示】 1,明确哪些代码是多线程运行代码,须写入run()方法 2、明确什么是共享数据。 3,明确多线程运行代码中哪些语句是操作共享数据的。安全问题是共享的数据通过多线程的方式来操作
代码:
public class AccountTest {public static void main(String[] args) {Account acct = new Account();Customer customer1 = new Customer(acct, "甲");Customer customer2 = new Customer(acct, "乙");customer1.start();customer2.start();}
}
class Account { //账户private double balance; //余额public synchronized void deposit(double amt) { //this:是不是唯一的?即为acct,是唯一的if(amt > 0){balance += amt;}try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "存钱1000块,余额为:" + balance);}
}
class Customer extends Thread {Account account;public Customer(Account acct){this.account = acct;}//通过 构造器设置名称public Customer(Account acct,String name){super(name);this.account = acct;}@Overridepublic void run() {for (int i = 0; i < 3; i++) {account.deposit(1000);}}
}
同步机制中的锁
单例设计模式之懒汉式(线程安全)
总结:
解决单例模式中的懒汉式的线程安全问题 > 饿汉式:不存在线程安全问题。 > 懒汉式:存在线程安全问题,((需要使用同步机制来处理)
代码:
/*** ClassName: BankTest* Package: com.atguigu04.threadsafemore.singleton* Description:* 实现线程安全的懒汉式* @Author: lwfstart* @Create 2025-03-28 11:36* @Version: 1.0*/
public class BankTest {static Bank b1 = null;static Bank b2 = null;public static void main(String[] args) {Thread t1 = new Thread(){@Overridepublic void run() {b1 = Bank.getInstance();}};Thread t2 = new Thread(){@Overridepublic void run() {b2= Bank.getInstance();}};t1.start();t2.start();//保证这两个线程都进来参与完成之后,才开始执行后面主线程的代码,防止出现了b1与b2为空null的情况try {t1.join();} catch (InterruptedException e) {throw new RuntimeException(e);}try {t2.join();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(b1);System.out.println(b2);System.out.println(b1 == b2);}
}
class Bank {private Bank(){}private static volatile Bank instance = null;//实现线程安全的方式1
// public static synchronized Bank getInstance(){ //同步监视器,默认为Bank.class
// if(instance == null){
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// instance = new Bank();
// }
// return instance;
// }//实现线程安全的方式2
// public static Bank getInstance(){ //同步监视器,默认为Bank.class
// synchronized (Bank.class){
// if(instance == null){
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// instance = new Bank();
// }
// return instance;
// }
// }//实现线程安全的方式3:相较于方式1和方式2来讲,效率更高,为了避免出现指令重排,需要将instance声明为volatile//假如有三个线程,第一个和第二个线程进入时instance都为空,进入synchronized只能是第一个线程能进入,此时instance已经被new好了,//第二个线程就不会进入到synchronized中,第三个线程也就不会通过第一个if语句判断public static Bank getInstance(){if(instance == null){synchronized (Bank.class){if(instance == null){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}instance = new Bank();}}}return instance;}
}
线程的死锁问题
总结:
线程的同步机制带来的问题:死锁 1.如何看待死锁? 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。 我们编写程序时,要避免出现死锁。 2.诱发死锁的原因? - 互斥条件 - 占用且等待 - 不可抢夺(或不可抢占) - 循环等待 3.如何避免死锁? 死锁一旦出现,基本很难人为干预,只能尽量规避。可以考虑打破上面的诱发条件。 针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。 针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。 针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源.。 针对条件4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题。
代码:
public class DeadLoackTest {public static void main(String[] args) {/*** ab* 12* abcd* 1234*/StringBuilder s1 = new StringBuilder();StringBuilder s2 = new StringBuilder();new Thread(){@Overridepublic void run() {synchronized (s1){s1.append("a");s2.append("1");try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (s2){s1.append("b");s2.append("2");System.out.println(s1);System.out.println(s2);}}}}.start();//死锁:上面的线程第一个同步代码块中,s1被拿到了,然后sleep了一分钟//下面的线程第一个同步代码块中,s2被拿到了,然后sleep//上面的想成第二个同步代码块中要拿s2,下面的线程第二个同步代码块中要拿s1这个锁//两个线程都在等待拿到对方占有的同步资源new Thread(){@Overridepublic void run() {synchronized (s2){s1.append("c");s2.append("3");try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (s1){s1.append("d");s2.append("4");System.out.println(s1);System.out.println(s2);}}}}.start();}
}
代码2:
class A {public synchronized void foo(B b){System.out.println("当前线程名:" + Thread.currentThread().getName()+ " 进入了A实例的foo方法"); //②try {Thread.sleep(200);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("当前线程名:" + Thread.currentThread().getName()+ " 企图调用B实例的last方法"); //②b.last();}public synchronized void last(){System.out.println("进入了A类的last方法内部");}
}
class B {public synchronized void bar(A a){System.out.println("当前线程名:" + Thread.currentThread().getName() + " 进入了B实例的bar方法"); //②try {Thread.sleep(200);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("当前线程名:" + Thread.currentThread().getName()+ " 企图调用A实例的last方法"); //②a.last();}public synchronized void last(){System.out.println("进入了B类的last方法内部");}
}
public class DeadLock implements Runnable {A a = new A();B b = new B();public void init(){Thread.currentThread().setName("主线程");//调用a对象的foo方法a.foo(b);System.out.println("进入了主线程之后");}public void run(){Thread.currentThread().setName("副线程");//调用b对象的bar方法b.bar(a);System.out.println("进入了副线程之后");}public static void main(String[] args) {DeadLock dl = new DeadLock();new Thread(dl).start();dl.init();}
}
Lock(锁)
总结:
除了使用synchronized同步机制处理线程安全问题之外,还可以使用jdk5.0提供的Lock锁的方式 1.步骤: 1、创建lock的实例,需要确保多个线程共用同一个Lock实例!需要考虑将次对象声明为static final 2.执行lock方法,锁定对共享资源的调用 3.unlock()的调用,释放对共享数据的锁定2.面试题: synchronized同步的方式与Lock的对比? synchronized不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放对同步监视器的调用。 Lock是通过两个方法控制需要被同步的代码,更灵活一些。 Lock作为接口,提供了多种实现类,适合更多更复杂的场景,效率更高。
代码:
import java.util.concurrent.locks.ReentrantLock;/*** ClassName: WindowTest1* Package: com.atguigu03.threadsafe.notsafe* Description:* 实现继承Thread类的方式,实现卖票* 使用同步代码块的方式解决线程安全问题** @Author: lwfstart* @Create 2025-03-28 08:18* @Version: 1.0*/
class Window extends Thread {static int ticket = 100;//1、创建lock的实例,需要确保多个线程共用同一个Lock实例!需要考虑将次对象声明为static finalprivate static final ReentrantLock lock = new ReentrantLock();@Overridepublic void run() {while (true) {try {//2.执行lock方法,锁定对共享资源的调用lock.lock();if (ticket > 0) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);ticket--;} else {break;}}finally {//3.unlock()的调用,释放对共享数据的锁定lock.unlock();}}}
}public class LockTest {public static void main(String[] args) {Window w1 = new Window();Window w2 = new Window();Window w3 = new Window();w1.setName("窗口1");w2.setName("窗口2");w3.setName("窗口3");w1.start();w2.start();w3.start();}
}
线程的通信
总结:
1.线程间通信的理解 当我们`需要多个线程`来共同完成一件任务,并且我们希望他们有规律的执行」 那么多线程之间需要一些通信机制, 可以协调它们的工作,以此实现多线程共同操作一份数据。 2.涉及到三个方法的使用: wait():线程一旦执行此方法,就进入等待状态。同时,会释放对同步监视器的调用 notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程。(如果被wait()的多个线程的优先级相同,随机唤醒一个)。 notifyALL(): 一旦执行此方法,就会唤醒所有被wait的线程, 3.注意点: > 此三个方法的使用,必须是在同步代码块或同步方法中。(超纲:Lock需要配合Condition实现线程间的通信) > 此三个方法的调用者,必须是同步监视器。否则,会报IllegalMonitorStateException异常 > 此三个方法声明在0bject类中。4.案例: 案例1:使用两个线程打印 1-100。线程1,线程2 交替打印 案例2:生产者&消费者 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有 固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品 了再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来 取走产品。5.wait()和 sleep()的区别? 相同点:一旦执行,当前线程都会进入阻塞状态不同点: > 声明的位置:wait():声明在0bject类中sleep():声明在Thread类中,静态的 > 使用的场景不同:wait():只能使用在同步代码块或同步方法中sLeep():可以在任何需要使用的场景 > 使用在同步代码块或同步方法中:wait():一旦执行,会释放同步监视器sleep():一旦执行,不会释放同步监视器 > 结束阻塞的方式:wait():到达指定时间自动结束阻塞 或 通过被notify唤醒,结束阻塞sleep():到达指定时间自动结束阻塞
代码:
class PrintNmber implements Runnable {private int number = 1;Object obj = new Object();@Overridepublic void run() {while (true) {
// synchronized (this) {synchronized (obj) { //运行报错IllegalMonitorStateExceptionobj.notify();
// notify(); //省略thisif (number <= 100) {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() + ":" + number);number++;try {
// wait();obj.wait(); //线程一旦 执行此方法就进入等到状态,会释放对同步监视器的调用} catch (InterruptedException e) {throw new RuntimeException(e);}} else {break;}}}}
}public class PrintNumberTest {public static void main(String[] args) {PrintNmber p = new PrintNmber();Thread t1 = new Thread(p, "线程1");Thread t2 = new Thread(p, "线程2");t1.start();t2.start();}
}
面包消费问题:
/*** ClassName: ProducerConsumerTest* Package: com.atguigu05.communication* Description:* 案例2:生产者&消费者生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有* 固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生* 产;如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。** 分析:* 1.是否是多线程问题? 是,生产者、消费者* 2.是否有共享数据?有!共享数据是:产品* 3.是否有线程安全问题? 有!因为有共享数据* 4.是否需要处理线程安全问题?是!如何处理?使用同步机制* 5.是否存在线程间的通信? 存在。** @Author: lwfstart* @Create 2025-03-28 16:12* @Version: 1.0*/
class Clerk { //店员private int productNum = 0; //产品的数量//增加产品数量的方法public synchronized void addProduct(){if(productNum >= 20){//等待try {wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else {productNum++;System.out.println(Thread.currentThread().getName() + "生产了第" + productNum + "个产品");//唤醒notifyAll();}}//减少产品数量的方法public synchronized void minusProduct(){if(productNum <= 0){//等待try {wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else {System.out.println(Thread.currentThread().getName() + "消费了第" + productNum + "个产品");productNum--;//唤醒notifyAll();}}
}
class Producer extends Thread { //生产者private Clerk clerk;public Producer(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {while (true) {System.out.println("生产者开始生产产品...");try {Thread.sleep(50);} catch (InterruptedException e) {throw new RuntimeException(e);}clerk.addProduct();}}
}
class Consumer extends Thread { //消费者private Clerk clerk;public Consumer(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {while (true) {System.out.println("消费者开始消费产品...");try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}clerk.minusProduct();}}
}
public class ProducerConsumerTest {public static void main(String[] args) {Clerk clerk = new Clerk();Producer pro1 = new Producer(clerk);Consumer con1 = new Consumer(clerk);Consumer con2 = new Consumer(clerk);pro1.setName("生产者1");con1.setName("消费者1");con2.setName("消费者2");pro1.start();con1.start();con2.start();}
}
JDK5.0 新增线程创建方式
新增方式一:实现Callable接口
新增方式二:使用线程池
总结:
1.创建多线程的方式三:实现Callable(jdk5.0新增的) 与之前的方式的对比:与Runnable方式的对比的好处 > call()可以有返回值,更灵活 > call()可以使用throws的方式处理异常,更灵活 > Callable使用了泛型参数,可以指明具体的call()的返回值类型,更灵活有缺点吗?如果在主线程中需要获取分线程ca11()的返回值,则此时的主线程是阻塞状态的。2.创建多线程的方式四:使用线程池此方式的好处: > 提高了程序执行的效率。(因为线程已经提前创建好了) > 提高了资源的复用率。(因为执行完的线程并未销毁,而是可以继续执行其他的任务) > 可以设置相关的参数,对线程池中的线程的使用进行管理
代码:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;/*** ClassName: Callable* Package: com.atguigu06.createmore* Description:* 创建多线程的方式三:实现Callable(jdk5.0新增的)* @Author: lwfstart* @Create 2025-03-28 17:41* @Version: 1.0*/
//1.创建一个实现Callable的实现类
class NumThread implements Callable {//2.实现call方法,将此线程需要执行的操作声明在call()中@Overridepublic Object call() throws Exception {int sum = 0;for(int i = 1; i <= 100; i++) {if(i%2==0){System.out.println(i);sum += i;}Thread.sleep(100);}return sum;}
}
public class CallableTest {public static void main(String[] args) {//3.创建Callable接口实现类的对象NumThread numThread = new NumThread();//4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象FutureTask futureTask = new FutureTask(numThread);//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()Thread t1 = new Thread(futureTask);t1.start();try {//6.获取Callable中call方法的返回值//get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值Object sum = futureTask.get();System.out.println("总和为:" + sum);}catch (Exception e) {e.printStackTrace();}}
}
代码2:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;/*** ClassName: ThreadPool* Package: com.atguigu06.createmore.pool* Description:* 创建并使用多线程的第四种方法:使用线程池* @Author: lwfstart* @Create 2025-03-28 19:16* @Version: 1.0*/
class NumberThread implements Runnable {public void run(){for(int i = 0;i <= 100; i++){if(i%2==0){System.out.println(Thread.currentThread().getName() + ":"+i);}}}
}
class NumberThread1 implements Runnable {public void run(){for (int i = 1; i <= 100; i++){if(i%2 != 0){System.out.println(Thread.currentThread().getName() + ":"+i);}}}
}
public class ThreadPool {public static void main(String[] args) {//1.提供指定线程数量的线程池ExecutorService service = Executors.newFixedThreadPool(10);ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;//设置线程池的属性
// System.out.println(service.getClass()); //设置线程池中线程数的上线service1.setMaximumPoolSize(50); //设置线程池中线程数的上限//2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象service.execute(new NumberThread()); //适合适用于Runnableservice.execute(new NumberThread1()); //适合适用于Runnable//service.submit(Callable callable);//适合适用于Callable//3.关闭连接池service.shutdown();}
}