java第17天----线程的创建,线程安全,单例中的线程安全,线程的非正常停止方法

昨天知识总结

  1. TreeMap的注意点
  2. 增强for循环
  3. Arrays
  4. 可变参数
  5. Collections
  6. 多线程
    • 多线程的基础
    • 多线程的原理
    • 多线程的实例—垃圾回机制
    • 多线程的创建方式

线程

线程创建的两种方法

  • 实例:实现两个售票员售票
    • 分析:创建4个线程,–4个售票员
    • 任务:只需要一个
    • 数据:只需要一个
  • 创建线程的第一种方法:通过创建Thread类的子类—让run留在了线程的内部,造成任务与线程的绑定,操作不方便
	public static void main(String[] args) {
		//创建线程对象
		Seller seller1 = new Seller();
		Seller seller2 = new Seller();
		Seller seller3 = new Seller();
		Seller seller4 = new Seller();
		//开启线程
		seller1.start();
		seller2.start();
		seller3.start();
		seller4.start();
	}
	class Seller extends Thread{
	static int num = 40;//为了实现num的共享
	public void run() {
		for(int i = 0; i < 10;i++) {
			System.out.println(Thread.currentThread().getName()+"  i:"+i+" "+--num);
		}
	}
}
  • 第二种方式:让线程与任务分离----将run从线程中独立出来,好处:操作起来更方便,那个线程想工作,我就把任务交给谁。
	//第二种线程与任务分离
	public static void main(String[] args) {
		//创建任务对象
		Ticket ticket = new Ticket();
		//创建线程对象并关联同一个任务
		//如果我们创建了自己独立的任务类,线程会优先调用我们手动传入的任务类对象的run方法,不会再去调用Thread默认的run方法
		Thread seller1 = new Thread(ticket);
		Thread seller2 = new Thread(ticket);
		Thread seller3 = new Thread(ticket);
		Thread seller4= new Thread(ticket);
		//开启线程
		seller1.start();
		seller2.start();
		seller3.start();
		seller4.start();
	}
}
//第二种:线程与任务分离
//创建任务类
class Ticket implements Runnable{
	//因为Ticket对象呗四个线程共享,所以num作为属性也被共享了
	int num = 40;//为了实现num的共享
	public void run() {
		for(int i = 0; i < 10;i++) {
			System.out.println(Thread.currentThread().getName()+"  i:"+i+" "+--num);
		}
	}
}

线程安全和解决方法

  • 线程安全问题:
  • 分析:4个线程共用一个数据,出现了-1,-2,-3等错误的数据
  • 具体分析:1.共用了一个数据
  • 2.共有语句有多余,一个线程使用cpu,没有使用完,CPU被抢走,当再次抢到cpu的时候,直接执行后面的语句,造成了错误的发生
    java第17天----线程的创建,线程安全,单例中的线程安全,线程的非正常停止方法
  • 解决:
  • 在代码中使用同步代码块儿(同步锁)
  • 解释:在某一段任务中,同一时间只允许一个线程执行任务,其他的线程即使抢到了CPU,也无法进入当前的任务区间,只有当当前的线程将任务执行完后,其他的线程才能有资格进入
  • 同步代码块儿的构成:
  • synchronized(锁(对象)){
  • 同步的代码
  • }
  • 对做为锁的对象的要求:1.必须是对象 2.必须保证被多个线程共享
  • 可以充当锁的:
    1. 一个普通的对象
    2. 当前对象的引用----this
    3. 类的字节码文件
  • 同步代码块儿的特点:
    1. 可以保证线程安全
    2. 由于每次都要进行判断处理,所以降低了执行效率
  • 总结:什么时候使用同步代码块儿?
    1. 多个线程共享一个数据
    2. 至少有两个线程
public static void main(String[] args) {
   	//创建任务对象
   	Ticket1 ticket = new Ticket1();
   	//创建线程对象并关联同一个任务
   	//如果我们创建了自己独立的任务类,线程会优先调用我们手动传入的任务类对象的run方法,不会再去调用Thread默认的run方法
   	Thread seller1 = new Thread(ticket);
   	Thread seller2 = new Thread(ticket);
   	Thread seller3 = new Thread(ticket);
   	Thread seller4= new Thread(ticket);
   	//开启线程
   	seller1.start();
   	seller2.start();
   	seller3.start();
   	seller4.start();
   }
}
//第二种:线程与任务分离
//创建任务类
class Ticket1 implements Runnable{
   //因为Ticket对象呗四个线程共享,所以num作为属性也被共享了
   int num = 20;
   boolean flag = false;
   //让object充当锁
   //作为锁要满足两个条件:1.必须是对象,2.必须供所有的线程共享
   //可以作为锁的有1.任意一个实例对象,2.this 3.字节码文件
   Object object = new Object();
   public void run() {
   	//制造一个延迟,相当于让当前执行的run的线程休息一会儿(临时让出cpu)
   	try {
   		Thread.sleep(100);//100是时间,单位是毫秒
   	} catch (InterruptedException e) {
   		// TODO Auto-generated catch block
   		e.printStackTrace();
   	}
   	while(!flag) {
   		
   		synchronized (this) {//同步代码块--让线程之间互斥
   			try {
   				Thread.sleep(100);//100是时间,单位是毫秒
   			} catch (InterruptedException e) {
   				// TODO Auto-generated catch block
   				e.printStackTrace();
   			}
   			if(num > 0) {
   				System.out.println(Thread.currentThread().getName()+" "+--num);
   			}else {
   				flag = true;
   			}
   		}
   	}
   }
}

同步函数

  • 实例:两个人向同一个账户里面存钱
  • 一人存三次,每次存100
  • 注意:1.当一个类中同时存在多个synchronized修饰的代码块儿或函数式,要想安全,就必须让他们后面的对象一致,因为只有同一把锁才安全。
  • 静态同步函数的锁是其所属类的字节码文件
  • 理解synchronized关键字
  • 1.synchronized的关键字的作用域有两种:
    • a. 是某个对象实例内:synchronized aMethod(){}可以防止多个线程同事访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中一个synchronized方法,其他线程不能同时访问这个对象中的任何一个synchronized方法)。这时不同的对象实例的synchronized方法是不想干扰的。也就是说,其他线程照样可以同时访问相同类的另一个对象实例中的synchronized方法。
	public static void main(String[] args) {
		//创建任务类对象
		Ticket2 ticket2 = new Ticket2();
		
		//创建线程并绑定任务
		Thread thread1 = new Thread(ticket2);
		Thread thread2 = new Thread(ticket2);
		//开启线程
		thread1.start();
		thread2.start();
		
	}
}
class Ticket2 implements Runnable{
	Bank bank = new Bank();
	@Override
	public void run() {
		 //TODO Auto-generated method stub
		for(int i = 0; i  < 3;i++) {
			bank.addMany(100);
		}
	}
}
class Bank{
	int sum;
	//非静态的同步函数
	//相当于默认在synchronize的后面跟着this充当锁
	public synchronized void addMany(int money) {
		
		sum+=money;
		System.out.println(sum);
	}
}
    • b. 是某个类的范围:synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static方法。他可以对类的所有对象实例起到作用。
	public static void main(String[] args) {
		//创建任务类对象
		Ticket2 ticket2 = new Ticket2();
		
		//创建线程并绑定任务
		Thread thread1 = new Thread(ticket2);
		Thread thread2 = new Thread(ticket2);
		//开启线程
		thread1.start();
		thread2.start();
		
	}
}
class Ticket2 implements Runnable{
	Bank bank = new Bank();
	@Override
	public void run() {
		 //TODO Auto-generated method stub
		for(int i = 0; i  < 3;i++) {
			bank.addMany(100);
		}
	}
}
class Bank{
	int sum;
	//静态的同步函数
	//相当于默认在synchronize的后面跟着当前类的字节码文件
	public synchronized static void addMany(int money) {
		
//		sum+=money;
//		System.out.println(sum);
	}
}
  • 2.除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是:synchronized(this){//区块/},它的作用域是当前对象;
   public static void main(String[] args) {
   	//创建任务类对象
   	Ticket2 ticket2 = new Ticket2();
   	
   	//创建线程并绑定任务
   	Thread thread1 = new Thread(ticket2);
   	Thread thread2 = new Thread(ticket2);
   	//开启线程
   	thread1.start();
   	thread2.start();
   	
   }
}
class Ticket2 implements Runnable{
   Bank bank = new Bank();
   @Override
   public void run() {
   	 //TODO Auto-generated method stub
   	for(int i = 0; i  < 3;i++) {
   		bank.addMany(100);
   	}
   }
}
class Bank{
   int sum;
   public void addMany(int money) {
   	synchronized (this) {
   		sum+=money;
   		System.out.println(sum);
   	}	
   }
}
  • 3.synchronized关键字是不能被继承的,也就是说,基类的方法synchronized f(){}
  • 在继承类中并不自动式synchronizedf(){},而是变成了f(){},继承类需要你显示的指定它的某个方法为synchronized方法

在懒汉式单例中添加锁

java第17天----线程的创建,线程安全,单例中的线程安全,线程的非正常停止方法

class SingleInstance1{
	private static SingleInstance1 instance1; 
	private SingleInstance1() {
		
	}
	//因为同步代码块的效率高于同步函数,所以尽量使用同步代码块;
	public static SingleInstance1 getInstance() {
		if(instance1 == null) {//目的:尽量减少线程安全代码的判断次数,提高效率 
			synchronized (SingleInstance1.class) {
				if(instance1 == null) {
					instance1 = new SingleInstance1();
				}
			}
		}
		return instance1;
	}
}

线程的停止

  • 线程的停止:如何让它的任务结束

1.通过一个标识结束线程

	public static void main(String[] args) {
   	Test1 test1 = new Test1();
   	Thread thread = new Thread(test1);
   	thread.start();
   	
   	try {
   		Thread.sleep(100);
   	} catch (InterruptedException e) {
   		// TODO Auto-generated catch block
   		e.printStackTrace();
   	}
   	
   	int i = 0;
   	while(true) {
   		if(++i == 1000000000) {
   			test1.flag = false;//档主线程执行到每个简短的时候,让false值变成false,控制while循环结束,从而控制了线程的结束
   			break;
   		}
   	}
   }
}
class Test1 implements Runnable{
   boolean flag = true;

   @Override
   public void run() {
   	// TODO Auto-generated method stub
   	while(flag) {
   		System.out.println(Thread.currentThread().getName()+"      我们很嗨皮");
   	}
   }
}

2.通过stop方法结束线程----有固有的安全问题,已经过时,不建议在使用

3.调用interrupt()方法结束线程----

  • 原理: 线程可以调用wait()方法,让当前的线程处于钝化的状态(会立即释放CPU,并无法强CPU的状态,但当前的线程并没有死亡)
  • 调用interrupt方法就是将处于wait状态下的线程停止
  • 注意:wait方法必须在同步状态下使用
//调用interrupt()方法结束线程
public class Demo7 {
   public static void main(String[] args) {
   	Test1 test1 = new Test1();
   	Thread thread = new Thread(test1);
   	thread.start();
   	//让主线程休息一会
   	try {
   		Thread.sleep(100);
   	} catch (InterruptedException e) {
   		// TODO Auto-generated catch block
   		e.printStackTrace();
   	}
   	
   	int i = 0;
   	while(true) {
   		if(++i == 1000000000) {
   			thread.interrupt();//当调用这个方法的时候,会触发wait方法的InterruptedException异常,我们就可以在捕捉异常的
   								//的时候讲flag变成false,从而结束循环,结束任务,结束线程
   			break;
   		}
   	}
   }
}
class Test1 implements Runnable{
   boolean flag = true;

   @Override
   public synchronized void run() {
   	// TODO Auto-generated method stub

   	while(flag) {
   		try {
   			wait();
   		} catch (InterruptedException e) {
   			// TODO Auto-generated catch block
   			flag = false; 
   			System.out.println("InterruptedException");
   		}//wait方法由锁对象来调用
   		System.out.println(Thread.currentThread().getName()+"      我们很嗨皮");
   	}
   }
}