【韩顺平讲Java】一天学会线程 Thread Synchronized 互斥锁 进程 并行 并发 死锁等笔记
线程
线程介绍
进程
- 进程是指运行中的程序,操作系统为进程分配内存空间
- 进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有他自身产生、存在和消亡的过程。
线程
-
线程由进程创建的,是进程的一个实体
-
一个进程可以拥有多个线程
-
单线程:同一时刻,只允许执行一个线程
-
多线程哼:同一时刻,可以执行多个线程
-
并发**:同一时刻,多个任务交替执行**,造成一种“貌似同时”的错觉,简单来说,单核CPU实现的多任务就是并发。(eg:人只有一个大脑,一心多用)
-
并行:**多个任务同时执行。**多核CPU可以实现并行。
-
并发和并行可以在同一台电脑中存在
java获取电脑进程信息
public class RuntimeTest { public static void main(String[] args) { //获取java程序关联的运行时对象 var rt=Runtime.getRuntime(); //获取当前电脑的cpu数-->核心数:即可同时执行的进程数availableProcessors System.out.println("处理器数量:"+rt.availableProcessors()); System.out.println("空闲内存数:"+rt.freeMemory()); System.out.println("总内存数:"+rt.totalMemory()); System.out.println("可用最大内存数:"+rt.maxMemory()); } }
线程基本使用
- 继承Thead 类,重写run方法
- 实现Runnable接口,重写run方法
线程使用案例-继承Thead 类,重写run方法
/*
* 演示通过继承Thead类 创建线程
* */
import jdk.swing.interop.SwingInterOpUtils;
public class Thead01 {
public static void main(String[] args) {
//创建一个cat对象,可以当做线程使用
//crtl+j 显示所有快捷键的快捷键
Cat cat = new Cat();
//start() 源码
/*
(1)
public synchronized void start() {
start0();
}
(2)
//start0 是本地方法,是JVM调用,底层是c/c++实现
//真正实现多线程的作用,是start0(),而不是run方法。实际上是start0()中以多线程的机制调用run方法
private native void start0();
*/
cat.start(); //启动线程,会自动调用run方法
//cat.run(); //run方法就是一个普通的方法,没有真正启动一个线程,就会把run方法执行完毕,才向下执行。相当于串行化,而不是多线程
//说明:当main线程启动一个子线程 Thread-0, 主线程不会阻塞,会继续执行
System.out.println("主线程" + Thread.currentThread().getName() + "继续执行");
for (int i = 0; i < 10; i++) {
System.out.println("主线程 i=" + i);
//让主线程休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//1. 当一个类继承了Thead类,该类就可以当成线程使用
class Cat extends Thread {
@Override
public void run() { //重写run方法,实现自己的业务逻辑
int time = 0;
while (true) {
//该线程每隔1秒,在控制台输出“喵喵,我是小猫咪”
System.out.println("喵喵,我是小猫咪" + (++time) + " 线程名称:" + Thread.currentThread().getName());
//让线程休眠1秒 crtl+alt+t
try {
Thread.sleep(1000); //单位是毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (time == 8) {
break;
}
}
}
}
线程使用案例-实现Runnable接口,重写run方法
//实现Runnable接口,重写run方法
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();
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{ //可以把ThreadProxy类 当成Thread
private Runnable target=null;
@Override
public void run() {
if(target != null){ //动态绑定(运行时类型 tiger)
target.run();
}
}
public ThreadProxy(Runnable target) {
this.target = target;
}
public void start(){
start0(); //这个方法是真正实现多线程的方法
}
private void 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;
}
}
}
}
线程使用案例-多线程执行
/*
* main 线程启动两个子线程
* */
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();
thread2.start();
}
}
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 == 10) {
break;
}
}
}
}
class T2 implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
//每隔1秒输出 “hi”,输出5次
System.out.println("hi" + (++count) + "~~~");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
break;
}
}
}
}
继承Thread vs 实现Runnable 的区别
实现Runnable接口方式更适合多个线程共享一个资源的情况,并且避免了单继承的限制。建议使用Runnable接口。
T3 t3=new T3("hello");
Thread thread1 = new Thread(t3);
Thread thread2 = new Thread(t3);
thread1.start();
thread2.start();
System.out.println("主线程完毕");
售票问题
售票系统-模拟编程三个售票窗口售票100,分别使用 继承Thread和Runnable方式实现,并分析问题?
问题–>会出现超卖
package ticket;
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();
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 {
public 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));
}
}
}
//使用Runnable
class SellTicket02 implements Runnable {
public 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));
}
}
}
线程常用方法
常用基本方法
- setName //设置线程名称,使之与参数name相同
- getName //返回该线程的名称
- start //使该线程开始执行;java 虚拟机底层调用该线程的 start0 方法
- run //调用线程对象run方法
- setPriority //更改线程优先级
- getPriority //设置线程优先级
- sleep //在指定的毫秒数内让当前正在执行的线程休眠 (暂停执行)
- interrupt //终端线程
注意事项和细节
- start 底层会创建新的线程,调用run ,run就是一个简单的方法调用,不会启动新线程
- 线程优先级的范围 MIN_PRIORITY=1、 NORM_PRIORITY=5 、 MAX_PRIORITY=10;
- interrupt ,中断线程 , 但没有真正的结束线程。所以一般用于 中断正在休眠的线程
- sleep :线程的静态方法,使当前线程休眠
package method;
public class ThreadMethod01 {
public static void main(String[] args) throws InterruptedException {
T t = new T();
t.setName("laohan");
t.setPriority(Thread.MIN_PRIORITY);
t.start(); //启动子线程
// 主线程打印5个hi,然后就中断 子线程的休眠
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " hi" + i);
}
System.out.println(t.getName() + " 线程的优先级=" + t.getPriority());
t.interrupt(); //当执行到这里,就会中断子线程的休眠
}
}
class T extends Thread {
@Override
public void run() {
while (true) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " 吃包子~~~" + i);
}
try {
System.out.println(Thread.currentThread().getName() + " 休眠中~~~");
Thread.sleep(20000);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 被 interrupt了~~~");
}
}
}
}
yield和join
- yield: 线程的礼让。让出cpu, 让其他线程执行,但礼让时间不确定,所以也不一定礼让成功【礼让是自己让 Thread.yield() 】
- join: 线程的插队。一旦插队成功,则肯定执行完插入线程的所有任务【插队是别人插 t2.join();】
package method;
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);
System.out.println("主线程(小弟)" + " 吃了" + i + "包子");
if (i == 5) {
System.out.println("主线程(小弟)让 子线程(老大)先吃");
//join() 插队,一定成功
//t2.join(); //让子线程插队,执行完毕
Thread.yield(); //礼让,不一定成功
System.out.println("子线程(老大)吃完了 主线程(小弟)再吃");
}
}
}
}
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 + "包子");
}
}
}
用户线程和守护线程
-
用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
-
守护线程: 一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束 【Thread.setDaemon(true): 设置为守护线程】
-
常见的守护线程:垃圾回收机制
package method; public class ThreadMethod03 { public static void main(String[] args) throws InterruptedException { MyDaemonThread myDaemonThread = new MyDaemonThread(); //如果希望当main线程结束后,子线程自动结束 //只需将子线程设为守护线程即可 myDaemonThread.setDaemon(true); myDaemonThread.start(); for (int i = 0; i < 10; i++) { System.out.println("辛苦工作...."); Thread.sleep(1000); } } } class MyDaemonThread extends Thread { public void run() { for (; ; ) {//无限循环 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("快乐聊天,哈哈哈哈~~~"); } } }
线程生命周期
JDK 中用Thread.State 枚举表示了线程的几种状态
首先线程的状态可以分为6
态或7
态,具体状态如下
6态
New
:新建状态Runnable
:可运行状态Terminated
:终止状态Waiting
:等待状态TimedWaiting
:超时等待状态Blocked
:阻塞状态
7态
New
:新建状态Ready
:就绪状态Running
:运行状态Terminated
:终止状态Waiting
:等待状态TimedWaiting
:超时等待状态Blocked
:阻塞状态
其实6态与7态差别不大,只不过7
态把Runnable
可运行状态,拆解成了Ready
就绪状态与Running
运行状态。
线程状态转换图
Sychronized
线程同步机制
- 在多线程编程中,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
- 也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成。
同步具体方法-Sychronized
1. 同步代码块
Sychronized (对象){ //得到对象的锁,才能操作同步代码
//需要被同步代码;
}
2. Sychronized 还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m(String name){
//需要被同步代码;
}
【售票问题】-同步机制:不会出现超卖
package ticket;
public class SellTicket {
public static void main(String[] args) {
//测试
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();
}
}
//使用Runnable,使用synchronized 实现线程同步
class SellTicket03 implements Runnable {
public static int ticketNum = 100; //让多个线程共享 ticketNum
private boolean loop=true;
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));
}
@Override
public void run() {
while (loop) {
sell(); //sell 是一个同步方法
}
}
}
互斥锁
- java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象
- 关键字synchronized 来与对象的互斥锁联系。当某个对象用 synchronized 修饰时,表明该对象在任一时刻只能由一个线程访问
- 同步的局限性: 导致程序的执行效率降低
- 同步方法(非静态的)的锁可以是this, 也可以是其他对象(要求是同一对象)
- 同步方法(静态的)的锁为当前类本身
public class SellTicket {
public static void main(String[] args) {
//测试
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();
}
}
//使用Runnable,使用synchronized 实现线程同步
class SellTicket03 implements Runnable {
public static int ticketNum = 100; //让多个线程共享 ticketNum
private boolean loop=true;
Object object =new Object();
//同步方法(静态的)的锁为当前类本身
//1. public synchronized static void m1(){} 锁加在 SellTicket03.class
//2. 如果在静态方法中,要实现一个同步代码块,互斥锁还是 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(){//同步方法,在同一时刻,只能有一个线程执行sell方法
synchronized(/*this*/ object){ //改为 nchronized(new Object()) 又出现超卖现象,锁失效,因为不是同一个锁对象
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));
}
}
@Override
public void run() {
while (loop) {
sell(); //sell 是一个同步方法
}
}
}
注意事项和细节
-
同步方法如果没有使用static修饰: 默认对象为this
-
如果同步方法使用static修饰,默认锁对象: 当前类.class
-
实现的步骤:
- 需要先分析上锁的代码
- 选择同步代码块或同步方法
- 要求多个线程的锁对象为同一个即可! 【多个线程争夺同一个锁对象】
死锁
- 多个线程都占用了对方的锁资源,但不肯相让,导致了死锁
- 在编程里一定要避免产生死锁
/*
* 模拟线程死锁
* */
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;
}
public void run() {
//下面这个业务逻辑的分析
//1. 如果flag 为 T, 线程A 就会先得到o1 对象锁, 然后尝试去获取 o2 对象锁
//2. 如果线程A 得不到 o2 对象锁,就会Blocked
//3. 如果flag 为 F, 线程B 就会先得到o2 对象锁, 然后尝试去获取 o1 对象锁
//4. 如果线程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");
}
}
}
}
}
释放锁
下面操作会释放锁
- 当前线程的同步方法、同步代码块执行结束
- 当前线程在同步方法、同步代码块遇到break、return
- 当前线程在同步方法、同步代码块中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步方法、同步代码块中执行了线程对象的wait() 方法,当前线程暂停,并释放锁
下面操作不会释放锁
-
线程执行同步方法或同步代码块时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,不会释放锁
-
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起
- 提示:应尽量避免使用suspend()和resumen()来控制线程,方法不推荐使用