1. 了解线程调度:
- 线程调度模型有:
(1)抢占式调度:那个线程优先级比较高,抢占到CPU时间片的概率就高/或者时间片多一些;java采用的就是抢占式调度;
(2)均分式调度: 平均分配CPU时间片;
-
java中提供的和线程调度有关的方法
(1)void setPriority( int newPriority) 设置线程的优先级
(2)int getPriority() 获取线程的优先级
默认优先级是5
最高优先级是10 最低优先级是1
(3) 静态方法:static void yield( ) 暂停正在执行的线程对象,并执行其他线程;
不是阻塞方法,让当前线程让位;从“运行状态”回到“就绪状态”
(4)void join() 合并线程
线程对象t.join() 当前线程阻塞,t线程执行,直到t线程结束。当前线程才可以继续
package 多线程;
public class T3 {
public static void main(String[] args) {
System.out.println("最高优先级"+Thread.MAX_PRIORITY);
System.out.println("最低优先级"+Thread.MIN_PRIORITY);
System.out.println("默认优先级"+Thread.NORM_PRIORITY);
//获取当前线程对象,及其优先级
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"线程的默认优先级是:"+thread.getPriority());
Thread thread2 = new Thread(new MyThread3());
thread2.setName("t");
thread2.start();
}
}
class MyThread3 implements Runnable{
@Override
public void run() {
//获取线程优先级
System.out.println(Thread.currentThread().getName()+"线程的默认优先级是:"+Thread.currentThread().getPriority());
}
}
/*最高优先级10
最低优先级1
默认优先级5
main线程的默认优先级是:5
t线程的默认优先级是:5
*/
package 多线程;
//线程优先级高的,只是抢到的CPU时间片相对多一些
public class T3 {
public static void main(String[] args) {
// 设置主线程的优先级设为1
Thread.currentThread().setPriority(1);
// t线程的优先级设为10
Thread thread2 = new Thread(new MyThread3());
thread2.setName("t");
thread2.setPriority(10);
thread2.start();
// 主线程的代码
for (int i = 0; i < 6; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
class MyThread3 implements Runnable {
@Override
public void run() {
// 与主线程并发执行的分支线程的代码
for (int i = 0; i < 6; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
/*main--->0
main--->1
t--->0
main--->2
t--->1
main--->3
t--->2
t--->3
main--->4
t--->4
main--->5
t--->5*/
2.重点:线程安全
关于多线程并发环境下,数据的安全问题。
- why?
因为开发中,项目都是运行在服务器当中的,而服务器已经将线程的定义,创建,启动等。关键就是:多线程并发环境下,数据的安全问题;
- When?多线程并发环境下,数据不安全?
比如多线程并发对同一个账户进行取款,若存在网络延迟,可能会多取出钱。
三个条件:
(1)多线程并发
(2) 有共享数据
(3)共享数据有修改行为
- How?解决此问题?
线程排队执行,不能并发;这种机制被称为:线程同步机制
线程同步就是线程排队,会牺牲一部分效率,但数据安全第一位
- 涉及到两种模型:
(1)异步编程模型:
线程t1和线程t2, 各自执行各自的,就是多线程并发,效率较高。
异步就是并发
(2)同步编程模型:
线程t1和线程t2, 在线程t1执行的时候,必须等待线程t2执行完,就是 线程之间存在等待关系,效率较低。
同步就是排队
-
编写程序实现多线程并发对同一个账户进行取款
package 多线程; //银行账户 public class Account { private String no;// 账号 private double balance;// 余额 // 无参构造 public Account() { } // 有参构造 public Account(String no, double balance) { this.no = no; this.balance = balance; } // set和get方法 public String getNo() { return no; } public void setNo(String no) { this.no = no; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } // 取款的方法 public void withdraw(double money) { // 取款之前的余额 double before = this.getBalance(); // 取款之后的余额 double after = before - money; // 更新余额 this.setBalance(after); } } //两个线程实现对账户取款 class AccountThread extends Thread { // 两个线程必须共享同一个账户对象 , 通过构造方法实现传递过来账户对象 private Account act; public AccountThread(Account act) { this.act = act; } // run方法的执行表示取款操作 public void run() { // 假设取款5000 double money = 5000; // 取款 act.withdraw(money); System.out.println("线程" + Thread.currentThread().getName() + "对" + act.getNo() + "取款成功,余额为" + act.getBalance()); } } /* * 多线程并发对同一个账户进行取款: act.withdraw(money); t1 和 t2 并发执行这个方法, t1 和 * t2是两个栈,两个栈操作堆中同一个对象 若t1先取款,只要此代码还未执行 this.setBalance(after);//更新余额 * 而此时t2也进来取款,执行到double after = before - money; 仍然是5000 * 此时就出问题了。最后两个都取了5000,然后余额还剩5000 但也有可能运行时,不出问题,t1执行完“更新余额”,t2才开始执行withdraw方法 */
package 多线程; public class T2 { public static void main(String[] args) { //创建账户对象 Account account = new Account("001",10000); //创建两个线程 AccountThread t1 = new AccountThread(account); AccountThread t2 = new AccountThread(account); //给两个线程取名 t1.setName("t1"); t2.setName("t2"); //启动线程取款 t1.start(); t2.start(); } } /*运行多次的结果可能性: * 1. * 线程t2对001取款成功,余额为0.0 线程t1对001取款成功,余额为5000.0 2. 线程t1对001取款成功,余额为0.0 线程t2对001取款成功,余额为0.0 3. 线程t1对001取款成功,余额为5000.0 线程t2对001取款成功,余额为5000.0 */
-
模拟实现线程同步机制解决线程安全问题
解决:其中一个线程可以在执行完withdraw方法体中的代码后,另外的线程才可以执行此方法,,就是排队执行
**线程同步机制的语法:synchronized () {线程同步代码块}其中synchronized后面的小括号中传递的数据必须是多线程共享的数据,才能达到多线程排队,小括号中写想让哪些线程同步,就写它们所共享的对象 **
package 多线程; //银行账户 public class Account { private String no;// 账号 private double balance;// 余额 // 无参构造 public Account() { } // 有参构造 public Account(String no, double balance) { this.no = no; this.balance = balance; } // set和get方法 public String getNo() { return no; } public void setNo(String no) { this.no = no; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } // 取款的方法 public void withdraw(double money) { //此处的代码必须是线程排队执行 //synchronized后面的小括号中传递的数据必须是多线程共享的数据 //小括号中写什么?? /*那你想让那些线程同步,假设共有4个线程,若只希望t1,t2排队,t3,t4不需要排队, * 那就在其中写t1,t2共享的对象,而对于t3,t4不是共享的*/ //对于当前代码,t1和t2是共享“账户对象”,而this就是“账户对象” synchronized (this) { // 取款之前的余额 double before = this.getBalance(); // 取款之后的余额 double after = before - money; // 更新余额 this.setBalance(after); } } } //两个线程实现对账户取款 class AccountThread extends Thread { // 两个线程必须共享同一个账户对象 , 通过构造方法实现传递过来账户对象 private Account act; public AccountThread(Account act) { this.act = act; } // run方法的执行表示取款操作 public void run() { // 假设取款5000 double money = 5000; // 取款 act.withdraw(money); System.out.println("线程" + Thread.currentThread().getName() + "对" + act.getNo() + "取款成功,余额为" + act.getBalance()); } }
package 多线程; public class T2 { public static void main(String[] args) { //创建账户对象 Account account = new Account("001",10000); //创建两个线程 AccountThread t1 = new AccountThread(account); AccountThread t2 = new AccountThread(account); //给两个线程取名 t1.setName("t1"); t2.setName("t2"); //启动线程取款 t1.start(); t2.start(); } } /*线程t2对001取款成功,余额为0.0 线程t1对001取款成功,余额为5000.0*/
原理:
在java 中,任何一个对象都有“一把锁”, 这把锁就是标记
//对于当前代码,t1和t2是共享“账户对象”,而this就是“账户对象” synchronized (this) { // 取款之前的余额 double before = this.getBalance(); // 取款之后的余额 double after = before - money; // 更新余额 this.setBalance(after); }
以上代码的执行原理:
线程t1和t2并发,肯定有先后执行顺序:
若t1先执行,遇到synchronized,会自动地找”后面共享对象“的对象锁,找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是占有这把锁的,直到同步代码块执行结束,这把锁才会释放。
假设t1已经占有了这把锁,此时t2也遇到了synchronized,也会去占有”后面共享对象“的对象锁,结果这把锁被t1占有,t2只能在同步代码块的外面等待t1的结束,直到t1将同步代码块执行结束了,t1会归还这把锁,此时,t2会占有这把锁之后,进入同步代码块执行。
以上,就达到了线程排队执行。
这个共享对象一定是你需要排队执行的线程所共享的;
处于运行状态的线程遇到synchronized,会去找共享对象的对象锁,线程进入锁池找共享对象的对象锁的时候,会释放之前占有的CPU时间片,若没找到,就在锁池中等待,找到了就会进入就绪状态继续抢夺CPU时间片。
若想要使所有的线程都同步,就是都排队,还可以在小括号中写一个字符串常量,因为它是保存在常量池中的,唯独一份,一把锁。
若此时,
Account account2 = new Account("001",10000); AccountThread t3 = new AccountThread(account);
那此时,小括号中的this, 还是指t1,t2同步,t3与它们不同步 , 因为它们不共享同一个账户对象。
- java中有三大变量:
实例变量:在堆中
静态变量:在方法区中
局部变量:在栈中
以上变量中,局部变量永远不会有线程安全问题,因为它不共享(一个线程一个栈)
在java虚拟机中,堆和方法区只有一个,栈有多个
-
// 取款的方法
//当synchronized出现在实例方法上一定锁的一定是this,所以这种方式不灵活,
//表示整个方法体都需要同步,可能无故扩大同步的范围,导致程序的执行效率较低
//优点:代码写得少,简洁
//如果共享的对象是this,需要同步的代码块是整个方法体,就用此方式
public synchronized void withdraw(double money) {
// 取款之前的余额
double before = this.getBalance();
// 取款之后的余额
double after = before - money;
// 更新余额
this.setBalance(after);
}
(1)StringBuilder是非线程安全的,StringBuffer是线程安全的
若使用局部变量,建议使用StringBuilder,因为局部变量不存在线程安全问题
(2)ArrayList是是非线程安全的,Vector是是线程安全的
(3)HashMap HashSet是非线程安全的,Hashtable是线程安全的