Java多线程

多线程

  • 相关理解
    • 进程:分配应用程序的内存空间
    • 线程:负责进程中内容执行的控制单元,也叫执行路径执行情景
    • 多线程:一个进程可以有多个执行路径。一个进程至少有一个线程
    • 任务:每个线程要运行的内容
    • CPU切换线程执行可以时间片来切换执行不同进程。随机切换。
  • 好处
    • 可以让多部分同时运行
  • 弊端
    • 不同的进程开启了很多的线程,CPU分给每个线程的执行时间变少,因此线程运行变慢,效率变低(可以加CPU)
  • JVM的多线程
    • JVM启动时启动了多个线程,至少可以分析出两个线程
      • 主线程(main函数执行),任务代码都定义在main函数
      • 垃圾回收线程(GC),任务代码定义在垃圾回收器内部
      • 其他
    • 主线程结束,其他线程不一定结束。所有线程结束,jvm结束。
    • finalize();子类重写该方法可以执行其他清除,不写的话,直接默认被垃圾回收器清除。Object类的方法
    • System.gc(); 运行垃圾回收器,告诉垃圾回收器收垃圾,什么时候回收垃圾是随机的。
  • 多线程创建
    • windows当中,任务管理器创建需要的进程和线程
    • 主线程名是main
    • 继承Thread类来创建线程
      • 定义一个类继承Thread类
      • 覆盖Thread类中的run()方法,run()调用需要执行的代码
      • 建一个该类的对象,创建线程
      • 调用start()方法启动线程,run()方法自动运行
      • 一个类有父类,就不要继承Thread了(不然多继承了)
      • 快速创建线程可以使用匿名内部类
      • 相关方法
        • getName()          获取线程的名称,线程对象创建后,名称就定义了。运行run()方法也可以获取其名称
        • currentThread()  返回当前正在执行线程的引用
        • start()                 开启一个线程
        • stop()                 停止当前所有线程的运行(危险,慎用)
        • sleep()                使当前线程睡眠
        • wait()                 使当前线程处于冻结状态
        • notify()               从线程池中唤醒一个线程
        • interrupt()          中断当前的wait或sleep等的状态
        • setDaemon(true)设置线程为守护线程,需要在start()前设置。前台线程全部结束,该线程会自动结束,无论是否是冻结状态。守护线程也叫后台线程,true标记为守护线程
        • join()                  执行该方法就是冻结当前运行线程,执行该线程。该线程运行完,在继续运行刚刚中止的线程。可能抛出异常InterruptedException
        • toString()            返回字符串形式的线程名称,优先级(获取CPU执行权的几率,越大获取几率越高(1-10))和线程组(线程的组的划分,可以创建线程的时候指定)
        • setPriority(Thread.MAX_PRIORITY)  设置进程执行优先级,MAX_PRIORITY 10 ,MIN_PRIORITY  1,NORM_PRIORITY  5(默认为5)
        • yield()                 释放当前线程执行权。
/*三个线程,自定义的两个,主线程一个*/
class Test{
	public static void main(String[] args){
		/*	
			创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码同时运行
			运行的指定代码就是这个执行路径的任务。
			jvm创建的主线程的任务都定义在了主函数中
			自定义的线程的任务在哪里?
			Thread类用于描述线程,线程是需要任务的,所以Thread类也对任务描述
			这个任务通过Thread类的run()方法来体现(run方法封装了自定义线程的任务)
			run()方法中定义了线程运行的任务代码
		*/
		DemoThread d1 = new DemoThread("旺财");
		DemoThread d2 = new DemoThread("qiang");
		d1.start();/*开启线程,调用run()方法*/
		for(int x = 0; x < 20; x++){
			System.out.println(x+">>>"+Thread.currentThread().getName());
		}
		d2.start();
	}
}
class DemoThread extends Thread{
	private String name;
	DemoThread(String name){
		super(name);/*调用父类构造函数,自动修改进程名称*/
	}
	/*重写run()就是为了运行自定义任务*/
	public void run(){
		show();
	}
	public void show(){
		for(int x = 0; x < 10; x++){
			for(int y = -999999999; y < 999999999; y++){}
			System.out.println(name+"....x="+x+"....name="+Thread.currentThread().getName());/*获取当前运行线程的名字,Thread.currentThread().getName()*/
		}
	}
}
      • 上述代码运行在栈中的体现
        • 每一个线程一个栈。主函数先运行main(),开启一个栈,到了d1.start(),运行run(),开启一个栈,到了d2.start(),运行run(),开启一个栈
        • 每个线程中的代码在各自的栈中分别执行
        • 如果主线程或其他某个线程出现异常,该线程执行中断,不影响其他线程。
    • 实现Runnable接口创建线程(有父类,但需要创建多线程,可以从API查找,更常用)
      • 定义类实现Runnable接口,覆盖run()方法,将线程任务代码封装到run()中
      • 创建Thread对象,传入实现了Runnable接口的子类对象,即可调用线程该对象创建的任务
      • 向Thread对象传入Runnable接口子类对象是因为它封装了任务代码,传进去,线程开启前才能运行指定任务。
class Test{
	public static void main(String[] args){
		DemoThread d = new DemoThread();/*创建了一个任务*/
		Thread t1 = new Thread(d);/*传入实现了Runnable接口的对象,相当于分配了任务*/
		Thread t2 = new Thread(d);
		t1.start();/*执行任务*/
		t2.start();
		for(int i = 0; i < 10; i++){
			for(int j = -999999999; j < 999999999; j++){}
			System.out.println(i+"..."+Thread.currentThread().getName());
		}
	}
}
class DemoThread implements Runnable{
	public void show(){
		for(int i = 0; i < 10; i++){
			for(int j = -999999999; j < 999999999; j++){}
			System.out.println(i+"..."+Thread.currentThread().getName());
		}
	}
	public void run(){
		show();
	}
}
      • 传入Runnable对象的解释
class Thread{
	private Runnable r;
	Thread(){
		
	}
	Thread(Runnable r){
		this.r = r;/*传入的Runnable对象相当于在这里将引用赋值给了内部*/
	}
	public void run(){
		if(r != null)
			r.run();/*运行的时候看传入自定义的任务没,传入就执行自定义的*/
	}
	public void start(){
		run();
	}
}
      • Runnable接口的好处
        • 将线程任务从线程子类分离出来,将线程的任务进行对象封装,不必继承Thread所有方法。
        • 避免了Java单继承局限性
        • 与Thread相比有了思想上的变化,继承Thread是让任务成为他的一部分,实现Runnable是将任务封装成一个对象。
        • Thread也实现了Runnable是因为和其他对象可以向上抽取任务的run()方法。
  •  线程的状态
    • start():线程从创建运行
    • run():线程从运行消亡(运行完了线程就消亡了)
    • stop():线程从运行消亡(主动关闭线程,不安全)
    • sleep(time):线程从运行冻结,time(毫秒)时间后运行临时阻塞
    • wait():线程从运行冻结(一直冻结)
    • notify():线程从冻结运行临时阻塞
    • CPU执行资格:可以被CPU处理,在处理队列中排队
    • CPU执行权:正在被CPU处理
  • 线程使用
    • 同一段代码,好多人都需要同时用,可以封装成线程。
  • Runtime异常的情况
    • 功能没问题,传值出现问题
    • 功能状态发生问题
      • Thread t = new Thread(); t1.start();
      • 线程已开启,处于一种状态,再开启会出现问题
  • 线程安全(可以参看实际问题中的买票代码)
    • 下边代码是线程执行的代码
    • 因为在线程执行的时候有随机性,假如说现在num=1,有t1和t2两个线程
    • CPU执行t1,先判断num为1大于0,然后转换执行t2,此时num不变还是1大于0
    • 切换到t1,执行输出,1,切换到t2输出0,因而输出了不应该输出的数据。有安全隐患
    • 可以加上sleep()方法观察一下
public void sale(){
	while(true){
		if(num > 0){
			/*sleep可能抛出异常,但是实现的接口没有抛出异常,所以不能声明只能catch*/
			/*try{
				Thread.sleep(10);这里会释放执行权
			}
			catch(InterruptedException e){
				
			}*/
			System.out.println(Thread.currentThread().getName()+">>>>"+num--);
		}
		else
			break;
	}
}
  • 线程安全产生的原因
    • 多个线程操作共享数据
    • 操作共享数据的代码超过一条
  • 线程安全问题的解决
    • 同步
      • 将操作共享数据的多条代码封装成整体(使用同步代码块),有一个线程在执行这个代码,其他线程不能执行,该线程执行完,其他线程继续执行。
      • 表现形式
        • 同步代码块代码块, 同步函数(synchronized可以修饰函数)
      • 同步代码块格式
synchronized(对象){/*放一个标记对象,相当于整个任务对象的标记(锁,同步锁),后边用于同步代码监视,不能放到方法里,放到成员变量里*/
	/*放需要被同步代码;*/
	if(num > 0){
		try{
			Thread.sleep(10);
		}
		catch(InterruptedException e){
						
		}
		System.out.println(Thread.currentThread().getName()+">>>>"+num--);
	}
	else
		break;
}
      • 上述代码解释
        • 一个线程先判断该对象是否被持有,没有被持有则占用他,执行里边的代码,执行到sleep()的时候相当于释放了执行权
        • CPU转而执行其他线程,但是每个线程在执行同步代码的时候,先判断对象持有情况,一直都被持有,则无法执行同步代码
        • 直到第一个线程sleep()结束并执行完接下来的同步代码,才会释放对象的持有。这个时候CPU再次挑选线程执行相应代码。
      • 好处
        • 解决了线程安全问题。
      • 弊端
        • 相对降低了效率(执行同步代码的线程不会一直占有CPU,执行权转到外部线程时,外部线程会一直判断同步锁,却无法执行代码)
      • 前提
        • 必须有多个线程使用同一个锁(锁写到方法里,每个线程一个锁,相当于没加,所以写到成员变量里)
      • 同步函数格式
public synchronized void run(){
	while(true){
		if(num > 0){
			try{
				Thread.sleep(10);
			}
			catch(InterruptedException e){
				
			}
			System.out.println(Thread.currentThread().getName()+">>>"+num--);
		}
		else{
			break;
		}
	}
}
        • 将函数同步,一个线程占有该函数之后,除非将里边的所有代码全部执行完,否则不会放开该函数占有权。就算放开执行权,其他线程无法占有该函数,也无法执行,只能最开始的线程执行。
        • 所以只将需要同步的代码封装到函数里即可,然后调用该函数
        • 同步函数用的锁是this对象。
public void run(){
	while(true){
		show();
	}
}
public synchronized void show(){
	if(num > 0){
		try{
			Thread.sleep(10);
		}
		catch(InterruptedException e){
			
		}
		System.out.println(Thread.currentThread().getName()+">>>"+num--);
	}
}
      • 同步函数和同步代码块的区别:
        • 同步函数的锁只能是this对象,同步代码块可以用不同的对象。
        • 同步代码块用了this,可以简写为同步函数。
        • 建议使用同步代码块。
      • 下边的代码可以测试出同步函数和同步代码块是否在使用同一个锁
        • 当运行run()方法的时候,通过flag来判断当前线程要运行哪一个run()方法,t1默认运行同步代码块
        • 为了不让t1.start()和t2.start()运行的太快,中间加一个睡眠,来达到先让t1的线程判断flag,然后再执行main线程后边的方法。
        • t1线程运行起来之后,flag被修改为false,然后运行同步函数。
        • 同步函数默认调用当前对象锁,同步代码块传入的是this对象,所以两个线程操作的是同一个锁,不会再有线程安全问题。
class Ticket implements Runnable{
	private int num = 100;
	boolean flag = true;
	public void run(){
		if(flag)
			while(true){
				synchronized(this){
					if(num > 0){
						try{
							Thread.sleep(10);
						}
						catch(InterruptedException e){
							
						}
						System.out.println(Thread.currentThread().getName()+">>>code>>>"+num--);
					}
				}
			}
		else
			while(true){
				show();
			}
	}
	public synchronized void show(){
		if(num > 0){
			try{
				Thread.sleep(10);
			}
			catch(InterruptedException e){
				
			}
			System.out.println(Thread.currentThread().getName()+">>>function>>>"+num--);
		}
	}
}
class Test{
	public static void main(String[] args){
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		t1.start();
		try{
			Thread.sleep(10);
		}
		catch(InterruptedException e){
		}
		t.flag = false;
		t2.start();
	}
}
      • 静态同步函数
        • 使用的锁是当前字节码文件所属的对象
public static synchronized void show(){
	if(num > 0){
		try{
			Thread.sleep(10);
		}
		catch(InterruptedException e){
			
		}
		System.out.println(Thread.currentThread().getName()+">>>function>>>"+num--);
	}
}
        • this.getClass()可以获取当前静态函数所在字节码文件所属的对象
        • 类名.class也可以获取当前静态函数所在字节码文件所属的对象(用静态属性获取)
synchronized(this){
	if(num > 0){
		try{
			Thread.sleep(10);
		}
		catch(InterruptedException e){
			
		}
		System.out.println(Thread.currentThread().getName()+">>>code>>>"+num--);
	}
}
      • 单例模式中懒汉式和饿汉式的线程安全问题。
/*饿汉式*/
class Single{
	private static final Single s = new Single();
	private Single(){
	}
	public static Single getInstance(){
		/*将这个方法放到多线程返回没有问题,因为只有一句话且变量已经是final*/
		return s;
	}
}
/*懒汉式-同步函数*/
class Single{
	private static Single s = null;
	private Single(){
		
	}
	public static synchronized Single getInstance(){
		/*将该方法放到多线程中返回可能会有安全隐患,同时调用可能返回不同的对象,有多句话,执行顺序可能不同,可以加同步函数,但降低了效率*/
		
		if(s == null)
			Single = new Single();
		return s;
	}
}
/*懒汉式-同步代码块*/
class Single{
	private static Single s = null;
	private Single(){
		
	}
	public static Single getInstance(){
		/*不能用this.getClass(),因为非静态*/
		/*前边s为空的时候会判断锁,不为空则不判断锁,减少了锁资消耗*/
		/*加锁解决线程安全问题,多加一个if提高了效率,解决了安全和效率。虽然多加了if,但判断s的时候这个不可少*/
		if(s == null){
			synchronized(Single.class){
				if(s == null)
					Single = new Single();
				return s;
			}
		}
	}
}
      • 同步中的死锁
        • 常见情景
          • 同步嵌套:一共两个锁,两个线程,每个线程必须同时拿到两个锁才能进行执行功能,但是每个线程只拿到一个,想用对方的,但对方都没放开,产生死锁。
            • 下边的代码在调用的过程中会在执行到某次的时候,每个线程各自拿到一个锁(this或obj),想得到对方那个,却得不到
/*死锁程序1*/
class Ticket implements Runnable{
	private int num = 100;
	Object obj = new Object();
	boolean flag = true;
	public void run(){
		if(flag)
			while(true){
				synchronized(obj){
					show1();
				}
			}
		else
			while(true)
				show();
	}
	public synchronized void show(){
		synchronized(obj){
			if(num > 0){
				try{
					Thread.sleep(10);
				}
				catch(InterruptedException e){
					
				}
				System.out.println(Thread.currentThread().getName()+">>>>"+num--);
			}
		}
	}
	public synchronized void show1(){
		if(num > 0){
			try{
				Thread.sleep(10);
			}
			catch(InterruptedException e){
				
			}
			System.out.println(Thread.currentThread().getName()+">>>>"+num--);
		}
	}
	
}
class Test{
	public static void main(String[] args){
		Ticket t = new Ticket();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		
		t1.start();
		try{
			Thread.sleep(10);
		}
		catch(InterruptedException e){
			
		}
		t.flag = false;
		t2.start();
	}
}
/*死锁程序2*/
class Lock implements Runnable{
	private boolean flag;
	Lock(boolean flag){
		this.flag = flag;
	}
	public void run(){
		if(flag){
			synchronized(MyLock.locka){
				System.out.println(Thread.currentThread().getName()+"if>>>locka");
				synchronized(MyLock.lockb){
					System.out.println(Thread.currentThread().getName()+"if>>>lockb");
				}
			}
		}
		else{
			synchronized(MyLock.lockb){
				System.out.println(Thread.currentThread().getName()+"else>>>lockb");
				synchronized(MyLock.locka){
					System.out.println(Thread.currentThread().getName()+"else>>>locka");
				}
			}
		}
	}
}
class MyLock{
	public static final Object locka = new Object();
	public static final Object lockb = new Object();
}
class Test{
	public static void main(String[] args){
		Lock la = new Lock(true);
		Lock lb = new Lock(false);	
		Thread t1 = new Thread(la);
		Thread t2 = new Thread(lb);	
		t1.start();
		t2.start();
	}
}
      • 线程间通信
        • 多个线程处理同一资源,但是任务不同(四个车同时往进拉煤,三个车同时往外运煤)
          • 赋值:input
          • 取值:output
          • 资源:resource
        • 线程间等待唤醒机制
          • 下边三个方法(监视器方法)都必须定义在同步中,用于操作线程状态,必须要明确操作的是哪个锁的线程。
            • wait():让线程处于冻结状态,会抛出中断异常(InterruptedException)(释放CPU执行权和执行资格,并将该线程存到该锁的线程池)
            • notify():唤醒该锁线程池中的一个任意线程(使线程具备执行资格)
            • notifyAll():唤醒该锁线程池中所有线程(使线程具备执行资格)
          • sleep与wait的异同
            • sleep必须指定时间,wait可以不指定时间
            • sleep和wait都释放了CPU执行权
            • sleep没有释放线程锁,wait释放了线程锁
          • 操作的是哪个锁,就用哪个锁的方法来处理该线程
          • 锁也叫监视器。
          • 线程操作方法放到了Object类中,是因为这些方法都是监视器(锁)的方法,任意对象都可以作为监视器(锁),故而放到里边
/*
Input和Output都需要使用同一个对象,但使用单例全局就只能有一个对象
所以可以创建好对象,然后将对象传进来。
*/
/*
线程间等待唤醒机制,适用于单生产消费模式
wait()
notify()
notifyAll()
*/
class Resource{
	String name;
	String sex;
	boolean flag = false;/*用来标记资源是否被赋值。*/
}
class Input implements Runnable{
	Resource r;
	Input(Resource r){
		this.r = r;
	}
	public void run(){
		int x = 0;
		while(true){
			synchronized(r){
				if(r.flag)
					try{
						r.wait();
					}
					catch(InterruptedException e){
						
					}
				if(x == 0){
					r.name = "Easul";
					r.sex = "male";
				}
				else{
					r.name = "王文博";
					r.sex = "女";
				}
				r.flag = true;
				r.notify();
			}
			x =(++x) % 2;
		}
	}
}
class Output implements Runnable{
	Resource r;
	Output(Resource r){
		this.r = r;
	}
	public void run(){
		while(true){
			synchronized(r){
				if(!r.flag)
					try{
						r.wait();
					}
					catch(InterruptedException e){
						
					}
				System.out.println(r.name+">>>"+r.sex);
				r.flag = false;
				r.notify();
			}
		}
	}
}
class Test{
	public static void main(String[] args){
		/*
		创建资源
		创建任务
		创建线程
		开启线程
		*/
		Resource r = new Resource();
		Input in = new Input(r);
		Output out = new Output(r);
		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);
		t1.start();
		t2.start();
	}
}
/*
将成员变量私有化之后,变量的赋值在成员方法里执行,所以应该在方法里加锁
同样把等待和唤醒拿过来就可以用了
对于变量的赋值,因为是使用this锁,所以使用同步函数
*/
class Resource{
	private String name;
	private String sex;
	private boolean flag = false;/*用来标记资源是否被赋值。*/
	public synchronized void set(String name, String sex){
		if(flag)
			try{
				this.wait();/*wait()醒了之后,往下走,不再判断flag*/
			}
			catch(InterruptedException e){
				
			}
		this.name = name;
		this.sex = sex;
		flag = true;
		this.notify();
	}
	public synchronized void out(){
		if(!flag)
			try{
				this.wait();
			}
			catch(InterruptedException e){
				
			}
		System.out.println(this.name+"<<<"+this.sex);
		flag = false;
		this.notify();
	}
}
class Input implements Runnable{
	Resource r;
	Input(Resource r){
		this.r = r;
	}
	public void run(){
		int x = 0;
		while(true){
			if(x == 0){
				r.set("Easul", "male");
			}
			else{
				r.set("王文博", "女");
			}
			x =(++x) % 2;
		}
	}
}
class Output implements Runnable{
	Resource r;
	Output(Resource r){
		this.r = r;
	}
	public void run(){
		while(true){
			r.out();
		}
	}
}
class Test{
	public static void main(String[] args){
		/*
		创建资源
		创建任务
		创建线程
		开启线程
		*/
		Resource r = new Resource();
		Input in = new Input(r);
		Output out = new Output(r);
		Thread t1 = new Thread(in);
		Thread t2 = new Thread(out);
		t1.start();
		t2.start();
	}
}

      • 多生产者,多消费者
        • 多次生产和死锁原因
          • if判断标记,只判断一次,可能导致不该运行的线程运行,数据出现错误。
          • notify只能唤醒一个线程,有很大几率无法唤醒对方线程。(加while会导致死锁)
        • 解决多次生产和死锁问题
          • while判断标记,解决了线程获取执行权后是否执行的问题。
          • notifyAll解决了本方线程一定能够唤醒对方线程的问题。(唤醒了所有线程,降低了效率,需要判断本方线程)
/*
多生产者:t0 t1
多消费者:t2 t3
假设t0先拿到CPU,生产烤鸭1,通知线程池其他线程运行(随机运行一个),线程池没有,则从阻塞队列中挑一个。自己wait()
t1拿到CPU,t1判断有烤鸭,wait()
t2拿到CPU,t2判断有烤鸭,消费烤鸭1,通知线程池其他某个线程运行(t0或t1),t0唤醒,自己wait()
t3拿到CPU,t3判断没有烤鸭,wait()
t0拿到CPU,生产烤鸭2,通知线程池其他线程运行(t1 t2或t3),t1唤醒,自己wait()
t1拿到CPU,生产烤鸭3,通知线程池其他线程运行(t0 t2或t3),自己wait()
于是有了烤鸭2和烤鸭3,烤鸭2无法消费
这里问题在于t1生产烤鸭3的时候不知道已经生产了烤鸭2,所以可以在判断一下有没有生产过(while)。
直接while,按上述步骤,最后唤醒t1,然后全部wait()
但是因为无法唤醒某个指定线程所以会死锁.只能全部唤醒。
*/
class Resource{
	private String name;
	private int count = 1;
	private boolean flag = false;
	public synchronized void set(String name){
		while(flag)/*判断自己等待后是否已经有线程做过自己做过的事情*/
			try{
				wait();
			}
			catch(InterruptedException e){	
			}
		this.name = name + count;
		count++;
		System.out.println(Thread.currentThread().getName()+"生产了"+this.name);
		this.flag = true;
		notifyAll();/*避免没人工作而唤醒所有线程*/
	}
	public synchronized void out(){
		while(!flag)
			try{
				wait();
			}
			catch(InterruptedException e){			
			}
		System.out.println(Thread.currentThread().getName()+"消费了"+this.name);
		this.flag = false;
		notifyAll();
	}
}
class Producer implements Runnable{
	private Resource r;
	Producer(Resource r){
		this.r = r;
	}
	public void run(){
		while(true)
			r.set("烤鸭");
	}
}
class Consumer implements Runnable{
	private Resource r;
	Consumer(Resource r){
		this.r = r;
	}
	public void run(){
		while(true)
			r.out();
	}
}
class Test{
	public static void main(String[] args){
		Resource r = new Resource();
		Producer p = new Producer(r);
		Consumer c = new Consumer(r);
		Thread t0 = new Thread(p);
		Thread t1 = new Thread(p);
		Thread t2 = new Thread(c);
		Thread t3 = new Thread(c);
		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}
}
        • 锁的升级
          • 可以使用java.util.concurrent.locks包中的Lock接口和Condition接口
            • Lock接口替代了synchronized,将同步和锁封装成了对象
            • Condition接口替代了Object的监视器方法(wait notify notifyAll),将其方法封装成了对象。一个Lock可以挂多个Condition。每个Condition都有一组监视器
          • Lock与synchronized的区别
            • synchronized对于锁的操作是隐式的,看不到获取与释放锁的过程
            • Lock对于锁的操作是可控的
          • Lock相关方法
            • lock()                开启锁
            • unlock()            释放锁
            • newCondition() 获取一组监视器对象,类型为Condition,同一个锁可以获取多组监视器
          • Condition相关方法
            • await()              将线程从运行到冻结
            • signal()             将线程从冻结到阻塞
            • signalAll()         将线程池中所有冻结线程恢复到阻塞
          • 上边的代码升级
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
class Resource{
	private String name;
	private int count = 1;
	private boolean flag = false;
	private Lock lock = new ReentrantLock();/*创建了锁对象,ReentrantLock是互斥锁*/
	/*通过一个锁,获取多个监视器,监视不同线程*/
	private Condition producer_con = lock.newCondition();/*获取一个锁上的监视器*/
	private Condition consumer_con = lock.newCondition();
	public void set(String name){
		lock.lock();/*获取锁*/
		try{
			while(flag)
				try{
					producer_con.await();/*等待时放到了生产者线程池,用时唤醒生产者线程池即可*/
				}
				catch(InterruptedException e){	
				}
			this.name = name + count;
			count++;
			System.out.println(Thread.currentThread().getName()+"生产了"+this.name);
			this.flag = true;
			consumer_con.signal();/*唤醒消费者监视器,只唤醒一个就可以了,提高了效率*/
		}
		finally{
			lock.unlock();/*释放锁,如果发生异常可以放到finally*/
		}
	}
	public void out(){
		lock.lock();
		try{
			while(!flag)
				try{
					consumer_con.await();
				}
				catch(InterruptedException e){			
				}
			System.out.println(Thread.currentThread().getName()+"消费了"+this.name);
			this.flag = false;
			producer_con.signal();
		}
		finally{
			lock.unlock();
		}
	}
}
class Producer implements Runnable{
	private Resource r;
	Producer(Resource r){
		this.r = r;
	}
	public void run(){
		while(true)
			r.set("烤鸭");
	}
}
class Consumer implements Runnable{
	private Resource r;
	Consumer(Resource r){
		this.r = r;
	}
	public void run(){
		while(true)
			r.out();
	}
}
class Test{
	public static void main(String[] args){
		Resource r = new Resource();
		Producer p = new Producer(r);
		Consumer c = new Consumer(r);
		Thread t0 = new Thread(p);
		Thread t1 = new Thread(p);
		Thread t2 = new Thread(c);
		Thread t3 = new Thread(c);
		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}
}
        • 同步里只能有一个线程执行,但是活着的可以不止一个
/*
如果t0 t1 t2全部调用show(),进入wait()状态,t3调用method(),然后notifyAll()
那么show()中的t0 t1 t2都会唤醒,可能会获得执行权,
但是只要没有拿到同步锁,就无法向下执行代码,
所以可以获取执行权,但是不会向下执行代码。谁有锁谁执行
*/
class Demo{
	void show{
		wait();
	}
	void method{
		wait();
		code
		notifyAll();
	}
}
      • 停止线程
        • 中断:一种冻结状态,中止正在运行的状态
        • 线程的stop()方法
        • 线程的run()方法结束
          • 任务中都有循环结构,控制循环就可以结束任务
          • 控制循环结束用标记来控制,常用。while(flag){}
          • 如果线程处于wait(冻结状态)或sleep,就无法结束线程(无法读取到标记)
            • 使用interrupt(),中断当前wait()或sleep()的状态使其具备CPU执行资格
            • 因为是强制执行,wait或sleep可能会抛出InterruptedException

实际问题

  • 买票
/*
卖票100张
4个窗口(同时卖)
*/
/*
1 用继承,需要创建四个线程,每个线程都执行100次,不符合实际
2 创建一个线程,运行四次。错误方式,一个线程只能开启一次
3 使用静态的num,但如果有多种100张票,num无法实现多种使用
4 使用实现,将任务封装,然后交给线程处理。
*/
class Ticket implements Runnable{
	private int num;/*100张票*/
	Ticket(int num){
		this.num = num;
	}
	public void sale(){/*买票的代码需要被同时使用*/
		while(true){
			if(num > 0)
				System.out.println(Thread.currentThread().getName()+">>>>"+num--);
			else
				break;
		}
	}
	public void run(){
		sale();
	}
}
class Test{
	public static void main(String[] args){
		Ticket t = new Ticket(100);/*创建线程任务对象*/
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		Thread t3 = new Thread(t);
		Thread t4 = new Thread(t);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
    • 内存解释
      • 对于这个代码来说,创建了任务对象,放到堆中,创建了四个线程,每个线程所使用的数据都是这个任务对象的数据,从而实现了数据共享,不需要静态。
    • 运行后出现的输出不规律
      • 多核CPU交替运行可能导致运算的顺序不同
      • 输出和运算顺序不一样(参考异常输出和其他线程共同运行的情况)
  • 如果错误,发生在哪一行
    • 错误在第一行,因为没有重写run()方法,相当于把接口的run()方法继承了过来,是抽象类,但是没有abstract关键字
class Demo implements Runnable{
	public void run(Thread t){
	}
}
输出1还是2
  • 输出2,因为下边是子类对象,传的任务对象相当于是Thread对象的重写的run(),但是子类对象又重写了,所以运行子类对象的run().子类为主。
new Thread(new Runnable(){
	public void run(){
		System.out.println(1);
	}
}){
	public void run(){
		System.out.println(2);
	}
}.start();

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值