声明:以下笔记来源于韩顺平视频https://www.bilibili.com/video/BV1fh411y7R8?p=1,笔记为楼主亲手劳动,劳动不易,转载请标明谢谢。
线程
创建线程的两种方式
- 继承Thread类,重写run方法
- 实现Runnable接口,重写run方法
- 关于为什么调用 .start() 而不是直接调用 .run() 方法,因为如果直接调用.run() 方法就是主线程调用,而不是开启了多线程,主线程会阻塞在.run() 方法处,调用.start()方法,会让jvm机调用 本地 .start0()方法,根据不同的操作系统与CPU,内存,IO等资源开辟新的线程。才能实现多线程。
静态代理模式
使用到
静态代理模式
的一个实例就是 子类要用到某个方法,但是没有实现,可以扔给其它实现其接口的父类,帮忙执行public class StaticProxy_ { public static void main(String[] args) { // dog.start(); Dog dog = new Dog(); Thread thread = new Thread(dog); thread.start(); } } class ThreadProxy implements Runnable {//模拟代理模式 private Runnable target = null; @Override public void run() { if (target != null) { target.run(); } } public ThreadProxy(Runnable target) {//其实是上转型对象 this.target = target; } } class Animal {} class Dog extends Animal implements Runnable{ @Override public void run() { } }
继承 Thread 与实现 Runnable 的区别
- 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thread类本身就实现了Runnable接口
- 实现Runnable接口方式更加适合多个线程共享一个资源的情况,并且避免了单继承的限制
- 建议使用 Runnable 接口
多线程售票问题
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();*/ SellTicket02 sellTicket02 = new SellTicket02(); new Thread(sellTicket02).start(); new Thread(sellTicket02).start(); new Thread(sellTicket02).start(); } } class SellTicket01 extends Thread { public static int ticketNum = 100; @Override public void run() { while (true) { if (ticketNum <= 0) { System.out.println("售票结束~~"); break; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + "售出" + --ticketNum + "张票~"); } } } class SellTicket02 implements Runnable{ public int ticketNum = 100; @Override public void run() { while (true) { if (ticketNum <= 0) { System.out.println("售票结束~~"); break; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + "售出" + --ticketNum + "张票~"); } } }
控制线程退出
当线程完成任务后,会自动退出。
还可以通过使用变量来控制run方法退出的方式停止线程,即
通知方式
public class TreadExit_ { public static void main(String[] args) throws InterruptedException { T t = new T(); t.start(); Thread.sleep(10 * 1000); t.setLoop(false); } } class T extends Thread { private int count = 0; private boolean loop = true; @Override public void run() { while (loop) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T线程正在运行" + ++count ); } } public void setLoop(boolean loop) { this.loop = loop; } }
线程常用方法
setName
//设置线程名称,使之与参数name相同
getName
//返回该线程的名称
start
//使该线程开始执行; Java虚拟机底层调用该线程的startO方法
run
//调用线程对象 run方法;
setPriority
//更改线程的优先级
getPriority
//获取线程的优先级
sleep
//在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
interrupt
//中断线程
yield
:线程的礼让。让出cpu, 让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功,如果CPU资源充足,几乎不会礼让成功。
join
:线程的插队。插队的线程旦插队成功, 则肯定先执行完插入的线程所有的任务
案例:创建-个子线程,每隔1s输出hello,输出20次,主线程每隔1秒,输出hi ,输出20次.要求:两个线
程同时执行,当主线程输出5次后,就让子线程运行完毕,主线程再继续。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); if (i == 5) { System.out.println("主线程让子线程先吃包子"); // t2.join();//插队 Thread.yield();//礼让 System.out.println("主线程继续吃包子"); } System.out.println("主线程吃了" + i + "个包子"); } } } class T2 extends Thread { @Override public void run() { for (int i = 1; i <= 20; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程吃了" + i + "个包子"); } } }
用户线程与守护线程
用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束
常见的守护线程:垃圾回收机制
设置守护进程
.setDaemon()
public class ThreadMethod03_ { public static void main(String[] args) throws InterruptedException { MyDaemonThread myDaemonThread = new MyDaemonThread(); myDaemonThread.setDaemon(true); myDaemonThread.start(); for (int i = 0; i < 10; i++) { System.out.println("强在辛苦的工作" + i); Thread.sleep(1000); } } } class MyDaemonThread extends Thread { @Override public void run() { for (;;) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("宋和马在疯狂聊天"); } } }
线程的状态 (线程的声明周期)
NEW
尚未启动的线程处于此状态。RUNNABLE
在Java虚拟机中执行的线程处于此状态。BLOCKED
被阻塞等待监视器锁定的线程处于此状态。WAITING
正在等待另一个线程执行特定动作的线程处于此状态。TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。TERMINATED
已退出的线程处于此状态。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vzDqYxZ1-1648481367329)(D:/Typora%20Data/Img/image-20220313163745510.png)]
public class ThreadState_ { public static void main(String[] args) throws InterruptedException { T t = new T(); System.out.println(t.getName() + " 的状态为 " + t.getState()); t.start(); while(t.getState() != Thread.State.TERMINATED) { System.out.println(t.getName() + " 的状态为 " + t.getState()); Thread.sleep(1000); } System.out.println(t.getName() + " 的状态为 " + t.getState()); } } 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; } } }
线程同步机制 Synchronized
- 在多线程编程,-些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
- 也可以这里理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作.
互斥锁
JAVA语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性.
每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象.
关键字同步来与对象的互斥锁联系.当某个对象用同步修饰时表明该对象在任一时刻只能由一个线程访问
同步的局限性:导致程序的执行
效率要降低
同步方法(非静态的)的锁
可以是this
,也可以是其他对象
(要求是同一个对象
)
同步方法(静态的)
的锁为当前类本身.类.class
注意事项与细节
同步方法如果没有
使用static修饰:默认锁对象为this
如果方法使用static修饰,默认锁对象:当前类.class
Synchronized (this) 是非公平锁
实现的落地步骤:
需要先分析。上锁的代码
选择同步代码 块或同步方法
要求
多个线程的锁对象为同个
即可!SellTicket01 sellTicket01 = new SellTicket01();//继承Thread类的不同对象 SellTicket01 sellTicket02 = new SellTicket01(); SellTicket01 sellTicket03 = new SellTicket01(); sellTicket01.start(); sellTicket02.start(); sellTicket03.start();
SellTicket03 sellTicket03 = new SellTicket03();//实现Runnable接口的同一对象 new Thread(sellTicket03).start(); new Thread(sellTicket03).start(); new Thread(sellTicket03).start();
class SellTicket03 implements Runnable{ private boolean loop = true; Object object= new Object(); public static void m2(){ synchronized (SellTicket03.class) {//this 会报错 cannot be referenced from a static context System.out.println("这里不可以填this"); } } public /*synchronized*/ void sell() { synchronized ( /*this*/ object) {//对象锁 非静态的锁可以是加在不同的对象上 if (ticketNum <= 0) { System.out.println("售票结束~~"); loop = false; return; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("窗口 " + Thread.currentThread().getName() + "售出" + --ticketNum + "张票~"); } } public int ticketNum = 100; @Override public void run() { while (loop) { sell(); } } }
释放锁
以下情况会释放锁
- 当前线程的同步方法、同步代码块
执行结束
- 当前线程在同步代码块、同步方法中遇到
break、return
。- 当前线程在同步代码块、同步方法中出现了未处理的
Error或Exception
,导致异常结束- 当前线程在同步代码块、同步方法中执行了线程对象的
wait()
方法,当前线程暂停,并释放锁。以下情况不会释放锁
- 线程执行同步代码块或同步方法时,程序调用
Thread.sleep()、Thread.yield()
方法暂停当前线程的执行,不会释放锁- 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
提示:应尽量避免使用suspend(和resume()
来控制线程,方法不再推荐使用
编程题
有2个用户分别从同一个卡上取钱(总额: 10000)
每次都取1000,当余额不足时,就不能取款了
不能出现超取现象=》线程同步问题.
public class HomeWork02 { public static void main(String[] args) { T t = new T(); Thread thread = new Thread(t); thread.setName("t1"); Thread thread2 = new Thread(t); thread2.setName("t2"); thread.start(); thread2.start(); } } class T implements Runnable { private static int money = 10000; @Override public void run() { while (true) { synchronized (this) { if (money <= 0) { System.out.println("余额不足~~"); break; } money -= 1000; System.out.println(Thread.currentThread().getName() + "取走了1000,当前余额为" + money); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName() + "线程退出~~"); } }