java初学笔记11-多线程

一.程序、进程、线程的概念

程序(program) 是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码, 静态对象。

进程(process) 是程序的一次执行过程, 或是正在运行的一个程序。
动态过程: 有它自身的产生、存在和消亡的过程。

  • 如: 运行中的QQ
  • 程序是静态的, 进程是动态的

线程(thread) 进程可进一步细化为线程, 是一个程序内部的一条执行路径

  • 若一个程序可同一时间执行多个线程, 就是支持多线程的

二.使用多线程的优点

  1. 提高应用程序的响应。对图形化界面更有意义, 增强用户体验。
  2. 提高计算机系统CPU的利用率
  3. 改善程序结构。将既长又复杂的进程分为多个线程, 独立运行, 利于理解和修改。

多线程应用场景:

  • 程序需要同时执行两个或多个任务。
  • 程序想要实现一些需要等待的任务时, 如用户输入、文件读写操作、网络操作、搜索等。
  • 需要一些后台运行的程序时。

三.Java中多线程的创建和使用

Java语言的JVM允许程序运行多个线程, 它通过java.lang.Thread类来实现

1.Thread类

Thread类的特性:

  • 每个线程都是通过某个特定的Thread对象的 run() 方法来完成操作的, 经常把 run() 方法的主体称为线程体
  • 通过该Thread对象的 start() 方法来调用这个线程

Thread类构造方法:

  • Thread(): 创建新的Thread对象
  • Thread(String threadname): 创建线程并制定线程实例名
  • Thread(Runnable target): 指定创建线程的目标对象, 它实现了Runnable接口中的run方法
  • Thread(Runnable target, String name): 同上, 创建新的Thread对象

2.创建线程的两种方式:

1.继承Thread类

  1. 定义子类继承Thread类
  2. 子类中重写Thread类中的 run() 方法
  3. 创建Thread子类对象, 即创建了线程对象。
  4. 调用线程对象 start() 方法: 启动线程, 调用 run() 方法

多线程: 继承Thread类的子类, 重写 run() 方法

/**
 * 继承Thread类的方式进行多线程
 * @author chenyao
 */
public class TestThread extends Thread {
	String name = "默认";
	public TestThread() {//构造方法
		System.out.println("默认多线程");
	}
	public TestThread(String name) {
		this.name = name;
		System.out.println("这是多线程"+name);
	}
	@Override
	public void run() {
		System.out.println("线程运行的主逻辑");
		for(int i = 0; i < 10; i++) {
			System.out.println("这是"+name+"多线程的逻辑代码"+i);
		}
		super.run();
	}
}

主线程: 创建多线程对象, 调用start()方法启动多线程

public class Test {
	public static void main(String[] args) {
		Thread t0 = new TestThread();
		t0.start();//启动线程
		Thread t1 = new TestThread("线程2");
		t1.start();//启动多个线程
		try {
			t0.join();//多线程插入运行,只有多线程运行结束才会执行t0.join();后面的代码
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		//主线程在t0.start();之后的代码与t0的run()方法代码并行运行 
		//主线程与多线程运行按各自顺序,没有先后关系,这就是多线程的异步性
		System.out.println("-------");
		System.out.println("-------");
		System.out.println("-------");
		System.out.println("-------");
	}
}

2.实现Runnable 接口

  1. 定义子类, 实现Runnable接口
  2. 子类中实现Runnable接口中的 run() 方法
  3. 通过Thread类含参构造器创建线程对象
  4. 将Runnable接口的子类对象作为实际参数传递类Thread类的构造方法中。(Thread类的构造方法接收Runnable接口的实现类对象)
  5. 调用Thread类的 start() 方法: 开启线程, 调用Runnable子类接口的 run() 方法

多线程: 创建子类实现Runnable接口, 实现 run() 方法

/**
 * 通过实现Runnable接口进行多线程
 * @author chenyao
 */
public class TestRunnable implements Runnable {
	public TestRunnable() {
		System.out.println("创建多线程"+Thread.currentThread().getName());
//		//打印的线程为main,调用构造器的过程仍属于主线程
	}
	
	@Override
	public void run() {
		//run()方法里面为线程体,运行的代码属于多线程
		System.out.println(Thread.currentThread().getName());//打印的线程名称为线程3
		for(int i = 0; i < 10; i++) {
			System.out.println("这是Runnable多线程的逻辑代码"+i);
		}
	}
}

主线程: 创建多线程对象, 调用 srart() 方法, 启动多线程

public class Test {
	public static void main(String[] args) {
		Test.testRunnable();
	}
	
	public static void testRunnable() {
//	Thread t2 = new Thread(new TestRunnable());
//	t2.start();

	Thread t3 = new Thread(new TestRunnable(),"线程3");
//	System.out.println(t3.getName());//
//	System.out.println(Thread.currentThread().getName());//当先运行的线程名称,main
	//Thread类的构造方法Thread(String threadname)
	t3.start();
	}
}

3.继承方式和实现方式的联系与区别

public class Thread extends Object implements Runnable

区别:

  • 继承Thread : 线程代码存放在Thread子类的run方法中。
  • 实现Runnable : 线程袋面存放在Runnable接口子类的run方法中。

一般使用实现接口的方式来实现多线程
实现接口方法的好处:

  • 避免了单继承的局限性(只能继承Thread一个父类, 实现方法不影响其继承其他父类)
  • 多个线程可以共享同一个接口实现类的对象, 非常适合多个相同线程来处理同一份资源(子类中的同一个属性可以被多个线程调用)

(一个实现类可以创建多个实现类对象, 每个对象可以创建多个多线程)

/**
 * 通过实现Runnable接口进行多线程
 * @author chenyao
 */
public class TestRunnable implements Runnable {
	int count = 0;//该属性可被同个TestRunnable对象下的多个线程共享
	@Override
	public void run() {
		//run()方法里面为线程体,运行的代码属于多线程
		System.out.println(Thread.currentThread().getName());//打印的线程名称为线程3
		for(int i = 0; i < 10; i++) {
			count++;
			System.out.println("这是Runnable多线程的逻辑代码"+i+",count-"+count);
		}
	}
}
public class Test {
	public static void main(String[] args) {
		Test.testRunnable();
	}
	
	public static void testRunnable() {
		TestRunnable trun = new TestRunnable();
		//基于同一个实现类对象trun创建多个多线程
		Thread t3 = new Thread(trun,"线程3");
		Thread t4 = new Thread(trun,"线程4");
		t3.start();
		t4.start();
	}
}

4.Thread类的有关方法

  • void start() : 启动线程, 并执行对象的 run() 方法
  • run() : 线程被调用时执行的操作
  • String getName() : 返回线程的名称
  • void setName() : 设置该线程的名称
  • static currentThread() : 返回当前线程
  • 线程优先级
  • getPriority() : 返回线程优先级
  • setPriority(int newPriority) : 设置线程的优先级
  • 线程的调度
  • static void yield() : 线程让步
  • join() : 线程优先执行
  • static void sleep(long mills) : 线程休眠 (指定时间: 毫秒)
  • stop(): 强制线程生命期结束
  • boolean isAlive() : 返回boolean值, 判断线程是否活动

5.线程的优先级、线程调度

1.线程的优先级:

Java中线程的优先级用数字1-10表示, 数字越大优先级越高,默认为5
优先级高则表示该线程有较大的概率优先被执行, 但不一定先执行

  • MAX_PRIORITY(10)
  • MIN_PRIORITY(1);
  • NORM_PRIORITY(5);

涉及的方法:

  • getPriority() : 返回线程优先级
  • setPriority(int newPriority) : 设置线程的优先级
  • 线程创建时继承父线程的优先级
public class TestThreadMethod {
	public static void main(String[] args) {
		TestRun run0 = new TestRun();
		TestRun run1 = new TestRun();
		
		Thread t0 = new Thread(run0);
		Thread t1 = new Thread(run1);
	
		System.out.println("t0默认线程名称: "+t0.getName());//如果创建线程时没有指定名称,默认名称Thread-0
		System.out.println("t1默认线程名称: "+t1.getName());//通过getName()方法获取线程名称,线程默认名称Thread-1
//		t0.setName("线程t0");//设置线程名称
		
		/**
		 * 线程的优先级, 就是哪个线程有[较大的概率]被执行
		 * 优先级用数字1-10表示,数字越大,优先级越高
		 * 默认优先级是5,线程创建时会继承父线程的优先级
		 */
		
		System.out.println("t0的默认优先级是:"+t0.getPriority());//获取线程的优先级
		System.out.println("t1的默认优先级是:"+t1.getPriority());
		t0.setPriority(10);//设置线程的优先级
		t1.setPriority(1);
		//优先级高,表示有较大的概率优先被执行,但不一定会先执行
		
		t0.start();
		t1.start();
		System.out.println("====================");
		System.out.println("====================");
		System.out.println("====================");
	}
}
class TestRun implements Runnable{
	int count = 0;
	public TestRun() {
		System.out.println("创建多线程"+Thread.currentThread().getName());
	}
	
	@Override
	public void run() {
		for(int i = 0; i < 3; i++) {
			count++;
			System.out.println("这是多线程"+Thread.currentThread().getName()+"的逻辑代码:"+i+",count-"+count);
		}
	}
}

2.线程调度

涉及的方法:
static void yield() : 线程让步

  • 暂停当前正在执行的线程, 把执行机会让给优先级相同或更高的线程
  • 若队列中没有同优先级的线程, 忽略此方法

join() : 线程插队(必须先启动线程)

  • 当某个程序执行流中调用其他线程的 join() 方法时, 调用线程将被阻塞(如main线程的后续的代码被阻塞), 直到 join() 方法加入的join线程执行完为止
  • 低优先级的线程也可以获得优先执行

static void sleep(long mills) : 线程休眠 (指定时间: 毫秒)

  • 令当前活动线程在指定时间段内放弃对CPU控制, 使其他线程有机会被执行, 时间到之后重新排队
  • 抛出InterruptedException (线程中断) 异常

stop(): 强制线程生命期结束(不安全, 该方法已过时)

boolean isAlive() : 返回boolean值, 判断线程是否活着

interrupt(): 线程中断

boolean isInterrupt() : 返回boolean值, 判断线程是否被中断

public class TestThreadMethod {
	public static void main(String[] args) {
		TestRun run0 = new TestRun();
		TestRun run1 = new TestRun();
		
		Thread t0 = new Thread(run0);
		Thread t1 = new Thread(run1);
	
		System.out.println("t0默认线程名称: "+t0.getName());//如果创建线程时没有指定名称,默认名称Thread-0
		System.out.println("t1默认线程名称: "+t1.getName());//通过getName()方法获取线程名称,线程默认名称Thread-1
//		t0.setName("线程t0");//设置线程名称
		
		/**
		 * 线程的优先级, 就是哪个线程有[较大的概率]被执行
		 * 优先级用数字1-10表示,数字越大,优先级越高
		 * 默认优先级是5,线程创建时会继承父线程的优先级
		 */
		
//		System.out.println("t0的默认优先级是:"+t0.getPriority());//获取线程的优先级
//		System.out.println("t1的默认优先级是:"+t1.getPriority());
//		t0.setPriority(10);//设置线程的优先级
//		t1.setPriority(1);
//		//优先级高,表示有较大的概率优先被执行,但不一定会先执行
		
		t0.start();
		t1.start();
		System.out.println("====================");
		System.out.println("t1线程是否活着"+t1.isAlive());
		
		t1.stop();//强制终止线程
//		t1.interrupt();//线程中断
//		System.out.println("t1线程是否被中断"+t1.isInterrupted());
		
		try {
			t0.join();//t0线程插队
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		//线程插队后,调用线程(main线程)后续代码被阻塞,直至join线程执行完成
		System.out.println("====================");
		
		try {
			System.out.println("====================1");
			Thread.sleep(1000);//当前线程休眠1000ms,即设置时间间隔
			System.out.println("====================2");		
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("t1线程是否活着"+t1.isAlive());//为什么还是存活??
		System.out.println("t0线程是否活着"+t0.isAlive());//t0线程运行结束,所以返回值为false
		Thread main = Thread.currentThread();//获取主线程对象
//		main.stop();//可以对主线程进行操作
		System.out.println("主线程是否活着"+main.isAlive());
	}
}
class TestRun implements Runnable{
	int count = 0;
	public TestRun() {
		System.out.println("创建多线程"+Thread.currentThread().getName());
	}
	
	@Override
	public void run() {
		for(int i = 0; i < 100; i++) {
			count++;
			if(Thread.currentThread().getName() == "Thread-0") {
				Thread.yield();//线程让步
			}
//			if(i == 2) {
//				Thread.currentThread().stop();
//			}
			System.out.println("这是多线程"+Thread.currentThread().getName()+"的逻辑代码:"+i+",count-"+count);
		}
	}
}

四.线程的生命周期

JDK用Thread.State枚举表示了线程的几种状态
要想实现多线程, 必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程, 在它的一个完整生命周期中通常要经历如下的五种状态:

  • 新建(new Thread) : 当一个Thread类或其子类的对象被声明并创建时, 新生的线程对象处于新建状态
  • 就绪(runnable) : 处于新建状态的线程被 start() 后, 将进入线程队列等待CPU时间片, 此时它已具备了运行的条件
  • 运行(running) : 当就绪的线程被调度并获得处理器资源时, 便进入运行状态, run() 方法定义了线程的操作和功能
  • 阻塞(blocked) : 在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态
  • 死亡(dead) : 线程完成了它的全部工作或线程被提前强制性的终止
    分为自然终止、异常终止
    线程的生命周期

五.线程的同步

1.线程同步解决的问题

问题的提出:

  • 多个线程执行的不确定性引起执行结果的不稳定。
  • 多个线程对账本的共享, 会造成操作的不完整性, 会破坏数据。
    解决方法:
  • 让一个线程都执行完毕, 在执行过程中, 其他线程不可以参与执行。
public class Test {
	public static void main(String[] args) {
		//定义银行账户对象
		Account zhangsan = new Account(3000);
		
		//多线程对象,
		//通过一个多线程对象创建两个线程,或者各创建一个线程对象,有何区别
		//如果需要传入的参数不同,则需要两个对象
		
		User u_weixin = new User(zhangsan,2000);
		User u_zhifubao = new User(zhangsan,2000);
		
		Thread weixin = new Thread(u_weixin,"微信");
		Thread zhifubao = new Thread(u_zhifubao,"支付宝");
		
		weixin.start();
		zhifubao.start();
	}
}
class Account{
	public  int money;
	public Account(int money) {
		this.money = money;
	}
	/**
	 * 多线程调用这个方法,存在问题,线程共享资源时,一个线程在执行方法(调用资源)没有完毕时,另一个线程又开始执行这个方法
	 * 由于多线程的异步性,对共享资源的操作就会存在问题
	 * 
	 * 解决方法:让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
	 * @param drawMoney
	 */
	public void drawing(int drawMoney) {
		String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
		System.out.println(way+"操作,账户内原有金额"+money+"元");
		if(drawMoney>0) {
			if(drawMoney<=money) {
				System.out.println(way+"操作,取款"+drawMoney+"元");
				money -= drawMoney;
				System.out.println(way+"操作,取款后账户余额:"+money+"元");
			}else {
				System.out.println(way+"操作,账户余额不足,帐号余额:"+money+"元");
			}
		}else {
			System.out.println("取款金额必须为正数");
		}
	}
}
class User implements Runnable{//定义多线程实现类
	Account account;
	int money;
	public User(Account account,int money){
		this.account = account;
		this.money = money;
	}
	@Override
	public void run() {
		account.drawing(money);
	}
}

2.同步机制

Java对于多线程的安全问题提供了专业的解决方式: 同步机制
Synchronized的使用方法:

  1. 给方法加上同步锁。
    synchronized可以放在方法声明中, 表示整个方法为同步方法。
    如:
    public synchronized void show(String name){

    }
  2. 给代码块加上同步锁。
    synchronized(对象){
    //需要被同步的代码
    }

注意:

 * 在普通方法上加同步锁synchronized,锁的是调用方法的实例对象this。
 * this对象所有加了synchronize的一般方法共用一把锁,所以一次只能访问一个方法
 * 对象锁不住不同的对象
 * 
 * 在静态方法上加同步锁synchronized, 锁的是整个类对象, 类似于(X.class)。
 * 该类中所有加了synchronized的静态方法共用一把锁,一次只能调用一个
 * 对该类所有对象有效, 因为调用的都是类方法, 操作全局变量。
 *

特殊情况: 类锁和对象锁是两个不一样的锁,控制着不同的区域,两者互不干扰。
在线程获得对象锁的同时,也可以获得该实例的类锁,即同时获得两个锁,这是允许的。

1.普通方法加同步锁:

public class Test {
	public static void main(String[] args) {
		//定义银行账户对象
		Account zhangsan = new Account(3000);
		
		//多线程对象,
		//通过一个多线程对象创建两个线程,或者各创建一个线程对象,有何区别
		//如果需要传入的参数不同,则需要两个对象
		
		User u_weixin = new User(zhangsan,2000);
		User u_zhifubao = new User(zhangsan,2000);
		
		//public drawing()方法前加synchronized同步锁锁的是Account对象zhangsan
		//同一个Account对象,调用这个对象中所有加了synchronize的方法,只能一次调用一个
//		Account lisi = new Account(3000);//public drawing()方法前加synchronized,对不同的Account对象无效
//		User u_zhifubao = new User(lisi,2000);
		
		Thread weixin = new Thread(u_weixin,"微信");
		Thread zhifubao = new Thread(u_zhifubao,"支付宝");
		
		weixin.start();
		zhifubao.start();
	}
}
class Account{
	public int money = 3000;
	public Account(int money) {
		this.money = money;
	}
	
	/**
	 * 多线程调用这个方法,存在问题,线程共享资源时,一个线程在执行方法(调用资源)没有完毕时,另一个线程又开始执行这个方法
	 * 由于多线程的异步性,对共享资源的操作就会存在问题
	 * 
	 * 解决方法:让一个线程都执行完,在执行过程中,其他线程不可以参与执行。
	 * 通过synchronized同步锁来完成
	 * 1.可以直接在方法前加上synchronized关键字
	 * 在普通方法上加同步锁synchronized,锁的是调用方法的实例对象this。锁不住不同的对象
	 * 同时,this对象中所有加了synchronize的方法只能一次调用一个
	 * 
	 * 在静态方法上加同步锁synchronized,锁的是整个类对象,类似于(X.class)。对所有对象有效
	 * 该类中所有加了synchronized的静态方法,一次只能调用一个
	 * @param drawMoney
	 */
	public synchronized void drawing(int drawMoney) {
		String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
		System.out.println(way+"操作,账户内原有金额"+money+"元");
		if(drawMoney>0) {
			if(drawMoney<=money) {
				System.out.println(way+"操作,取款"+drawMoney+"元");
				money -= drawMoney;
				System.out.println(way+"操作,取款后账户余额:"+money+"元");
			}else {
				System.out.println(way+"操作,账户余额不足,帐号余额:"+money+"元");
			}
		}else {
			System.out.println("取款金额必须为正数");
		}
	}
}
class User implements Runnable{//定义多线程实现类
	Account account;
	int money;
	public User(Account account,int money){
		this.account = account;
		this.money = money;
	}
	@Override
	public void run() {
		account.drawing(money);
	}
}

2.静态方法同步锁:

public class Test1 {
	public static void main(String[] args) {
		//定义不同的Account类对象
		Account1 zhangsan = new Account1();
		Account1 lisi = new Account1();
		//多线程对象,
		//通过一个多线程对象创建两个线程,或者各创建一个线程对象,有何区别
		//如果需要传入的参数不同,则需要两个对象
		
		User1 u_weixin = new User1(zhangsan,2000);
		User1 u_zhifubao = new User1(lisi,2000);
		
		Thread weixin = new Thread(u_weixin,"微信");
		Thread zhifubao = new Thread(u_zhifubao,"支付宝");
		
		weixin.start();
		zhifubao.start();
	}
}
class Account1{
	public static int stcmoney = 3000;//全局变量

	/**
	 * 在静态方法上加同步锁synchronized,锁的是整个类对象,类似于(X.class)。对所有对象有效
	 * 该类中所有加了synchronized的静态方法,一次只能调用一个
	 * @param drawMoney
	 */
	public static synchronized void drawing(int drawMoney) {
		String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
		System.out.println(way+"操作,账户内原有金额"+stcmoney+"元");
		if(drawMoney>0) {
			if(drawMoney<=stcmoney) {
				System.out.println(way+"操作,取款"+drawMoney+"元");
				stcmoney -= drawMoney;
				System.out.println(way+"操作,取款后账户余额:"+stcmoney+"元");
			}else {
				System.out.println(way+"操作,账户余额不足,帐号余额:"+stcmoney+"元");
			}
		}else {
			System.out.println("取款金额必须为正数");
		}
	}
	//静态方法加同步锁synchronized
	public static synchronized void drawing1(int drawMoney) {
		String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
		System.out.println(way+"操作,账户内原有金额"+stcmoney+"元");
		if(drawMoney>0) {
			if(drawMoney<=stcmoney) {
				System.out.println(way+"操作,取款"+drawMoney+"元");
				stcmoney -= drawMoney;
				System.out.println(way+"操作,取款后账户余额:"+stcmoney+"元");
			}else {
				System.out.println(way+"操作,账户余额不足,帐号余额:"+stcmoney+"元");
			}
		}else {
			System.out.println("取款金额必须为正数");
		}
	}
}
class User1 implements Runnable{//定义多线程实现类
	Account1 account;
	int money;
	public User1(Account1 account,int money){
		this.account = account;
		this.money = money;
	}
	@Override
	public void run() {
		if(Thread.currentThread().getName()=="微信") {
			Account1.drawing(money);//调用不同的静态方法,同步锁仍然生效
		}else {
			Account1.drawing1(money);//调用不同的静态方法,同步锁仍然生效
		}
	}
}

3.代码块加同步锁(根据当前对象)

使用synchronized (this) {} 代码块, 表示当前对象的代码块被加了同步锁

  • 用this代码块是表示当前的对象, 有一个调用则当前对象即被锁定, 传入不同的对象共用一把锁
  • 其他的方法中的synchronized (this){}代码块,共用一个同步锁
  • 类似于类锁, 因为只针对代码块之内的代码, 所以比类锁效率更高
public class Test2 {
	public static void main(String[] args) {
		Account2 zhangsan = new Account2();
		
		//多线程对象,
		//通过一个多线程对象创建两个线程,或者各创建一个线程对象,有何区别
		//如果需要传入的参数不同,则需要两个对象
		User2 u_weixin = new User2(zhangsan,2000);
		User2 u_zhifubao = new User2(zhangsan,2000);
		
		Thread weixin = new Thread(u_weixin,"微信");
		Thread zhifubao = new Thread(u_zhifubao,"支付宝");
		
		weixin.start();
		zhifubao.start();
	}
}
class Account2{
	public static int stcmoney = 3000;//全局变量

	/**
	 * 代码块上加同步锁synchronized
	 * @param drawMoney
	 */
	public void drawing(int drawMoney) {
		
		synchronized (this) {
//			用this代码块是表示当前的对象, 有一个调用则当前对象即被锁定, 传入不同的对象共用一把锁
//			其他的方法中的synchronized (this){}代码块,共用一个同步锁
//			类似于类锁, 因为只针对代码块之内的代码, 所以比类锁效率更高		
			
			String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
			System.out.println(way+"操作,账户内原有金额"+stcmoney+"元");
			if(drawMoney>0) {
				if(drawMoney<=stcmoney) {
					System.out.println(way+"操作,取款"+drawMoney+"元");
					stcmoney -= drawMoney;
					System.out.println(way+"操作,取款后账户余额:"+stcmoney+"元");
				}else {
					System.out.println(way+"操作,账户余额不足,帐号余额:"+stcmoney+"元");
				}
			}else {
				System.out.println("取款金额必须为正数");
			}
		}
	}
}
class User2 implements Runnable{//定义多线程实现类
	Account2 account;
	int money;
	public User2(Account2 account,int money){
		this.account = account;
		this.money = money;
	}
	@Override
	public void run() {
		account.drawing(money);
	}
}

4.代码块加同步锁(根据传入的不同对象)

synchronized修饰代码块, 想要根据不同对象有不同的锁, 则需要创建 代码块所在的类 的不同的对象, 并且使用 synchronized (obj) {} 以传入不同的对象

  • 用obj代码块是表示传入的对象, 每个对象分配一把锁, 不同的对象间互不干扰
  • 锁定的对象中, 其他的方法中的synchronized (this){}代码块,共用一个同步锁
  • 类似于对象锁, 因为只针对代码块之内的代码, 所以比对象锁效率更高
public class Test3 {
	public static void main(String[] args) {
		//创建不同的Account对象
		Account3 zhangsan = new Account3();
		Account3 lisi = new Account3();
		
		//多线程对象,
		//通过一个多线程对象创建两个线程,或者各创建一个线程对象,有何区别
		//如果需要传入的参数不同,则需要两个对象
		User3 u_weixin = new User3(zhangsan,2000);
		User3 u_zhifubao = new User3(lisi,2000);
		
		Thread weixin = new Thread(u_weixin,"微信");
		Thread zhifubao = new Thread(u_zhifubao,"支付宝");
		
		weixin.start();
		zhifubao.start();
	}
}
class Account3{
	public static int stcmoney = 3000;//全局变量
	
	/**
	 * synchronized修饰代码块,想要根据不同对象有不同的锁
	 * @param drawMoney
	 */
	public void drawing1(int drawMoney,Account3 a) {
		
		synchronized (a) {
//			用obj代码块是表示传入的对象, 每个对象分配一把锁, 不同的对象间互不干扰
//			锁定的对象中, 其他的方法中的synchronized (this){}代码块,共用一个同步锁
//			类似于对象锁, 因为只针对代码块之内的代码, 所以比对象锁效率更高

			String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
			System.out.println(way+"操作,账户内原有金额"+stcmoney+"元");
			if(drawMoney>0) {
				if(drawMoney<=stcmoney) {
					System.out.println(way+"操作,取款"+drawMoney+"元");
					stcmoney -= drawMoney;
					System.out.println(way+"操作,取款后账户余额:"+stcmoney+"元");
				}else {
					System.out.println(way+"操作,账户余额不足,帐号余额:"+stcmoney+"元");
				}
			}else {
				System.out.println("取款金额必须为正数");
			}
		}
	}
}
class User3 implements Runnable{//定义多线程实现类
	Account3 account;
	int money;
	public User3(Account3 account,int money){
		this.account = account;
		this.money = money;
	}
	@Override
	public void run() {
		account.drawing1(money,account);
	}
}

5.小结

普通方法加同步锁:
锁的是当前对象, 当前对象的所有加了同步锁的方法共用一个同步锁
静态方法加同步锁:
该类所有对象共用一个同步锁
代码块加同步锁——synchronized (this) {} 形式:
所有执行代码块的当前对象, 执行的所有synchronized (this) {} 代码块共用一个同步锁
代码块加同步锁——synchronized (obj) {} 形式:
根据不同的对象有不同的锁

6.线程的死锁问题

死锁的概念:

  • 不同的线程分别占用对方需要的同步资源不放弃, 都在等待对方放弃自己需要的同步资源, 就形成了线程的死锁

解决方法:

  • 专门的算法、原则, 比如加锁顺序一致
  • 尽量减少同步资源的定义, 尽量避免锁未释放的场景

六.线程的通信

wait() 与 notify() 和 notifyAll()

使线程从运行进入阻塞——以及从阻塞进入就绪状态

  • wait() : 令当前线程挂起并放弃CPU、同步资源, 使别的线程可访问并修改共享资源, 而当前线程排队等候 再次对资源的访问
  • notify() : 唤醒正在排队等待同步资源的线程中 优先级最高者, 结束等待
  • notifyAll() : 唤醒正在排队等待资源的所有线程结束等待

java.lang.Object提供的这三个方法只有在synchronized方法或synchronized代码块中才能使用, 否则会报java.lang.IllegalMonitorStateException(监控器非法)异常

1.wait()方法

在当前线程中调用方法: 对象名.wait()
使当前线程进入等待(某对象)状态, 直到另一线程对该对象发出 notify (或者notifyAll) 为止。
调用方法的必要条件: 当前线程必须具有对该对象的监控权 (加锁)
调用此方法后, 当前线程将释放对象监控权, 然后进入等待
在当前线程被notify后, 要重新获得监控权, 然后从断点处继续代码的执行

2.notify()/notiifyAll()方法

在当前线程中调用方法: 对象名.notify()
功能: 唤醒等待该对象监控权的一个线程
调用方法的必要条件: 当前线程必须具有对该对象的监控权 (加锁)

public class Test2 {
	public static void main(String[] args) {
		Account2 zhangsan = new Account2();
		
		//多线程对象,
		//通过一个多线程对象创建两个线程,或者各创建一个线程对象,有何区别
		//如果需要传入的参数不同,则需要两个对象
		User2 u_weixin = new User2(zhangsan,2000);
		User2 u_zhifubao = new User2(zhangsan,2000);
		
		Thread weixin = new Thread(u_weixin,"微信");
		Thread zhifubao = new Thread(u_zhifubao,"支付宝");
		
		weixin.start();
		zhifubao.start();
	}
}
class Account2{
	public static int stcmoney = 3000;//全局变量
	boolean flag = true;
	/**
	 * 代码块上加同步锁synchronized
	 * @param drawMoney
	 */
	public void drawing(int drawMoney) {

		synchronized (this) {//表示当前对象的代码块被加了同步锁
		//用this代码块是表示当前的对象, 不同的对象调用的是同一个同步锁
		//如果在其他的方法中也有synchronized (this){}代码块,使用的也都是同一个同步锁
			
		/**
		 * 线程的通信,wait() 与 notify() 和 notifyAll()
		 * 需求: 如果是微信操作,先不执行,等支付宝操作,支付宝操作完微信再继续
		 * 实现: 首先判断,如果当前线程名称是"微信",则wait(),并放弃当前占有的同步锁,进入阻塞状态。
		 * "支付宝"线程会抢占同步锁,并重新执行同步锁的代码,当前线程名称为"支付宝"
		 * 再次判断,当前线程名称为"支付宝",notify()唤醒优先级最高的进程,此时"微信"被唤醒执行同步锁中的后续代码
		 */
			String way = Thread.currentThread().getName();//多线程名称,为微信、支付宝各创建一个线程
			if(flag && way.equals("微信")) {//flag确保判断只进行一次,避免支付宝线程完毕后微信线程运行受阻
				try {
					System.out.println(way+"进入等待");
					this.wait();//当前的线程进入等待的阻塞状态
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			flag = false;
			System.out.println(way+"操作,账户内原有金额"+stcmoney+"元");
			if(drawMoney>0) {
				if(drawMoney<=stcmoney) {
					System.out.println(way+"操作,取款"+drawMoney+"元");
					stcmoney -= drawMoney;
					System.out.println(way+"操作,取款后账户余额:"+stcmoney+"元");
				}else {
					System.out.println(way+"操作,账户余额不足,帐号余额:"+stcmoney+"元");
				}
			}else {
				System.out.println("取款金额必须为正数");
			}
			
			if(way.equals("支付宝")) {
				System.out.println("唤醒线程"+way);
//				this.notify();//唤醒当前优先级最高的线程,进入就绪状态
				this.notifyAll();//唤醒当前所有线程,进入就绪状态
			}
		}
	}
}
class User2 implements Runnable{//定义多线程实现类
	Account2 account;
	int money;
	public User2(Account2 account,int money){
		this.account = account;
		this.money = money;
	}
	@Override
	public void run() {
		account.drawing(money);
	}
}

七.经典例题:生产者/消费者问题

生产者(Producer)将产品交给店员(Clerk), 而消费者(Customer)从电源处取走产品, 店员一次只能持有固定数量的产品(比如:20), 店员会叫生产者听一下, 如果店中有空位放产品了再通知生产者继续生产; 如果店中没有产品, 店员会告诉消费者等一下, 如果店中有产品了再通知消费者来取走产品。
这里可能出现两个问题:

  • 生产者比消费者快时, 消费者会漏掉一些数据没有取到
  • 消费者比生产者快时,消费者会取到相同的数据
/**
 * 生产者与消费者问题
 * @author chenyao
 *
 */
public class Test4 {
	public static void main(String[] args) {
		Clerk c = new Clerk();
		//消费时不生产,生产时不消费
		//生产者
		new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized(c) {
					while(true) {//无限循环,代表无限的生产次数
						if(c.productNum == 0) {//产品数为0,开始生产
							System.out.println("产品数为0,开始生产");
							while(c.productNum < 4) {//产品数<4,一直生产
								c.productNum ++;//生产
								System.out.println("库存:"+c.productNum);
							}
							System.out.println("产品数为"+c.productNum+",结束生产");
							
							c.notify();//唤醒消费者
						}else {
							try {
								c.wait();//让生产者线程等待
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
					}
				}
			}
			},"生产者").start();
		
		//消费者
		new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized(c) {
					while(true) {//无限循环,代表无限的消费次数
						if(c.productNum == 4) {//产品数为4,开始消费
							System.out.println("产品数为4,开始消费");
							while(c.productNum > 0) {//产品数>0,一直消费
								c.productNum --;//消费
								System.out.println("库存:"+c.productNum);
							}
							System.out.println("产品数为"+c.productNum+",结束消费");
							
							c.notify();//唤醒生产者
						}else {
							try {
								c.wait();//让消费者线程等待
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
					}
				}
			}
			},"消费者").start(); 
		}
}
class Clerk{
	public int productNum = 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code tea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值