菜鸟先飞之JAVA_多线程

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Happy_cloudlife/article/details/78248292
多线程的概述
线程是程序执行的一条路径, 一个进程中可以包含多条线程,多线程并发执行可以提高程序的效率, 可以同时完成多项工作。
并发和并行的区别,并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU);并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。

多线程的实现方式
1)继承Thread,该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例
/*
 * 开启线程的第一种方法,通过继承Thread,来开启线程。
 * 1、定义类继承Thread
 * 2、重写run方法
 * 3、把线程要做的事情写在run方法中
 * 4、创建线程对象
 * 5、开启新线程,内部会自动执行run方法
 * 
 */
public class demo02_thread {

	public static void main(String[] args) {
		// 4、创建自定义的线程对象
		MyThread mt = new MyThread();
		// 5、开启线程
		mt.start();
	}

}

// 1、定义一个类继承Thread
class MyThread extends Thread {

	// 2、重写run方法
	@Override
	public void run() {
		// 3、将要执行的代码,写在run方法中
		for (int i = 0; i < 1000; i++) {
			System.out.println("aaaaaaaa");
		}
	}

}

2)实现 Runnable 接口。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。
/*
 * 开启线程的第二种方法,通过实现Runnable方法。
 * 1、定义类实现Runnable接口
 * 2、实现run方法
 * 3、把新线程要做的事写在run方法中
 * 4、创建自定义的Runnable的子类对象
 * 5、创建Thread对象, 传入Runnable
 * 6、调用start()开启新线程, 内部会自动调用Runnable的run()方法
 */
public class demo03_thread {

	public static void main(String[] args) {
		// 4、创建自定义的Runnable的子类对象
		MyRunnable mr = new MyRunnable();
		// 5、创建Thread对象, 传入Runnable的子对象
		Thread mt = new Thread(mr);
		// 6、开启线程
		mt.start();
		
	}

}

// 1、定义类实现Runnable接口
class MyRunnable implements Runnable {
	// 2、实现run方法
	@Override
	public void run() {
		// 3、把新线程要做的事写在run方法中
		for (int i = 0; i < 1000; i++) {
			System.out.println("aaaaaaa");
		}
	}

}

实现Runnable的原理,看Thread类的构造函数,传递了Runnable接口的引用,通过init()方法找到传递的target给成员变量的target赋值,查看run方法,发现run方法中有判断,如果target不为null就会调用Runnable接口子类对象的run方法。
3)两种方式的区别
a)继承Thread : 由于子类重写了Thread类的run(),当调用start()时, 直接找子类的run()方法。
b)实现Runnable : 构造函数中传入了Runnable的引用, 成员变量记住了它, start()调用run()方法时内部判断成员变量Runnable的引用是否为空, 不为空编译时看的是Runnable的run(),运行时执行的是子类的run()方法。
c)继承Thread:好处是,可以直接使用Thread类中的方法,代码简单;弊端是,如果已经有了父类,就不能用这种方法。
d)实现Runnable接口:好处是,即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的;弊端是,不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂。

Thread类的成员方法
public final String getName();返回该线程的名称。
public final void setName(String name);改变线程名称,使之与参数 name 相同。
public static Thread currentThread();返回对当前正在执行的线程对象的引用。
public static void sleep(long millis)throws InterruptedException;在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。该线程不丢失任何监视器的所属权。
public final void setDaemon(boolean on);将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。
public final void join()throws InterruptedException;等待该线程终止。
public final void join(long millis)throws InterruptedException;等待该线程终止的时间最长为 millis 毫秒。超时为 0 意味着要一直等下去。
public static void yield();暂停当前正在执行的线程对象,并执行其他线程。
public final void setPriority(int newPriority);更改线程的优先级。

同步代码块和同步锁
当多线程并发,有多段代码同时执行时, 我们希望某一段代码执行的过程中CPU不要切换到其他线程工作。这时就需要同步。如果两段代码是同步的,那么同一时间只能执行一段,在一段代码没执行结束之前,不会执行另外一段代码。使用synchronized关键字加上一个锁对象来定义一段代码, 这就叫同步代码块;使用synchronized关键字修饰一个方法, 该方法中所有的代码都是同步的。多个同步代码块如果使用相同的锁对象, 那么他们就是同步的。
class Printer {
	public static void print1() {
		//锁对象可以是任意对象,但是被锁的代码需要保证是同一把锁,不能用匿名对象
		synchronized(Printer.class){				
			System.out.print("H");
			System.out.print("E");
			System.out.print("L");
			System.out.print("L");
			System.out.print("O");
			System.out.print("\r\n");
		}
	}
	/*
	 * 非静态同步函数的锁是:this
	 * 静态的同步函数的锁是:字节码对象
	 */
	public static synchronized void print2() {	
		System.out.print("W");
		System.out.print("O");
		System.out.print("R");
		System.out.print("D");
		System.out.print("\r\n");
	}
}

两个线程间的通信
什么时候需要通信,当多个线程并发执行时,在默认情况下CPU是随机切换线程的,如果我们希望他们有规律的执行,就可以使用通信,例如每个线程执行一次打印。
怎么通信,如果希望线程等待,就调用wait(),如果希望唤醒等待的线程,就调用notify();这两个方法必须在同步代码中执行,并且使用同步锁对象来调用。
Object类中的方法:
1)public final void wait()throws InterruptedException;在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
2)public final void notify();唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。
3)public final void notifyAll();唤醒在此对象监视器上等待的所有线程。

线程组的概述和使用
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 class demo21_ThreadGroup {

	public static void main(String[] args) {
//		demo1();
		
		//创建线程组
		ThreadGroup tg = new ThreadGroup("线程组");
		//创建Runnable的子类对象
		MyRunnable mr = new MyRunnable();
		//创建线程实例
		Thread t1 = new Thread(tg, mr, "啊啊啊");
		Thread t2 = new Thread(tg, mr,"bbb");
		//打印线程组的名字
		System.out.println(t1.getThreadGroup().getName());
		System.out.println(t2.getThreadGroup().getName());
		
		
	}

	public static void demo1() {
		//创建Runnable的子类对象
		MyRunnable1 mr = new MyRunnable1();
		//创建线程实例
		Thread t1 = new Thread(mr,"qq");
		Thread t2 = new Thread(mr,"ww");
		
		ThreadGroup tg1 = t1.getThreadGroup();
		ThreadGroup tg2 = t2.getThreadGroup();
		
		System.out.println(tg1.getName());
		System.out.println(tg2.getName());
	}

}

class MyRunnable1 implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 1000; i++) {
			System.out.println(Thread.currentThread().getName()+"..."+i);
		}
	}
	
}

线程的五种状态


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

	public static void main(String[] args) {
		
		//创建线程池
		ExecutorService pool = Executors.newFixedThreadPool(2);
		
		//将线程放入线程池并执行
		pool.submit(new MyRunnable1());
		pool.submit(new MyRunnable1());
		//关闭线程池
		pool.shutdown();
	}

}



猜你喜欢

转载自blog.csdn.net/Happy_cloudlife/article/details/78248292