Java多线程专题笔记上
Java多线程专题笔记下
Java多线程专题目录
一、多线程的基本概念
1.基本概念
2.进程与线程的差别:
二、创建线程的两种方式
1.第一种方法:继承Thread超类,重写run方法
2.实现Runnable接口,实现run抽象类
3.匿名内部类创建线程
三、多线程机制
1.多线程
2.start方法
四、多线程的API方法
1.API方法
2.常用API方法实例:
3.设置线程优先级
4.休眠线程
5.用户线程和守护线程
五、线程的生命周期
六、线程同步 synchronized
1.多线程并发安全问题:
2、synchronized关键字
七、互斥锁
1.基本介绍:
2.使用情况:
3.注意事项:
4.互斥锁实例
六、死锁
1.线程死锁
2.释放锁
3.不会释放锁的情况
程序:是一段特定功能的静态代码的集合,是程序执行的蓝本。
进程:是程序的一次动态执行过程,代码的加载、执行,系统资源的调用。
线程:线程是进程内部的单一的顺序控制流程。
多线程:多个单一顺序执行的流程并发运行。造成"感官上同时运行"的效果。多线程是实现并发的一种有效手段。
并发:在同一时间,多个任务交替执行。
并行:在同一时间,多个任务同时执行。
做个简单的比喻:进程=火车,线程=车厢
线程在进程下行进(单纯的车厢无法运行)
一个进程可以包含多个线程(一辆火车可以有多个车厢),一个线程可以有多个协程(一个车厢有很多个乘客)
不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”。
class ThreadDem01 {public static void main(String[] args) {//创建两个线程Thread t1 = new MyThread1();Thread t2 = new MyThread2();//启动线程t1.start();t2.start();}
}//第一个线程
class MyThread1 extends Thread{public void run(){for (int i=0;i<1000;i++){System.out.println("hello姐~");}}
}
//第二个线程
class MyThread2 extends Thread{public void run(){for (int i=0;i<1000;i++){System.out.println("来了~老弟!");}}
}
第一种创建线程的
优点:结构简单,利于匿名内部类形式创建。
缺点:
1:由于java是单继承的,这会导致继承了Thread就无法再继承其他类去复用方法
2:定义线程的同时重写了run方法,这等于将线程的任务定义在了这个线程中导致线程只能干这件事。重(chong)用性很低。
public class ThreadDemo02 {public static void main(String[] args) {//创建线程任务MyRunnable01 myRunnable01 = new MyRunnable01();MyRunnable02 myRunnable02 = new MyRunnable02();//创建线程并指派任务Thread t1 = new Thread(myRunnable01);Thread t2 = new Thread(myRunnable02);//启动线程t1.start();t2.start();}
}class MyRunnable01 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("大冤种##########");}}
}class MyRunnable02 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("小牛马********");}}
}
public class lamdaThreadDemo {public static void main(String[] args) {//Thread匿名内部类的写法Thread thread1 = new Thread(){@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("小牛马######");}}};//Runnable的匿名内部类写法Thread thread2 = new Thread(()->{for (int i = 0; i < 1000; i++) {System.out.println("大冤种******");}});//启动线程thread1.start();thread2.start();}
}
当运行程序时,会启动一个进程,进程会启动一个main线程(主线程),当main进程启动一个子线程Thread-0(new一个进程对象),main线程不会阻塞,会继续执行,这时的main线程和Thread-0线程是交替执行的。 当主线程结束,子线程不会结束。当主线程和子线程都结束,才会结束进程。在终端使用JConsole可以监视线程执行情况
为什么启动线程,调用的是start()方法,而不是run方法? 直接调用run方法,就相当于执行普通方法,没有开启线程,当run方法执行完,才会接着去执行main方法。 如果调用start方法就会开启一个线程,run方法和main方法会交替执行。 只有调用start方法才会真正的启动一个线程。start方法源码解读 调用start方法,start方法会调用执行start0方法,由start0去调用run方法,JVM调用start0后,start0方法将run变成可执行状态,然后由CPU去统一去调度。 真正实现多线程的时start0方法。
start() //启动线程 currentThread(); //获取运行该方法的线程 getName(); //获取线程名 getId(); //获取线程id getPriority() //获取线程优先级 isAlive() //判断该线程是否还活着 isDaemon //判断该线程是否是守护线程 isInterrupted() //判断该线程是否被中断setDaemon() //设置守护线程 setName() //设置线程名字 setPriority(Thread.MAX_PRIORITY); //设置线程优先级 sleep() //线程休眠 interrupt(); //唤醒线程休眠 wait() //将线程设置成等待状态 notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程,进入等待运行状态;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。yield():线程的礼让,让出CPU,使当前线程由执行状态,变成为就绪状态,在下一个线程执行时候,此线程有可能被执行,也有可能没有被执行。 join():线程的插队,让CPU优先执行该线程,一旦插队成功,会先执行完该线程的所有任务。
public class ThreadInfoDemo {public static void main(String[] args) {//currentThread(); 获取主线程Thread mainThread = Thread.currentThread();//getName(); 获取线程名String name = Name();System.out.println("线程名:"+name);//设置线程名字mainThread.setName("小牛马");System.out.println("线程名:"+ Name());//getId(); 获取线程idlong id = Id();System.out.println("线程id:"+id);//getPriority() 获取线程优先级int priority = Priority();System.out.println("线程优先级:"+priority);//isAlive() 判断该线程是否还活着boolean life = mainThread.isAlive();System.out.println("线程是否还活着:"+life);//isDaemon 判断该线程是否是守护线程boolean guard = mainThread.isDaemon();System.out.println("线程是否是守护线程:"+guard);//isInterrupted() 判断该线程是否被中断boolean interrupt = mainThread.isInterrupted();System.out.println("线程是否被中断:"+interrupt);}
}
线程start后会纳入到线程调度器中统一管理,线程只能被动的被分配时间片并发运行,而无法主动索取时间片.线程调度器尽可能均匀的将时间片分配给每个线程. 线程有10个优先级,使用整数1-10表示:- 1为最小优先级,10为最高优先级.5为默认值- 调整线程的优先级可以最大程度的干涉获取时间片的几率.优先级越高的线程获取时间片的次数越多,反之则越少.
public class PriorityDemo {public static void main(String[] args) {//第一个线程:Thread max = new Thread(){@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println("熊大");}}};//第二个线程Thread min = new Thread(){@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println("熊小小");}}};//第三个线程Thread norm = new Thread(){@Overridepublic void run() {for (int i = 0; i < 10000; i++) {System.out.println("小牛马");}}};//设置线程优先级:优先级1-10级,默认5级max.setPriority(Thread.MAX_PRIORITY);min.setPriority(Thread.MIN_PRIORITY);//启动线程max.start();norm.start();min.start();}
}
1)线程阻塞:sleep 线程提供了一个静态方法: static void sleep(long ms)- 使运行该方法的线程进入阻塞状态指定的毫秒,超时后线程会自动回到RUNNABLE状态等待再次获取时间片并发运行.2)sleep方法处理异常:InterruptedException. 当一个线程调用sleep方法处于睡眠阻塞的过程中,该线程的interrupt()方法被调用时,sleep方法会抛出该异常从而打断睡眠阻塞.
public class SleepDemo02 {public static void main(String[] args) {//蓝线程:Thread blue = new Thread(){@Overridepublic void run(){System.out.println("线程休眠");try {Thread.sleep(9999999);} catch (InterruptedException e) {System.out.println("线程休眠唤醒");}System.out.println("线程休眠结束");}};//红线程:Thread red = new Thread(){public void run(){System.out.println("输出数据");for(int i=0;i<5;i++){System.out.println("red: "+i);try {Thread.sleep(1000);} catch (InterruptedException e) {}}System.out.println("输出结束!");//中断lin的睡眠阻塞blue.interrupt();}};//启动线程blue.start();red.start();}
}
线程的插队,让CPU优先执行该线程,一旦插队成功,会先执行完该线程的所有任务。
/*** 龟兔赛跑:* 先让兔子跑50步,然后乌龟在一直跑完,乌龟结束后,兔子再继续跑*/
public class JoinDemo {public static void main(String[] args) {//乌龟线程Thread t1 = new Thread(){@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println("乌龟在跑!!!"+i);}}};int i = 1;while (i <= 100){System.out.println("兔子在跑!!!"+i);i++;if (i==50){t1.start();try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}}}}
}
1)用户线程和守护线程 1.用户线程:也叫工作线程,当线程的任务执行完或通知方式结束。 2.守护线程:一般为工作线程服务的,当所有的用户线程结束,守护线程自动结束。2)守护线程也称为:后台线程- 守护线程是通过普通线程调用setDaemon(boolean on)方法设置而来的,因此创建上与普通线程无异.- 守护线程的结束时机上有一点与普通线程不同,即:进程的结束.- 进程结束:当一个进程中的所有普通线程都结束时,进程就会结束,此时会杀掉所有正在运行的守护线程.- 常见的守护线程:垃圾回收机制 进程结束:当Java进程中所有的普通线程都结束时,进程就会结束,此时它会杀死所有还在运行的守护线程。
//将线程设置成守护线程,设置守护线程必须在线程启动前进行
线程.setDaemon(true);
3)设置守护线程
public class DaemonThreadDemo {public static void main(String[] args) {//第一线程Thread t1 = new Thread(){@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("小牛马");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};//第二线程Thread t2 = new Thread(){@Overridepublic void run() {while (true){System.out.println("大冤种");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}};t1.start();//必须在启动线程前设置守护线程t2.setDaemon(true);t2.start();}
}
一个线程从创建到消亡的生命周期大致分为五个状态:新建状态、可运行状态、运行状态、阻塞状态、消亡状态。新建状态(new) 使用new创建一个新线程对象,该线程就处于新建状态 Thread myThread = newMyThread();等待运行状态(就绪状态 Runnable) 当线程创建后,调用了start()方法便进入该状态,会产生所需的系统资源,并在就绪队列等待执行 myThread.start();执行状态(Running) 当可运行状态的线程被CPU调用并获得系统资源,便进入执行状态,这时线程贵按顺序执行run()中每一条语句阻塞状态(Blocked) 当线程发生了以下几种情况就进入阻塞状态 1.等待阻塞:运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程调用notify()或notifyAll()方法才能被唤醒, 2.同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入“锁池”中。 3.其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。解除阻塞状态 1)如果线程处于休眠状态,当设定的休眠时间过后,便可进入运行状态,或者强制唤醒interrupt(); 2)如果线程正在等待某个条件,需要调用该条件所在对象的notify()或notifyALL()方法,便可进入运行状态 3)如果线程因为I/O阻塞,当I/O操作结束后,阻塞线程便可回到运行状态消亡状态(Dead) 当线程退出,不在执行,就是消亡状态。 线程的消亡状态分为自然消亡和强制消亡。 自然消亡是线程从run()正常退出。 强制消亡是线程被强制终止,如调用Thread的destroy()或stop()命令终止程序。
当多个线程并发操作同一临界资源,由于线程切换时机不确定,导致操作临界资源的顺序出现混乱严重时可能导致系统瘫痪. 临界资源:操作该资源的全过程同时只能被单个线程完成. yield(): 出让CPU,进行线程切换,让当前运行线程回到等待运行状态。
1)线程同步的概念:
线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作。
2)synchronized有两种使用方式
- 在方法上修饰,此时该方法变为一个同步方法
- 同步块,可以更准确的锁定需要排队的代码片段
同步监视器对象的选取: 对于同步的成员方法而言,同步监视器对象不可指定,只能是this 对于同步的静态方法而言,同步监视器对象也不可指定,只能是类对象 对于同步块而言,需要自行指定同步监视器对象,选取原则:1.必须是引用类型2.多个需要同步执行该同步块的线程看到的该对象必须是同一个
第一种使用方式:线程同步方法
public class SyncDemo01 {public static void main(String[] args) {//让两个线程一起抢豆子Table table = new Table();//第一线程Thread t1 = new Thread(){@Overridepublic void run() {while(true){//抢一个豆子int bean = Beans();//出让CPU,让其他线程运行
// Thread.yield();System.out.println(getName()+":"+bean);}}};//第二线程Thread t2 = new Thread(){@Overridepublic void run() {while(true){//抢一个豆子int bean = Beans();//出让CPU,让其他线程运行Thread.yield();System.out.println(getName()+":"+bean);}}};//启动多线程t1.start();t2.start();}
}class Table{private int beans = 20; //桌上有20个豆子//线程同步方法:获取豆子public synchronized int getBeans(){//当beans等于0,抛出异常,没有豆子了if (beans == 0){throw new RuntimeException("没有豆子啦!");}Thread.yield();return beans--;}
}
第二种使用方式:线程同步块
同步监视器对象即上锁的对象,要想保证同步块中的代码被多个线程同步运行,则要求多个线程看到的同步监视器对象是同一个。
public class SyncDemo02 {public static void main(String[] args) {Shop shop = new Shop();//第一线程Thread t1 = new Thread(){@Overridepublic void run() {shop.buy();}};//第二线程Thread t2 = new Thread(){@Overridepublic void run() {shop.buy();}};//启动线程t1.start();t2.start();}
}class Shop{public void buy(){Thread t = Thread.currentThread();try {System.out.Name()+":正在挑衣服...");Thread.sleep(3000);//使用同步块,需要指定上锁对象synchronized (this){ //同一时间,只能有一个线程操作该代码块System.out.Name()+":正在试衣服...");Thread.sleep(3000);}System.out.Name()+":结账离开");} catch (InterruptedException e) {e.printStackTrace();}}
}
当在静态方法上使用synchronized后,该方法是一个同步方法.由于静态方法所属类,所以一定具有同步效果.
静态方法使用的同步监视器对象为当前类的类对象(Class的实例).
注:类对象会在后期反射知识点介绍.
public class SyncDemo03 {public static void main(String[] args) {//第一线程Thread t1 = new Thread(){@Overridepublic void run() {Boo.dosome01();}};//第二线程Thread t2 = new Thread(){@Overridepublic void run() {Boo.dosome01();}};//启动线程t1.start();t2.start();}
}class Boo{public synchronized static void dosome01(){//获取该方法的线程Thread t = Thread.currentThread();try {System.out.Name()+"正在执行dosome方法...");Thread.sleep(5000);System.out.Name()+"执行dosome方法结束");} catch (InterruptedException e) {e.printStackTrace();}}}
}
静态方法中使用同步块时,指定的锁对象通常也是当前类的类对象
public class SyncDemo03 {public static void main(String[] args) {//第一线程Thread t1 = new Thread(){@Overridepublic void run() {Boo.dosome02();}};//第二线程Thread t2 = new Thread(){@Overridepublic void run() {Boo.dosome02();}};//启动线程t1.start();t2.start();}
}
class Boo{public static void dosome02(){//静态方法中使用同步块时,指定同步监视器对象通常还是用当前类的类对象//获取方式为:类名.classsynchronized (Boo.class) {Thread t = Thread.currentThread();try {System.out.Name() + ":正在执行dosome方法...");Thread.sleep(5000);System.out.Name() + ":执行dosome方法完毕!");} catch (InterruptedException e) {e.printStackTrace();}}}
}
Java语言中,引入了对象互斥锁的概念,来保证共亨数据操作的完整性。
每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized 来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问
当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的.
使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的.
1.同步方法如果没有使用static修饰,默认锁对象为this。
2.如果方法使用static修饰,默认锁对象为:当前类.class。
3.一般同步代码块的范围比同步方法的范围小,所以效率高一些。
4.多线程的锁对象必须为同一个对象。
/*** 互斥锁* 当使用synchronized锁定多个不同的代码片段,并且指定的同步监视器对象相同时,* 这些代码片段之间就是互斥的,即:多个线程不能同时访问这些方法。*/
public class SyncDemo04 {//主程序public static void main(String[] args) {Foo foo = new Foo();//第一个线程:执行A方法Thread t1 = new Thread(){@Overridepublic void run() {hodA();}};//第二个线程:执行A方法Thread t2 = new Thread(){@Overridepublic void run() {hodB();}};//启动线程t1.start();t2.start();}
}
//创建两个方法,并添加线程锁,指定相同的同步监视器对象
class Foo{//A方法public synchronized void methodA(){Thread t = Thread.currentThread();try {System.out.Name() + ":正在执行A方法...");Thread.sleep(5000);System.out.Name() + ":A方法执行完毕!");} catch (InterruptedException e) {e.printStackTrace();}}//B方法public synchronized void methodB(){Thread t = Thread.currentThread();try {System.out.Name() + ":正在执行B方法...");Thread.sleep(5000);System.out.Name() + ":B方法执行完毕!");} catch (InterruptedException e) {e.printStackTrace();}}
}
所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
线程死锁产生的四个条件
(1)互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
(2)不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放(只能是主动释放)。
(3)请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
(4)循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中下一个进程所请求。
1.当前线程的同步方法或同步代码块执行结束
2.当前线程在同步方法或同步代码块中遇到break、return
3.当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
4.当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前程序暂停,并释放锁。
1.线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前程序的执行,不会释放锁。
2.线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,不会释放锁。
我见青山如见君,君若见山亦如是。
本文发布于:2024-01-30 02:35:15,感谢您对本站的认可!
本文链接:https://www.4u4v.net/it/170655331718625.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |