JAVA基础——多线程(二)计时器,多线程之间的通信,线程组,线程池的概念

一、 Runtime类

Runtime类是一个单例类。我们可以进行查看源码可以看见,Runtime类就是运用饿汉式的方法进行实现。
//可以执行一些字符串命令,就是字cmd中的一些命令

public static void main(String[] args) throws IOException {
    
    
		//获取运行时对象
		Runtime runtime=Runtime.getRuntime();
		//可以执行一些字符串命令,就是字cmd中的一些命令
		//r.exec("shutdown -s -t 300");		
//300秒后关机
		runtime.exec("shutdown -a");				
//取消关机
	}

二、 Timer类:计时器

应用场景:在指定时间安排我们执行什么事情。一种工具,线程用安排以后在后台线程中执行的任务,可安排任务执行一次,或者定期重复执行。比如闹钟。

public static void main(String[] args) throws InterruptedException {
    
    
		Timer timer=new Timer();
//在指定时间安排指定任务,第一个参数是安排的任务,
//第二个参数是执行的时间,第三个参数是过多长时间再重复执行
		timer.schedule(new MyTimerTask(), new Date(120,10,2,16,52,10),3000);
		while (true) {
    
    
			Thread.sleep(1000);
			System.out.println(new Date());
		}
	}
class MyTimerTask extends TimerTask{
    
    
	@Override
	public void run() {
    
    
		// TODO Auto-generated method stub
		System.out.println("吃饭");
	}
}

效果和倒计时一样,会在固定的时间。执行一次吃饭任务。

三、 两个线程间的通信

  1. 什么时候需要通信
  • 多个线程并发执行时, 在默认情况下CPU是随机切换线程的
  • 如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
  1. 怎么通信
  • 如果希望线程等待, 就调用wait()
  • 如果希望唤醒等待的线程, 就调用notify();
  • 这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用

案例:我们想要将两个线程,进行两个方法隔行打印。所以我们需要一个等待唤醒机制

public static void main(String[] args) {
    
    
		final Printer printer =new Printer();	
		new Thread() {
    
    
			public void run() {
    
    
				while (true) {
    
    
					try {
    
    
						printer.print1();
					} catch (InterruptedException e) {
    
    
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}.start();
		
		new Thread() {
    
    
			public void run() {
    
    
				while (true) {
    
    
					try {
    
    
						printer.print2();
					} catch (InterruptedException e) {
    
    
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}.start();
	}
class Printer{
    
    
	private int flag = 1;
	public void print1() throws InterruptedException{
    
    	
		synchronized(this) {
    
     
			if (flag !=1) {
    
    			//
				this.wait();
			}		
			System.out.print("我");
			System.out.print("是");
			System.out.print("小");
			System.out.print("学");
			System.out.print("生");
			System.out.print("\r\n");	
			flag =2;
			this.notify();			//随机唤醒单个等待线程
		}
	}
	public  void print2() throws InterruptedException{
    
    	
		synchronized (this){
    
    
			if (flag !=2) {
    
    
				this.wait();
			}
			System.out.print("我");
			System.out.print("是");
			System.out.print("大");
			System.out.print("学");
			System.out.print("生");
			System.out.print("\r\n");
			flag =1;
			this.notify();
		}	
	}
	}

效果如下:
在这里插入图片描述
解释:this.wait()表示该线程进行等待去执行其他线程。this.notify()表示该线程唤醒。我们设置一个flag 一开始它的值为1。那么当它不等于1的时候就等待。但是它等于1.所以就进行了:我是小学生的操作。随后我们改变了flag值。这时候cpu是随机分配线程的。当进入我是小学生这个线程是。flag 并不是1。进入if判断,从而导致该线程执行了wait()操作。

四、 三个或三个以上间的线程通信

多个线程通信的问题

  • notify()方法是随机唤醒一个线程
  • notifyAll()方法是唤醒所有线程
  • JDK5之前无法唤醒指定的一个线程
  • 如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件
public static void main(String[] args) {
    
    
		final Printer2 printer2 =new Printer2();
		
		new Thread() {
    
    
			public void run() {
    
    
				while (true) {
    
    
					try {
    
    
						printer2.print1();
					} catch (InterruptedException e) {
    
    
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}.start();
		
		new Thread() {
    
    
			public void run() {
    
    
				while (true) {
    
    
					try {
    
    
						printer2.print2();
					} catch (InterruptedException e) {
    
    
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}.start();
		
		new Thread() {
    
    
			public void run() {
    
    
				while (true) {
    
    
					try {
    
    
						printer2.print3();
					} catch (InterruptedException e) {
    
    
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}.start();
	}

class Printer2{
    
    
	private int flag =1;
	public void print1() throws InterruptedException{
    
    	
		synchronized(this) {
    
     
			while (flag !=1) {
    
    			//
				this.wait();
			}
		
			System.out.print("我");
			System.out.print("是");
			System.out.print("小");
			System.out.print("学");
			System.out.print("生");
			System.out.print("\r\n");	
			flag =2;
			this.notifyAll();			//随机唤醒单个等待线程
		}
	}
	public  void print2() throws InterruptedException{
    
    	
		synchronized (this){
    
    
			while (flag !=2) {
    
    
				this.wait();
			}
			System.out.print("我");
			System.out.print("是");
			System.out.print("大");
			System.out.print("学");
			System.out.print("生");
			System.out.print("\r\n");
			flag =3;
			this.notifyAll();
		}	
	}
	
	public  void print3() throws InterruptedException{
    
    	
		synchronized (this){
    
    
			while (flag !=3) {
    
    
				this.wait();
			}
			System.out.print("大");
			System.out.print("家");
			System.out.print("都");
			System.out.print("是");
			System.out.print("小");
			System.out.print("学");
			System.out.print("生");
			System.out.print("\r\n");
			flag =1;
			this.notifyAll();
		}	
	}
	}

TIPS:

  1. 在同步代码块中,用哪个对象锁,就用哪个对象调用wait方法

  2. 为什么wait方法和notify方法定义在Object这类中?
    因为锁对象可以是任意对象,Object是所有的类的基类,所以wait方法和notify方法需要定义在Object这个类中

  3. sleep方法和wait方法有什么区别?
    sleep方法必须传入参数,参数就是时间,时间到了自动醒来。wait方法可以传入参数,也可以不传入参数。传入参数就是在参数的时间结束后等待。不传入参数就直接等待。sleep方法在同步函数或同步代码块中,不释放锁。wait方法在同步函数或者同步代码块中,释放锁

五、 JDK1.5的新特性互斥锁(主要是替换synchronized)

  1. 同步
  • 使用ReentrantLock类的lock()和unlock()方法进行同步
  1. 通信
  • 使用ReentrantLock类的newCondition()方法可以获取Condition对象
  • 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
  • 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了
class Printer3{
    
    
	private ReentrantLock reentrantLock =new ReentrantLock();
	private int flag =1;
	private Condition c1=reentrantLock.newCondition();
	private Condition c2=reentrantLock.newCondition();
	private Condition c3=reentrantLock.newCondition();
	public void print1() throws InterruptedException{
    
    	
		reentrantLock.lock();			//获取锁
			if (flag !=1) {
    
    			
				c1.await();
			}
		
			System.out.print("我");
			System.out.print("是");
			System.out.print("小");
			System.out.print("学");
			System.out.print("生");
			System.out.print("\r\n");	
			flag =2;					//随机唤醒单个等待线程
			c2.signal();              	
			reentrantLock.unlock();		//释放锁
		}
	
	public  void print2() throws InterruptedException{
    
    	
			reentrantLock.lock();
			if (flag !=2) {
    
    
				c2.await();
			}
			System.out.print("我");
			System.out.print("是");
			System.out.print("大");
			System.out.print("学");
			System.out.print("生");
			System.out.print("\r\n");
			flag =3;
			c3.signal();
		reentrantLock.unlock();
	}
	
	public  void print3() throws InterruptedException{
    
    	
			reentrantLock.lock();
			if (flag !=3) {
    
    
				c3.await();
			}
			System.out.print("大");
			System.out.print("家");
			System.out.print("都");
			System.out.print("是");
			System.out.print("小");
			System.out.print("学");
			System.out.print("生");
			System.out.print("\r\n");
			flag =1;
			c1.signal();
		reentrantLock.unlock();	
	}
	}

六、 线程组的概述和使用

线程组概述

  • Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。
  • 默认情况下,所有的线程都属于主线程组。
  • public final ThreadGroup getThreadGroup()//通过线程对象获取他所属于的组
  • public final String getName()//通过线程组对象获取他组的名字
  • 我们也可以给线程设置分组
    • 1,ThreadGroup(String name) 创建线程组对象并给其赋值名字
    • 2,创建线程对象
    • 3,Thread(ThreadGroup?group, Runnable?target, String?name)
    • 4,设置整组的优先级或者守护线程
public static void main(String[] args) {
    
    
		// TODO Auto-generated method stub
		//自己设置线程组
		ThreadGroup tg= new ThreadGroup("我是一个新的线程组");		//创建新的线程组
		MyRunnable myRunnable =new MyRunnable();			
//创建runnable的子类对象
		Thread t1=new Thread(tg,myRunnable,"张三");			//将线程t1放在组中
		Thread t2=new Thread(tg,myRunnable,"李四");			//将线程t2放在组中
		
		System.out.println(t1.getThreadGroup().getName());	//获取组名
		System.out.println(t2.getThreadGroup().getName());	
		tg.setDaemon(true);
	}
MyRunnable myRunnable =new MyRunnable();
		Thread t1 =new Thread(myRunnable,"张三");
		Thread t2 =new Thread(myRunnable,"李四");
		
		ThreadGroup tg1=t1.getThreadGroup();		
//默认是主线程
		ThreadGroup tg2=t2.getThreadGroup();
		
		System.out.println(tg1.getName());
		System.out.println(tg2.getName());

七、 线程池的概述和使用

  1. 线程池概述
    程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池
  2. 内置线程池的使用概述:
    JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
    • public static ExecutorService newFixedThreadPool(int nThreads)
    • public static ExecutorService newSingleThreadExecutor()
    • 这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法
    • Future<?> submit(Runnable task)
      • Future submit(Callable task)
  3. 使用步骤:
  • 创建线程池对象
  • 创建Runnable实例
  • 提交Runnable实例
  • 关闭线程池
ExecutorService pool =Executors.newFixedThreadPool(2);	//创建线程池
		pool.submit(new MyRunnable());
		pool.submit(new MyRunnable());
		
		pool.shutdown();

八、 多线程程序实现的方式3

// 创建线程池对象
ExecutorService pool = Executors.newFixedThreadPool(2);

// 可以执行Runnable对象或者Callable对象代表的线程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));
				// V get()
				Integer i1 = f1.get();
				Integer i2 = f2.get();

				System.out.println(i1);
				System.out.println(i2);

				// 结束
				pool.shutdown();
	}
class MyCallable implements Callable<Integer>{
    
    
	private int number;
	
	public MyCallable(int number) {
    
    
		this.number = number;
	}

	@Override
	public Integer call() throws Exception {
    
    
		int sum = 0;
		for (int x = 1; x <= number; x++) {
    
    
			sum += x;
		}
		return sum;
	}
}

猜你喜欢

转载自blog.csdn.net/Mr_GYF/article/details/108918485