W
e
l
c
o
m
e
: )

线程的调度与Synchromized

java中有关线程调度的方法

实例方法:

修改线程优先级

void setPriority(int newPriority)

获取线程优先级

int getPriority()

合并线程

void join()

最低优先级1
默认优先级5
最高优先级10

静态方法:

暂停当前线程,执行其他线程
static void yield() 让位
yield()方法不是阻塞方法,让当前线程让位,让给其他线程使用
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”
注意:回到就绪状态之后可能会再次抢到

什么时候数据在并发的情况下不安全?

三个条件
1:多线程并发
2:共享数据
3:数据有修改的行为
满足以上三个条件之后,就会存在线程安全问题
怎么解决线程安全问题?
线程排队执行,不能并发
这种机制被称为线程同步机制
专业术语:线程同步,实际就是线程不能同步了,线程必须排队执行
线程排队会牺牲一部分效率,但是数据安全最重要

关于同步与异步

异步编程模型
线程t1和t2各自执行各的,t1不管t2,t2不管t1
谁也不用等谁,这种编程模型叫异步编程模型,其实就是多线程并发(效率较高)

同步编程模型
线程t1和t2,在线程t1执行的时候,必须等待t2线程执行结束或
在线程t2执行的时候,必须等待t1线程执行结束,效率较低,线程排队执行

异步->并发
同步->排队

Synchronized

格式

synchronized(){
}

synchronized后小括号中传的这个数据是相当关键的,这个数据必须是多线程共享的数据,才能达到多线程排队
在java中,任何对象都有一把锁,100个对象100个锁
执行原理:
1:假设t1和t2线程并发,开始执行以下代码的时候,肯定一个先一个后
2:假设t1先执行了,遇到了synchronized,这时自动找后面共享对象的对象锁,找到后并占有这把锁
然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的,知道同步代码块结束,这把锁才会释放
3:假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面共享对象的这把锁,结果锁被t1占有
t2只能在同步代码块外等待t1的结束
直到t1把同步代码块执行结束,t1才会归还这把锁,t2占有这把锁之后,进入同步代码块执行程序
这样就达到了线程排队执行

需要注意:
这个共享对象一定是需要排队执行的这些线程对象所共享的

package Synchronized;
public class UseSync {
    public static void main(String[] args) {
        Account act = new Account("123", 10000);
        Thread t1 = new AccountThread(act);
        Thread t2 = new AccountThread(act);
        t1.start();
        t2.start();
    }
}

/*
银行账户
不适用线程同步机制,多线程对同一个账户取款,出现线程安全问题
*/
class Account {
    private String card;
    private double balance;

    public Account(String card, double balance) {
        this.card = card;
        this.balance = balance;
    }

    public Account() {
    }

    public String getCard() {
        return card;
    }

    public void setCard(String card) {
        this.card = card;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    //    取款的方法
    public void withdraw(double money) {
        //如果让线程异步执行,同一时间执行取款方法会出现线程安全问题
        //线程同步机制的语法使:
        /*
        synchronized(需要排队的共享对象){
        线程同步代码块
        }
       
        */
        synchronized (this) {
            double before = this.getBalance();
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }


    }
}

class AccountThread extends Thread {
    private Account act;

    public AccountThread(Account act) {
        this.act = act;
    }

    @Override
    public void run() {
        double money = 5000;
        act.withdraw(money);
        System.out.println(Thread.currentThread().getName() + "取款成功,余额为" + act.getBalance());
    }
}

java 三大变量

局部变量:栈
实例变量:堆
静态变量:方法区

以上变量中,只有局部变量不会出现线程安全问题
因为局部变量不会被共享,一个线程一个栈

堆和方法区在多线程中是共享的

线程安全问题的的三大条件

1:多线程并发
2:有共享数据
3:共享数据有修改的行为

synchronized使用在实例方法上

缺点:
synchronized使用在实例方法上,一定锁的使this,不能是其他对象
不灵活

2:整个实例方法都系要同步,可能无故扩大同步的范围导致程序的执行效率变低

优点:
1:节俭了

如果使用局部变量的话:
建议使用:StringBuilder
因为局部变量没有线程安全问题
选择StringBuilder因为StringBuffer效率比较低

Arraylist是非线程安全的
Vector是线程安全的
Hashmap HashSet是非线程安全的
Hashtable是线程安全的

synchronized的三种用法

第一种:同步代码块
灵活

synchronized(线程共享对象){
同步代码块
 }

第二种:在实例方法中使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体
第三种:在静态方法上使用synchronized
表示找类锁
类锁永远只用一把

关于死锁

当一个对象1执行过程中锁住了一个对象,而另外一个对象2执行过程中锁住了对象一
的需要使用的对象,而对象2也需要执行对象1锁住的那个对象。
于是程序僵持在那里,不出现异常,也不会出现错误
死锁例子:

package ThreadSafe;

import AboutThread.SetPriority;

//死锁的情况
//为了避免死锁的情况,尽量避免synchronized嵌套
public class ThreadSafe {
    public static void main(String[] args) {
        Object o1=new Object();
        Object o2=new Object();
//        t1和t2共享o1,o2
        Thread t1=new MyThread1(o1,o2);
        Thread t2=new MyThread1(o1,o2);
        t1.start();
        t2.start();
    }
}

class MyThread1 extends Thread {
    Object o1;
    Object o2;
    public MyThread1(Object o1,Object o2){
        this.o1=o1;
        this.o2=o2;
    }
    @Override
    public void run() {
       synchronized (o1){
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           synchronized (o2){

           }
       }
    }
}

class MyThread2 extends Thread {
    Object o1;
    Object o2;
    public MyThread2(Object o1,Object o2){
        this.o1=o1;
        this.o2=o2;
    }
    @Override
    public void run() {
        synchronized (o2){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (o1){

            }
        }
    }
}

如何解决线程安全问题

1:尽量避免线程同步(synchronized)
synchronized会让程序的执行效率变低,用户体验不好,系统的用户吞吐量降低,用户体验差
在不得已的情况下在使用线程同步机制
2:尽量使用局部变量代替实例变量和静态变量
3:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了
(一个线程对应一个对象,对象不共享,就没有数据安全问题)

posted @ 2021-03-12 23:20  rmxob  阅读(115)  评论(0)    收藏  举报