Java程序猿必学第十八篇—— 多线程

/*
1.1 休眠方法
语法: Thread.sleep(毫秒)
用法: 可以用在主线程或子线程中
目的: 在线程中可以复现互抢资源的现象  
*/

//1.2 线程优先级设置
//线程优先级设置:给线程设置优先级,可以大概率的确定谁先执行完; 但不是绝对的

//案例:创建两个子线程,分别打印1~200,并设置优先级,查看执行结果
//分析:创建一个线程类,new两次;其中一个设置高的优先级;一个设置低的优先级

//细节:
//1.调用设置优先级方法:setPriority
//2.给定线程名:

class MyThread extends Thread{
	//private String name;
	public MyThread(String name) {
		//this.name = name;
		super(name); //将线程名的值,传给父类的name属性
	}
	
	@Override
	public void run() {
		for(int i=1;i<=200;i++) {
			//调父类的getName方法,适用继承Thread方式
			//System.out.println(super.getName()+"---->"+i); 
			//通用方式:适用继承Thread与实现任务方式
			System.out.println(Thread.currentThread().getName()+"===>"+i);
		}
	}
}
public class Test1 {
	public static void main(String[] args) {
		MyThread thread1 = new MyThread("线程1");
		thread1.setPriority(Thread.MIN_PRIORITY); //设置小优先级
		thread1.start();
		
		
		MyThread thread2 = new MyThread("线程2");
		thread2.setPriority(Thread.MAX_PRIORITY); //设置优先级,再启动
		thread2.start();
	}
}


//1.3. 线程的礼让
//线程的礼让: yield
//设置了礼让的线程,会将线程资源让一次出去,继续跟其他线程争抢;
//会影响执行效率,但不是绝对性的
//案例:创建两个子线程,分别打印1~200,其中一个线程,每打印一次则yield一次,查看结果
//分析:创建两个线程继承Thread,其中一个设置礼让

class A extends Thread{
	@Override
	public void run() {
		for(int i=1;i<=200;i++) {
			System.out.println("设置礼让线程1===>"+i);
			Thread.yield();  //礼让
		}
	}
}

class B extends Thread{
	@Override
	public void run() {
		for(int i=1;i<=200;i++) {
			System.out.println("线程2===>"+i);
		}
	}
}
public class Test2 {
	public static void main(String[] args) {
		new A().start();
		new B().start();
	}
}


//1.4 线程的合并
//线程的合并(插队)--join
//在线程中设置了插队后,插队的线程绝对性地先执行完;
//案例:主线程和子线程,共同执行1~200,主线程执行到50后,子线程插主线程的队;子线程绝对先执行完

//说明:
//1. 最终的结论肯定是插队的子线程先执行完
//2. 过程有两种情况:
   //a.子线程一次性执行完,再执行主线程的
   //b.主线程执行到50,主线程会停下来,让插队的子线程先执行完
class SonThread extends Thread{
	@Override
	public void run() {
		for(int i=1;i<=200;i++) {
			System.out.println("子线程===>"+i);
		}
	}
}
public class Test3 {
	public static void main(String[] args) throws InterruptedException {
		SonThread son = new SonThread();
		son.start();
		
		for(int i=1;i<=200;i++) {
			System.out.println("主线程===>"+i);
			if(i==50) {
				son.join();  //son子线程插主线程的队
			}
		}
	}
}




 

//2. 线程安全(重点)

//2.1 多线程存储数组元素的数据安全问题
//线程安全案例:
//案例:在线程中给定一个数组,两个线程同时往数组中存元素
//分析:先执行的线程应该存储第一个下标位置,后执行的线程,应该存储第二个下标位置
//问题:还没来得及下标的累加,可能都存储到了第一个位置---因为线程具有随机互抢特性

//代码应用:
//问题1:打印数组时,可能数据还没有存储
//解决: 想个办法先执行子线程的存储;再执行主线程的打印---join
//问题2:new两个对象,线程中对象的属性都有两份,没办法共享数据
//解决:将线程的属性变为static
//问题3:数据出现问题,都存储到第一个位置了
//加锁:
//锁: 同步代码块,同步方法
//同步代码块: synchronized("lock") {}
//锁的注意事项: 1.两个线程使用同一把锁(同一个锁对象),2.锁的范围


class MyThread extends Thread{
	static String[] s = {"","","","",""};  //数据存数据
	static int    index;    //下标
	
	String value;
	public MyThread(String value) {
		this.value = value;
	}
	
	@Override
	public void run() {
		//只有一个线程能进入锁,另一个在外面等待,等待锁的线程执行完毕,另一个才能进入
		synchronized("lock") { //静态属性,字符串常量
			s[index] = value;  //将存储与下标累加操作进行捆绑
		    //睡眠3毫秒,复现有问题的数据
			index++;
		}
	}
}
public class Test {
	public static void main(String[] args) throws InterruptedException {
		MyThread my1 = new MyThread("hello");
		my1.start();
		
		MyThread my2 = new MyThread("world");
		my2.start();
		
		my1.join();  //两个子线程都要插主线程的队
		my2.join();
		//打印数组:
		System.out.println(Arrays.toString(MyThread.s));
	}
}



//2.2 继承Thread方式实现线程安全

//线程安全案例: 5个窗口共同卖1000张票
/*例如: 001窗口正在卖第1000张票
 *    003窗口正在卖第999张票
 *    005窗口正在卖第998张票 
 *    。。。
 *    002窗口正在卖第1张票 
 *    
 *1. 继承Thread方式    
 *    
 *问题1: 每个对象都会卖1000张,共5000张
 *解决方案: ticket属性+static修饰,变为了5个线程只卖1000张
 *
 *问题2:出现部分重票的问题---数据安全问题
 *解决方案: 加锁
 *锁的注意事项:
 *1.同一把锁(静态属性,常量值)    
 *2.锁的范围,锁整个while还是whlie里面的语句(锁住里面,如果锁while,则只有一个窗口卖) 
 *
 *问题3: 会出现负数和0?  临界点问题
 *原因: 当票到达1张时,所有线程都可进入到while判断ticket>0,只是一个线程进入锁,其他在锁外等待
 *解决: 在锁内部加入if(ticket>0)的判断
 *
 *问题4: 优化,有多少个窗口卖,必须有多少个窗口退出
 *解决方案:加入else提示,将while做一个死循环,在else中break
 *    
*/

class MyThread extends Thread{
	private static int ticket = 1000;
	
	public MyThread(String name) {
		super(name);
	}

	@Override
	public void run() {
		
		//锁方式1:同步代码块
		/*
		while(true) {
			synchronized ("lock") {
				if(ticket>0) {
					System.out.println(super.getName()+"窗口正在卖第"+ticket+"张票");
					ticket--;
				}else {
					System.out.println(super.getName()+"窗口已经卖完了");
					break;
				}
			}
		}*/
		
		//锁方式2: 同步方法:也要注意同一把锁(调用方法的对象-this有问题,需加static),和锁的范围
		while(true) {
			if(save()) {  //在同步方法调用中要进行返回值判断
				break;
			}
		}
	}

	//同步方法的实现
	private static synchronized boolean save() {
		if(ticket>0) {
			System.out.println(Thread.currentThread().getName()+"窗口正在卖第"+ticket+"张票");
			ticket--;
			return false;
		}else {
			System.out.println(Thread.currentThread().getName()+"窗口已经卖完了");
			return true;
		}
	}
	
	
}
public class Test1 {
	public static void main(String[] args) {
		for(int i=1;i<=5;i++) {
			new MyThread("00"+i).start();
		}
	}
}


 



//2.3 实现任务方式完成线程安全

//通过实现任务的方式完成卖票系统的案例:
//分析:创建一个Task类实现Runnable接口

//与继承Thread的区别
//1.多个线程操作同一个Task任务,所以属性无需加static
//2.同步代码块的锁对象可以用this
//3.同步方法无需加static

class Task implements Runnable{
	private int ticket = 1000;
	@Override
	public void run() {
		/*
		while(true) {
			//锁方式1:同步代码块
			synchronized (this) {
				if(ticket>0) {
					System.out.println(Thread.currentThread().getName()+"窗口正在卖第"+ticket+"张票");
					ticket--;
				}else {
					System.out.println(Thread.currentThread().getName()+"窗口已经卖完了");
					break;
				}
			}
		}
		*/
		//锁方式2: 同步方法:也要注意同一把锁(调用方法的对象-this有问题,需加static),和锁的范围
		while(true) {
			if(save()) {  //在同步方法调用中要进行返回值判断
				break;
			}
		}
	}
	
	//同步方法的实现
	private synchronized boolean save() {
		if(ticket>0) {
			System.out.println(Thread.currentThread().getName()+"窗口正在卖第"+ticket+"张票");
			ticket--;
			return false;
		}else {
			System.out.println(Thread.currentThread().getName()+"窗口已经卖完了");
			return true;
		}
	}
	
}

public class Test2 {
	public static void main(String[] args) {
		Task task = new Task();
		for(int i=1;i<=5;i++) {
			new Thread(task,"00"+i).start();   //多个线程共同操作同一个任务
		}
	}
}


//3. 死锁

//死锁案例: 线程的双方都握着对方的资源,都退不出去,最后形成了死锁---锁嵌套
//分析:
//创建一个类继承Thread,里面有一个属性,用于做判断的
//实例化两个线程,传递参数,1个为true,一个为false
//判断中,一个先执行A锁,一个先执行B锁的锁嵌套

//注意:学习死锁的目的,就是为了规避死锁

//死锁分析:
//1.当一个线程进入A锁,又直接进入B锁,则锁不住
//Thread-0--进入了A锁
//Thread-0--进入了B锁
//Thread-1--进入了B锁
//Thread-1--进入了A锁

//2.当一个线程进入A锁,另一个线程同时进入B锁,则死锁
//Thread-1--进入了B锁
//Thread-0--进入了A锁

class MyThread extends Thread{
	private boolean flag; 
	
	public MyThread(boolean flag) {
		this.flag = flag;
	}
	
	@Override
	public void run() {
		if(flag) {
			synchronized ("A") {
				System.out.println(super.getName()+"--进入了A锁");
				
				
				synchronized ("B") {
					System.out.println(super.getName()+"--进入了B锁");
				}
			}
		}else {
			synchronized ("B") {
				System.out.println(super.getName()+"--进入了B锁");
				
				synchronized ("A") {
					System.out.println(super.getName()+"--进入了A锁");
				}
			}
		}
	}
} 

public class Test1 {
	public static void main(String[] args) {
		new MyThread(true).start();
		
		new MyThread(false).start();
	}
}

猜你喜欢

转载自blog.csdn.net/m0_62718093/article/details/121127920
今日推荐