一、线程的优先级(priority)
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
- 线程的优先级用数字表示,范围1~10
- Thread.Min_Priority = 1; 最小等级
- Thread.Max_Priority = 10; 最大等级
- Thread.Norm_Priority = 5; 默认等级
- 使用以下方式改变或获取优先级
- getPriority 获取当前等级
- setPriority 改变当前等级(int 1~10)
package com.Thread.state;
//测试线程的优先级 Priority
public class TestPriority {
public static void main(String[] args) {
//主线程的优先级
System.out.println(Thread.currentThread().getName()+"--->等级为:"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1 = new Thread(myPriority,"t1"); //使用线程对象,将mypriority传入,定义线程名字为t1
Thread t2 = new Thread(myPriority,"t2");
Thread t3 = new Thread(myPriority,"t3");
Thread t4 = new Thread(myPriority,"t4");
Thread t5 = new Thread(myPriority,"t5");
//先设置优先级,在启动
t1.setPriority(2); //设置优先级2
t1.start();
t2.setPriority(10);
t2.start();
t3.setPriority(2);
t3.start();
t4.setPriority(9);
t4.start();
t5.setPriority(6);
t5.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
//获取当前线程的名字+当前线程的等级!
System.out.println(Thread.currentThread().getName()+"--->等级为:"+Thread.currentThread().getPriority());
}
}
二、线程守护(Daemon)
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等待。。
package com.Thread.state;
//测试守护线程
//上帝守护着你
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true); //默认是false表示用户线程,正常的线程都是用户线程
thread.start(); //上帝守护线程
new Thread(you).start(); //用户线程启动
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝守护着你");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("你一生都开心的活着!");
}
System.out.println("goodbye!离开世界!");
}
}
结果图
三、线程同步机制
- 现实生活中,我们会遇到“同一个资源,多个人想使用”的问题,比如:食堂排队打饭,每个人都想吃饭,最天然的解决办法就是,排队,一个个来。
- 处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用!
- 由于同一个进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排队锁,独占资源,其他线程必须等待!使用后释放锁即可,存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起;
- 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题;
- 如果一个优先级高的线程等待一个优先级低的线程释放锁,会导致优先级倒置,引起性能问题!
四、三大不安全的案例
案例一:购买火车票(模拟真实抢票)
package com.Thread.syn;
//模拟不安全的买车票2
public class UnsafeBuyTicket2 {
public static void main(String[] args) {
BuyTicket2 buyTicket2 = new BuyTicket2();
new Thread(buyTicket2,"学生").start();
new Thread(buyTicket2,"老师").start();
new Thread(buyTicket2,"黄牛党").start();
}
}
//买票的类
class BuyTicket2 implements Runnable{
private int Ticketnums = 10; //票数量为10张
boolean flag = true;
//买票的方法
public void buy(){
//如果还有票,则flag为真,继续抢票
while (flag){
//如果没票了,则退出循环!
if (Ticketnums<=0){
flag = false;
return;
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+Ticketnums--+"张票!余票为:"+Ticketnums);
}
}
@Override
public void run() {
try {
//模拟延时抢票
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
buy(); //调用抢票的方法!
}
}
结果图
案例二:银行取钱(模拟真实场景)
package com.Thread.syn;
//模拟银行取钱 不安全的取钱
public class UnsafeBank2 {
public static void main(String[] args) {
//账户
Account2 account2 = new Account2(1000,"结婚的礼金");
//银行
Drawing2 wo = new Drawing2(account2,510,"我"); //取钱的人
Drawing2 duixiang = new Drawing2(account2,500,"对象"); //取钱的人
//开启线程
wo.start();
duixiang.start();
}
}
//账户
class Account2{
//账户中的余额
int money;
//卡号
String cate;
public Account2(int money, String cate) {
this.money = money;
this.cate = cate;
}
}
class Drawing2 extends Thread{
//调用账户
Account2 account2;
//取了多少钱
int drawingMoney;
//手里有多少钱
int money;
//有参构造器
public Drawing2(Account2 account2,int drawingMoney,String name){
super(name);
this.drawingMoney = drawingMoney;
this.account2 = account2;
}
@Override
public void run() {
//判断账户是否有钱
if (account2.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+",您取钱失败,账户余额不足哦!");
return;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内的余额
account2.money = account2.money - drawingMoney;
//手上的余额
money = drawingMoney+money;
//打印当前卡内的余额
System.out.println("您当前卡内的余额为:"+account2.money);
//打印当前您手上的余额
System.out.println("您好,尊敬的"+this.getName()+",您的手上的余额为:"+money);
//判断账户是否有钱
}
}
结果图
案例三:ArrayList(集合存放数据)
package com.Thread.syn;
import java.util.ArrayList;
import java.util.List;
public class UnsafeList2 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
//lambda表达式
list.add(Thread.currentThread().getName());
}).start();
}
//集合的长度
System.out.println("集合的长度为:"+list.size());
}
}
结果图
总结:从可以上面的代码以及运行结果能看出,线程都是不安全的,案例一(购买火车票):多人抢购到了同一张票,导致后面余票成了-1;案例二(模拟银行取钱):两个人同时取钱时,都看到了1000的余额,但是当同时去取钱的时候,超出了卡内余额,仍然可以取出来;案例三(不安全的集合):在使用ArrayList集合去添加元素时,很明显是不安全的,因为在同时添加进去的时候,并没有添加完成,便终止了添加,也就是说有多个集合存放到了同一个数组下。解决方法看下一个练习题
五、同步块
- 同步块:synchronized(obj){}
- Obj称之为同步监视器
- Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
- 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class{反射中讲解}
- 同步监视器的执行过程
- 第一个线程访问,锁定同步监视器,执行其中代码。
- 第二个线程访问,发现同步监视器被锁定,无法访问
- 第一个线程访问完毕,解锁同步监视器!
- 第二个线程访问,发现同步监视器没有锁,然后锁定并访问!
案例一解决方法:
private synchronized void buy(){
//如果没票了,则退出循环!
if (Ticketnums<=0){
flag = false;
return;
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+Ticketnums--+"张票!余票为:"+Ticketnums);
}
结果图
总结:在买票的方法上加一个synchronized即可
案例二解决方法:
@Override
public void run() {
//判断账户是否有钱
synchronized (account2){
if (account2.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+",您取钱失败,账户余额不足哦!");
return;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡内的余额
account2.money = account2.money - drawingMoney;
//手上的余额
money = drawingMoney+money;
//打印当前卡内的余额
System.out.println("您当前卡内的余额为:"+account2.money);
//打印当前您手上的余额
System.out.println("您好,尊敬的"+this.getName()+",您的手上的余额为:"+money);
//判断账户是否有钱
}
}
结果图:
总结:在重写run方法内,加上synchronized对象锁即可,注意:锁的对象需要是操作的对象,也就是账户,并不是银行!
案例三解决方法:
for (int i = 0; i < 1000; i++) {
new Thread(()->{
synchronized (list){
//lambda表达式
list.add(Thread.currentThread().getName());
}
}).start();
}
结果图:
总结:在匿名内部类中将list对象锁上!使得循环的时候依次存放!
六、死锁
- 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的庆幸,某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题!
package com.Thread.Lock;
//死锁:多个线程互相抱着对方需要的资源,然后行程僵持
public class DeadLock {
public static void main(String[] args) {
Makeup makeup1 = new Makeup(0,"小红");
Makeup makeup2 = new Makeup(1,"小玲");
makeup1.start();
makeup2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
//化妆
class Makeup extends Thread{
//需要的资源只有一份 ,使用static保证只有一份!
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice; //选择使用什么化妆品
String girlName; //使用化妆品的人
//构造方法
public Makeup(int choice,String girlName){
this.choice = choice;
this.girlName = girlName;
}
@Override
public void run() {
//化妆
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//互相持有对方的锁,就是需要拿到对方的资源
private void makeup() throws InterruptedException {
if (choice == 0) { //如果选择的是0
synchronized (lipstick) { //获得口红的锁
System.out.println(this.girlName + "获得口红的锁");
Thread.sleep(3000);
synchronized (mirror) { //一秒钟后想获得镜子的
System.out.println(this.girlName + "获得镜子的锁");
}
}
} else {
synchronized (mirror) { //获得镜子的锁
System.out.println(this.girlName + "获得镜子的锁");
Thread.sleep(3000);
synchronized (lipstick) { //一秒钟后想获得口红的
System.out.println(this.girlName + "获得镜子的锁");
}
}
}
}
}
结果图:
总结:如上所示,本应小红获得口红之后,下一步便是获得镜子,但是线程增加了同步机制,也就是synchronized,另一个线程的小玲在获取镜子,所以互不相让,导致死锁的发生,都关闭了门,无法拿到对应的锁,导致死锁!
死锁的避免方法
- 产生死锁的四个必要条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在未使用完前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系!
注:文章仅做个人学习日记,不做学习建议,学习来源:狂神说