多线程 02


1. 了解线程调度:

  1. 线程调度模型有:

(1)抢占式调度:那个线程优先级比较高,抢占到CPU时间片的概率就高/或者时间片多一些;java采用的就是抢占式调度;

(2)均分式调度: 平均分配CPU时间片;

  1. 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.重点:线程安全

关于多线程并发环境下,数据的安全问题。

  1. why?

因为开发中,项目都是运行在服务器当中的,而服务器已经将线程的定义,创建,启动等。关键就是:多线程并发环境下,数据的安全问题;

  1. When?多线程并发环境下,数据不安全?

比如多线程并发对同一个账户进行取款,若存在网络延迟,可能会多取出钱。

三个条件:

(1)多线程并发

(2) 有共享数据

(3)共享数据有修改行为

  1. How?解决此问题?

线程排队执行,不能并发;这种机制被称为:线程同步机制

线程同步就是线程排队,会牺牲一部分效率,但数据安全第一位

  1. 涉及到两种模型:

(1)异步编程模型:

线程t1和线程t2, 各自执行各自的,就是多线程并发,效率较高。

异步就是并发

(2)同步编程模型:

线程t1和线程t2, 在线程t1执行的时候,必须等待线程t2执行完,就是 线程之间存在等待关系,效率较低。

同步就是排队

  1. 编写程序实现多线程并发对同一个账户进行取款

    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
    */
    
    1. 模拟实现线程同步机制解决线程安全问题

      解决:其中一个线程可以在执行完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与它们不同步 , 因为它们不共享同一个账户对象。

      1. 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是线程安全的

相关