JavaSE学习笔记总结day18(完结!!!)
迪丽瓦拉
2024-05-29 00:08:26
0

今日内容

零、 复习昨日
一、作业
二、进程与线程
三、创建线程
四、线程的API
五、线程状态
六、线程同步

零、 复习昨日

晨考

一、作业

见答案

二、进程与线程[了解]

一个进程就是一个应用程序,进程包含线程
一个进程至少包含一个线程,大部分都是有多条线程在执行任务(多线程)

进程是系统资源分配的最小单位

线程是进程内部的一个执行任务

进程内多个线程是共享进程资源,线程是资源调度的最小单位

Java程序是否是多线程的吗?
答: 是! 至少有main线程,垃圾回收线程等等

三、创建线程[重点]

Thread

创建线程的方式有很多

  • 继承Thread
  • 实现Runnable接口
  • 使用Callable 和Future来完成
  • 使用线程池获得线程

3.1 继承Thread

步骤

  1. 自定义类
  2. 继承Thread
  3. 重写run方法
  4. 创建子类对象
  5. 调用start方法启动线程 [特别强调,开启新线程的方法是start]
    start方法内部执行时会调用run方法执行
public class MyThread extends Thread{// 重写方法,方法内部,就是该线程执行的任务@Overridepublic void run() {for (int i = 1; i < 11; i++) {System.out.println("MyThread --> "+i );}}
}
    public static void main(String[] args) {// 创建一个线程MyThread myThread = new MyThread( );// 启动线程myThread.start();// ==============main线程执行====================for (int i = 1; i < 11; i++) {System.out.println("main-Thread" + i );}// 自定义线程和主线程同时执行,并且出现资源争抢情况}

使用匿名内部类的形式开启线程

        // ==========匿名内部类的方法开启线程==============new Thread(){@Overridepublic void run() {for (int i = 1; i < 1001; i++) {System.out.println("my-Thread-2 --> " + i );}}}.start();

myThread线程,main线程,my-Threah-2线程3个在争抢资源,如果没有出现效果,把主线程执行的代码放在最下方,因为主线程代码优先级较高,会优先执行…

3.2 实现Runnable接口

步骤

  1. 自定义类
  2. 实现Runnable接口
  3. 重写run方法
  4. 创建子实现类对象
  5. 把子实现类对象当构造方法的方法参数来创建Thread对象
  6. 由thread调用start方法开启线程
public class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 1; i < 101; i++) {System.out.println("MyRunnable - 1 --> " + i);}}
}
    public static void main(String[] args) {// 4 创建实现类对象MyRunnable myRunnable = new MyRunnable( );// 5 把对象当构造方法参数,创建Thread对象Thread thread = new Thread(myRunnable);// 6 开启线程thread.start();}

匿名内部类的形式创建线程

// ============= 匿名内部类完成实现Runnable接口 ============
new Thread(new Runnable(){@Overridepublic void run() {for (int i = 1; i < 101; i++) {System.out.println("MyRunable-2 --> " + i );}}
} ).start();

3.3 区别

继承Thread开启线程和实现Runnable接口开启线程有什么区别?


  • 一个继承,一个实现
  • 继承Thread后,直接创建对象即可调用start开启线程
  • 实现Runnable接口的子类,还需要再创建Thread类对象才可以调用strat开启线程

从使用便捷度来说,继承Thread开启线程会方便一点…

继承Thread类就限制了该类再继承别的类,因为类只能单继承,而实现接口的同时可以继承别的类,并且还允许多继承.

所以推荐使用接口来实现.

四、线程的API[熟悉]

  • void start() 开启线程,会自动run方法执行线程任务
  • void run() 执行线程的方法
    • run() 方法是start开启线程后,JVM自动调用
  • void setName(String name) 给线程设置名字
    • 也可以通过构造方法,在创建线程时指定线程名
  • String getName() 获得线程的名字
  • static Thread currentThread() 返回当前正在执行的线程对象
  • setPriority(int priority) 设置优先级
    • 级别是1-10 ,默认是5
  • getPriority() 获得优先级
  • join() 加入线程,等待该线程终止
  • join(int milles) 加入线程,最大等待直到毫秒数
  • void setDaemon(boolean on) 设置守护线程
  • static void yield() 礼让线程,暂停当前线程,让其他线程执行
  • static void sleep(long milles) 线程休眠
    • 会让当前线程暂停执行,让出系统资源,让其他线程执行
    • 时间到,当前会继续和其他争抢资源执行
  • void stop() 结束当前线程,线程死亡(已过式)
    • 被废弃的原因是因为这个中断线程太直接太暴力…
package com.qf.thread;/*** --- 天道酬勤 ---** @author QiuShiju* @desc 演示线程API*/
public class TestThread3 {public static void main(String[] args) {testStop();}/*** 测试线程停止*/public static void testStop() {Thread thread = new Thread("好人") {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {if (i == 5000) {Thread.currentThread().stop();}System.out.println(Thread.currentThread( ).getName( ) + " ---> " + i);}}};thread.start( );}public static void testSleep2() {Thread thread2 = new Thread("人") {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println(Thread.currentThread( ).getName( ) + " --->" + i);}}};Thread thread = new Thread("好人") {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {if (i == 500) {try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace( );}}System.out.println(Thread.currentThread( ).getName( ) + " ---> " + i);}}};thread.start( );thread2.start( );}/*** 演示线程休眠*/public static void testSleep() {System.out.println("倒计时.." );for (int i = 5; i > 0; i--) {System.out.println(i );// 线程休眠1秒钟try {// 让当前主线程睡觉Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace( );}}System.out.println("发射!" );}/*** 演示加入线程*/public static void testJoin() {Thread thread2 = new Thread("人") {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println(Thread.currentThread( ).getName( ) + " --->" + i);}}};Thread thread = new Thread("好人") {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {if (i == 500) {try {// 线程1(好人)执行到500,让线程2(人)加入执行,直到该线程结束,当前线程再继续执行//thread2.join();// 线程1(好人)执行到500,让线程2(人)加入执行,让其运行指定毫秒时间// 时间到,线程再争抢资源thread2.join(10);} catch (InterruptedException e) {e.printStackTrace( );}}System.out.println(Thread.currentThread( ).getName( ) + " ---> " + i);}}};thread.start( );thread2.start( );}/*** 演示礼让线程*/public static void testYield() {Thread thread = new Thread("好人") {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {// 获得当前正在执行线程对象if (i == 500) {Thread.yield( );}System.out.println(Thread.currentThread( ).getName( ) + " ---> " + i);}}};Thread thread2 = new Thread("人") {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println(Thread.currentThread( ).getName( ) + " --->" + i);}}};thread.start( );thread2.start( );}/*** 演示守护线程*/public static void testDaemon() {Thread thread = new Thread("皇上") {@Overridepublic void run() {for (int i = 0; i < 1; i++) {// 获得当前正在执行线程对象System.out.println(Thread.currentThread( ).getName( ) + " ---> " + i);}}};Thread thread2 = new Thread("妃子") {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println(Thread.currentThread( ).getName( ) + " --->" + i);}}};// 开启前设置某个线程为守护线程thread2.setDaemon(true);// 一旦设置一个线程为守护线程,那么其他的线程就是被守护的thread.start( );thread2.start( );}/*** 演示关于线程优先级的方法*/public static void testPriority() {Thread thread = new Thread("和谐号1") {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {// 获得当前正在执行线程对象System.out.println(Thread.currentThread( ).getName( ) + " ---> " + i);}}};Thread thread2 = new Thread("复兴号2") {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println(Thread.currentThread( ).getName( ) + " --->" + i);}}};// 默认优先级是5,System.out.println(thread.getName( ) + "--->" + thread.getPriority( ));System.out.println(thread2.getName( ) + "--->" + thread2.getPriority( ));// 设置优先级,最高是10,最低是1,不能越界// 注意不是一定优先级高的先执行,只是大概率上会是thread.setPriority(1);thread2.setPriority(10);thread.start( );thread2.start( );}/*** 演示关于线程名字的方法*/public static void testName() {// 通过构造方法设置线程名Thread thread = new Thread("和谐号") {@Overridepublic void run() {for (int i = 0; i < 10; i++) {// 获得当前正在执行线程对象System.out.println(Thread.currentThread( ).getName( ) + " ---> " + i);}}};Thread thread2 = new Thread( ) {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread( ).getName( ) + " --->" + i);}}};// 获得线程名// 默认线程名,从Thread-0开始//System.out.println(thread.getName( ));// 通过setName设置线程名thread2.setName("绿皮");//System.out.println(thread2.getName( ));thread.start( );thread2.start( );// 获得主线程的名字System.out.println(Thread.currentThread( ).getName( ));}
}

五、线程状态[面试]

线程的几种状态:

  • 创建/新建/初始
    • new 完线程对象
  • 就绪
    • 调用start
  • 等待/阻塞
    • join()或者sleep()
  • 运行
    • 执行run()方法
  • 死亡/销毁/终止
    • run方法执行完,或者调用stop()或者interrupt()中断等

清楚状态之间的转换

image-20230302170320045

六、线程安全[重点]

6.1 线程安全

  • 临界资源:共享资源(同⼀个对象),一次只可以有一个线程操作,才可以保证准确性

  • 原子操作:不可拆分的步骤,被视作一个整体。其步骤不能打乱

线程不安全: 1) 完整的步骤可能会被破坏 2) 线程内的数据可能被别的线程修改

6.2 线程安全方式

  • 同步方法
    • 给方法加锁,即设置同步锁关键词synchronized
    • 锁对象是当前对象,即this
  • 同步代码块
    • 将需要安全的代码使用同步代码块包裹,设置锁对象.
    • 锁可以是任意对象
    • 但是线程之前应该是同一把锁才能锁住,保证安全

其实就是给需要"同步",需要"安全",需要"步骤一致,不能打乱"的代码加锁.

需求: 要求打印机类的两个方法分别被两个线程同时执行.但是方法执行时不能被破坏,即方法执行时不能被另外一个线程抢走资源,要保证方法执行时步骤完整性.

package com.qf.sync;import java.util.Date;/*** --- 天道酬勤 ---** @author QiuShiju* @desc 打印机*/
public class Printer {// 锁需要是同一对象private Object obj = new Object();// 打印机1号public synchronized void print1() {//synchronized (obj){System.out.print(1+" ");System.out.print(2+" ");System.out.print(3+" ");System.out.print(4+" ");System.out.print("\r\n");//}}// 打印机2号public synchronized void print2() {//synchronized (obj) {System.out.print("一 ");System.out.print("二 ");System.out.print("三 ");System.out.print("四 ");System.out.print("\r\n");//}}
}
    public static void main(String[] args) {Printer printer = new Printer( );// 线程1new Thread(){@Overridepublic void run() {while(true){printer.print1();}}}.start();// 线程2new Thread(){@Overridepublic void run() {while(true){printer.print2();}}}.start();}
}

换种方式写

public class TestSync2 {public static void main(String[] args) {// 线程1new Thread( ) {@Overridepublic void run() {for (; ; ) {synchronized (TestSync2.class) {System.out.print(1 + " ");System.out.print(2 + " ");System.out.print(3 + " ");System.out.print(4 + " ");System.out.print("\r\n");}}}}.start( );// 线程2new Thread( ) {@Overridepublic void run() {for (; ; ) {synchronized (TestSync2.class) {System.out.print("一 ");System.out.print("二 ");System.out.print("三 ");System.out.print("四 ");System.out.print("\r\n");}}}}.start( );}
}

6.3 转账案例

需求: 假设银行账户类,属性有卡号,余额,名字 ; 另外有ATM机器,机器内能接收账户,给ATM指定取的钱数.ATM支持多线程操作.

// 账户
public class Account {private String id;private double balance;public Account() {}public Account(String id, double balance) {this.id = id;this.balance = balance;}public String getId() {return id;}public void setId(String id) {this.id = id;}public double getBalance() {return balance;}public void setBalance(double balance) {this.balance = balance;}
}
public class ATM extends Thread {private Account account;private double money;// 取钱是多线程操作@Overridepublic void run() {synchronized (ATM.class){if (account.getBalance() >= money) {// 模拟出钱的时间,目的是让出资源,让另外的线程执行,模拟争抢// 加锁后,sleep"抱着锁睡觉",是说线程休眠不会让出锁,即不会让出资源,其他线程无法执行,// 等着这个线程休眠结束并且执行完毕try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace( );}account.setBalance(account.getBalance() - money);System.out.println(Thread.currentThread().getName() + "取钱成功,取出" +money );System.out.println("余额:" + account.getBalance() );} else {System.out.println("你有多少钱,心里没数?!" );}}}public ATM() {}public ATM(Account account, double money,String name) {super(name);this.account = account;this.money = money;}public Account getAccount() {return account;}public void setAccount(Account account) {this.account = account;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}
}
public class Bank {public static void main(String[] args) {Account account = new Account("888888", 1000);new ATM(account,600,"小红").start();new ATM(account,600,"小黑").start();}
}

变式: 转账案例使用Runable接口来实现

练习

售票: 有一个窗口类售票,总共100张票,窗口不止一个可以多个窗口同时售票. 要保证票不能卖超,不能卖重复的票

6.4 线程安全的方式

不只有synchronized同步方法可以保证线程安全,其实还有很多

ThreadLocal

Volatile

Lock

ReentrantLock

相关内容