Java-day14(多线程)

多线程

0.基本概念

程序 :为完成特定任务,用某种编程语言编写的一组指令的集合(静态

进程 :程序的一次执行过程,或正在执行的一个程序(动态过程)

线程:程序内部的一条执行路径,若某个程序支持同一时间执行多个线程,即支持多线程

1.多线程的创建和使用

继承Thread类创建多线程

  • 一个线程只能执行一次start()
  • 不能通过Thread类的run方法去启动一个线程

例:

java 复制代码
package thead;  

//1.创建继承于Thread的子类
class SubThread extends Thread{
	//2.重写Thread类的run方法,方法内实现此子类线程要完成的功能
	public void run() {
		for(int i = 1;i <= 100;i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i); 
		}
	}
}
public class test1 {

	public static void main(String[] args) {
		//4.创建子类对象
		SubThread s = new SubThread();
		//5.调用start()方法,启动线程;再调用相应的run方法
		s.start();
		
		//主线程执行
		for(int i = 1;i <= 100;i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
		
	}
}

实现Runnable接口创建多线程
package thead; 
//创建多线程的方式二:通过实现的方式
//1.创建实现Runnable接口的类
class PrintNum implements Runnable{
	//2.重写run的方法
	public void run() {
		for(int i = 1;i <= 100;i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
}
public class test4 {

	public static void main(String[] args) {
		//3.创建一个Runnable接口的实现类的对象
		PrintNum p = new PrintNum();
		//4.将此对象作为形参传递给Thread类的构造器中
		Thread t1 = new Thread(p);//想启动多线程,必须调用start()
		t1.start();
		
		Thread t2 = new Thread(p);
		t2.start();
		

	}

}

继承Thread类与实现Runnable接口的区别

  • 1.都继承了Runnable接口(Thread类实现Runnable接口)
  • 2.实现的方式优于继承的方式(避免Java单线程的局限性;如果多个线程要操作同一份资源(或数据),更合适使用实现的方式)

案例:模拟火车站售票,开启三个窗口,总票数100张

继承

java 复制代码
package project;
//模拟火车站售票,开启三个窗口,总票数100张
class Window extends Thread{
	static int ticket = 100; //总票数
	public void run() {		
		while(true) {
			if(ticket > 0) {
				System.out.println(Thread.currentThread().getName() + "售票口:票号为:" + ticket--);	
			}else {
				break;
			}
		}
		
	}
}
public class test1 {

	public static void main(String[] args) {
		Window w1 = new Window();
		Window w2 = new Window();
		Window w3 = new Window();
		
		w1.setName("1号");
		w2.setName("2号");
		w3.setName("3号");
		
		w1.start();
		w2.start();
		w3.start();
	}
}

实现

java 复制代码
package project; 
//模拟火车站售票,开启三个窗口,总票数100张
class Window1 implements Runnable{
	int ticket = 100; //总票数
	public void run() {		
		while(true) {
			if(ticket > 0) {
				System.out.println(Thread.currentThread().getName() + "售票口:票号为:" + ticket--);	
			}else {
				break;
			}
		}	
	}
}
public class test2 {

	public static void main(String[] args) {
		Window1 w = new Window1();
		Thread t1 = new Thread(w);
		Thread t2 = new Thread(w);
		Thread t3 = new Thread(w);
		
		t1.setName("1号");
		t2.setName("2号");
		t3.setName("3号");
		
		t1.start();
		t2.start();
		t3.start();

	}
}

多线程的优点

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

Thread类的主要方法与配置线程优先级


java 复制代码
package thead;          
/*
 * 线程的主要方法
 * 1.start():启动线程并执行相应的run()方法
 * 2.run():子线程要执行的代码放入run()方法中
 * 3.currentThread():静态的,调取当前的线程
 * 4.getName():获取线程的名字
 * 5.setName():设置线程名
 * 6.yield():调用此方法的线程释放当前CPU的执行权
 * 7.join():在A线程中调用B线程的join()方法,当执行到此方法时,A停止,B执行,等B全部执行完后,A再继续执行剩下的任务。
 * 8.isAlive():检测线程是否存活
 * 9.sleep(long l):显式的让当前线程睡眠l毫秒
 * 10.线程通信:wait()  notify()  notifyAll()后续介绍
 * 11.线程优先级(默认5,最大10,最小1):只是提高了抢到的CPU执行权的概率
 * 		getPriority():返回线程的优先级
 * 		setPriority(int newPriority):改变线程的优先级
 */

class SubThread1 extends Thread{
	public void run() {
		
		for(int i = 1;i <= 100;i++) {
//			try {   
//				Thread.currentThread().sleep(1000);
//			} catch (InterruptedException e) {
//				// TODO Auto-generated catch block
//				e.printStackTrace();
//			}
			System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
		}
	}
}
public class test2 {

	public static void main(String[] args) {
		SubThread1 s1 = new SubThread1();
		s1.setName("线程1号");
		s1.setPriority(Thread.MAX_PRIORITY);
		s1.start();
		
		Thread.currentThread().setName("======主线程=====");
		for(int i = 1;i <= 100;i++) {
			System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
//			if(i % 10 == 0) {
//				Thread.currentThread().yield();
//			}
//			if(i == 10) {
//			try { 
//				s1.join();
//			} catch (InterruptedException e) {
//				e.printStackTrace();
//			}
//		}
			
		}
		System.out.println(s1.isAlive());
		
	}
}

练习

java 复制代码
package thead;       

class SuThread1 extends Thread{
	//1-100奇数
	public void run() {
		for(int i = 1;i <= 100;i = i + 2) {
			System.out.println(Thread.currentThread().getName() + " : " + i);
		}
	}
}

class SuThread2 extends Thread{
	//1-100偶数
	public void run() {
		for(int i = 2;i <= 100;i = i + 2) {
			System.out.println(Thread.currentThread().getName()+ " : "  + i); 
		}
	}
}

class test3{
	public static void main(String[] args) {
		SuThread1 st1 = new SuThread1();
		SuThread2 st2 = new SuThread2();
		st1.setName("子线程1号");
		st1.start();
		st2.setName("子线程2号");
		st2.start();
		
//		//继承Thread类的匿名类的对象
//		new Thread() {
//			public void run() {
//				for(int i = 2;i <= 100;i = i + 2) {
//					System.out.println(Thread.currentThread().getName()+ " : "  + i); 
//				}
//			}
//		}.start();
	}
}

2.线程的生命周期

生命周期中通常要经历的5种状态
新建 :当一个Thread类或子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪 :处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备运行的条件
运行 :当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞 :在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡 :线程完成全部工作或线程被提前强制中止

3.线程的同步(重点)

在上面列举的火车售票案例中存在售错票,重票 等的情况,存在线程安全问题
原因 :由于线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享的数据存在安全问题
解决 :必须让一个线程操作共享数据 完毕以后,其他线程才有机会参与共享数据的操作

Java利用线程同步来解决线程安全问题

方法一:同步代码块

java 复制代码
synchronized(同步监视器){
	//需要同步的代码(即为操作共享数据的代码);
	}
  • 1.同步监视器:由一个任意类的对象来充当。哪个线程获此监视器,谁就执行括号中被同步的代码。俗称:锁
  • 2.共享数据:多线程共同操作的同一个数据(变量)
  • 要求:所有的线程必须共用同一把锁

例:

java 复制代码
package project;
//模拟火车站售票,开启三个窗口,总票数100张
class Window2 implements Runnable{
	int ticket = 100; //总票数(共享数据)
	Object obj = new Object();
	public void run() {		
		while(true) {
			synchronized(obj){
			if(ticket > 0) {
				try {
					Thread.currentThread().sleep(10);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "售票口:票号为:" + ticket--);	
			}else {
				break;
			}
		}
	}
	}
}
public class test4 {

	public static void main(String[] args) {
		Window2 w = new Window2();
		Thread t1 = new Thread(w);
		Thread t2 = new Thread(w);
		Thread t3 = new Thread(w);
		
		t1.setName("1号");
		t2.setName("2号");
		t3.setName("3号");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

注:在实现方式中可以用this表示当前对象(同步监视器),若在继承的方式中慎用this

方法二:同步方法

java 复制代码
public synchronized void show() {
	//需要同步的代码(即为操作共享数据的代码);
	}

例:

java 复制代码
package project;  
class Window3 implements Runnable{
	int ticket = 100; 
	public void run() {		
		while(true) {
			show();
		}
	}
	public synchronized void show() {	
		if(ticket > 0) {
			try {
				Thread.currentThread().sleep(10);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "售票口:票号为:" + ticket--);	
		}
		
	}
}
public class test3 {
	public static void main(String[] args) { 
		Window3 w = new Window3();
		Thread t1 = new Thread(w);
		Thread t2 = new Thread(w);
		Thread t3 = new Thread(w);
		
		t1.setName("1号");
		t2.setName("2号");
		t3.setName("3号");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

互斥锁

单例之懒汉式的线程安全

例:

java 复制代码
package thead; 
/* 单例之懒汉式的线程安全:使用同步机制 */
//对于静态方法而言,使用当前类本身充当锁
class Singletion{
	private Singletion() {
		
	}
	private static Singletion instance = null;
	public static Singletion getInstance() {
		if(instance == null) {
			synchronized(Singletion.class) {
				if(instance == null) {
					instance = new Singletion();
				}
			}
		}
		return instance;
	}
}
public class test5 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Singletion s1 = Singletion.getInstance();
		Singletion s2 = Singletion.getInstance();
		System.out.println(s1 == s2);
	}
}
  • 线程同步的弊端:同一时间只能有一个线程访问共享数据,效率变低


练习

java 复制代码
package project;
//线程安全练习
//存在多线程,存在共享数据,需同步
class  Account{
	double balance;//余额
	public Account() {
		
	}
	//存取
	public synchronized void deposit(double amt) {
		balance += amt;
		try {
			Thread.currentThread().sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ":" + balance );
	}
}
class Customer extends Thread{
	Account account;
	public Customer(Account account) {
		this.account = account;
	}
	
	public void run() {
		for(int i = 0;i < 3;i++) {
		account.deposit(1000);
		}
	}
}

public class test5{
public static void main(String[] args) {
	Account acct = new Account();
	Customer c1 = new Customer(acct);
	Customer c2 = new Customer(acct);
	
	c1.setName("甲");
	c2.setName("乙");
	
	c1.start();
	c2.start();
	
	
}
}

线程死锁

例:

java 复制代码
package thead;
/* 死锁:处理线程同步时容易出现 */
//一个线程抓住一半锁,另一个线程抓住一半锁,互不相让
public class test6 {
	static StringBuffer sb1 = new StringBuffer();
	static StringBuffer sb2 = new StringBuffer();

	public static void main(String[] args) {
		new Thread() {
			public void run() {
				synchronized(sb1) {
					try {
					Thread.currentThread().sleep(10);
					}catch(InterruptedException e) {
						e.printStackTrace();
					}
				}
				sb1.append("A");
				synchronized(sb2){
					sb2.append("B");
					System.out.println(sb1);
					System.out.println(sb2);
				}
			}
		}.start();
	
	new Thread() {
		public void run() {
			synchronized(sb2) {
				try {
				Thread.currentThread().sleep(10);
				}catch(InterruptedException e) {
					e.printStackTrace();
				}
			}
			sb1.append("C");
			synchronized(sb1){
				sb2.append("D");
				System.out.println(sb1);
				System.out.println(sb2);
				}
			
			}		
		}.start();
}
}

4.线程的通信

wait()

让当前线程挂起并放弃CPU,同步资源,使用别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问

notify()

唤醒正在排队等待同步资源的线程中优先级最高者结束等待

ontifyAll()

唤醒正在排队等待资源的所有线程结束等待

以上三个方法(Java.lang.Object提供)只有在synchronized()方法或synchronized代码块中才能使用,否则会报异常

例1:

java 复制代码
package thead;  
/* 两个线程交替打印1~100的数 */
//线程通信:wait(),让当前线程停止;notify(),notifyAll()唤醒wait()停止的1个或多个线程
class PrintfNum implements Runnable{
	int num = 1;
	public void run() {
		while(true) {
			synchronized (this) {//解决线程安全问题
				notify();
				if (num <= 100) {
					try {
						Thread.currentThread().sleep(10);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					} //使隐藏的线程安全问题暴露处理
					System.out.println(Thread.currentThread().getName() + ":" + num);
					num += 1;
				} else {
					break;
				}
				try {
					wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}
public class test7 {

	public static void main(String[] args) {
		PrintfNum p = new PrintfNum();
		Thread t1 = new Thread(p);
		Thread t2 = new Thread(p);
		
		t1.setName("A");
		t2.setName("B");
		
		t1.start();
		t2.start();
		

	}

}
生产者/消费者问题

生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走。店员一次只能持有一定数量的产品(如:20).如果生产者试图多生产会被叫停,如果店中有空位才会通知生产者生产;如果店中没有产品,店员会告诉消费者等一下,有产品后在通知消费者取走产品。
:可能会出现的问题:

  • 1.生产者比消费者快,消费者会漏掉一些数据没有取到

  • 2.消费者比生产者快,消费者会取到相同的数据

java 复制代码
package thead; 
//生产者/消费者问题
//多线程:生产者,消费者;共享数据:产品数量;存在数据通信


//店员
class Clerk{
	int product;     
	public synchronized void addProduct() {
		if(product < 20) {
			product += 1;
			System.out.println(Thread.currentThread().getName() + ":生产到第" + product + "产品了!" );
			notify();
		}else
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	}
	public synchronized void consumProduct() {
		if(product <= 0) {
			try {
				wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}else {
			System.out.println(Thread.currentThread().getName() + ":消费到第" + product + "产品了!" );
			product -= 1;
			notify();
		}
	}
}
//生产者
class Producer implements Runnable{
	Clerk clerk;
	
	public Producer(Clerk clerk) {
		this.clerk = clerk;
	}
	
	public void run() {
		System.out.println("生产者开始生产产品");
		while(true) {
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			clerk.addProduct();
		}
	}
}

//消费者
class Customer implements Runnable{
Clerk clerk;
	
	public Customer(Clerk clerk) {
		this.clerk = clerk;
	}
	
	public void run() {
		System.out.println("消费者开始消费产品");
		while(true) {
			try {
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			clerk.consumProduct();
		}
	}
}


public class test8 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Clerk clerk = new Clerk();
		Producer producer = new Producer(clerk);
		Customer customer = new Customer(clerk);
		Thread t1 = new Thread(producer);//一个生产者线程
		Thread t3 = new Thread(producer);//一个生产者线程
		Thread t2 = new Thread(customer);//一个生产者线程
		
		t1.setName("生产1号");
		t2.setName("消费者");
		t3.setName("生产2号");
		
		t1.start();
		t2.start();
		t3.start();
	}
}

练习

在上次练习的基础上,实现交替存款

java 复制代码
package project;
//线程安全练习
//存在多线程,存在共享数据,需同步
//扩展实现交替打印

class  Account{
	double balance;//余额
	public Account() {
		
	}
	//存取
	public synchronized void deposit(double amt) {
		notify();
		balance += amt;
		try {
			Thread.currentThread().sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(Thread.currentThread().getName() + ":" + balance );
		try {
			wait();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
class Customer extends Thread{
	Account account;
	public Customer(Account account) {
		this.account = account;
	}
	
	public void run() {
		for(int i = 0;i < 3;i++) {
		account.deposit(1000);
		}
	}
}

public class test5{
public static void main(String[] args) {
	Account acct = new Account();
	Customer c1 = new Customer(acct);
	Customer c2 = new Customer(acct);
	
	c1.setName("甲");
	c2.setName("乙");
	
	c1.start();
	c2.start();
	
	
}
}

感谢大家的支持,关注,评论,点赞!

参考资料:
尚硅谷宋红康20天搞定Java基础下部

相关推荐
数据小爬虫@2 分钟前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
ZJ_.4 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
Narutolxy9 分钟前
深入探讨 Go 中的高级表单验证与翻译:Gin 与 Validator 的实践之道20241223
开发语言·golang·gin
Hello.Reader16 分钟前
全面解析 Golang Gin 框架
开发语言·golang·gin
禁默27 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood34 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑36 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb421528739 分钟前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶40 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
qq_4336184442 分钟前
shell 编程(二)
开发语言·bash·shell