线程的调度与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:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了
(一个线程对应一个对象,对象不共享,就没有数据安全问题)

浙公网安备 33010602011771号