黑马程序员 多线程

本文介绍了Java多线程的创建方式,包括继承Thread类和实现Runnable接口,并对比了两者的区别。讨论了多线程运行时可能出现的安全问题,提出了同步代码块和同步函数作为解决方案,并介绍了JDK1.5后的Lock操作作为更高效的多线程管理方式。此外,还提到了死锁、等待唤醒机制以及线程的结束方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 -------  android培训java培训、期待与您交流! ----------

 

 线程总结


      java的特点之一就是内置对多线程的支持。多线程允许同时完成多个任务,使人产生多个任务在同时执行的错觉。其实目前是计算机的处理器在同一时刻只能执行一个线程,但处理器可以在不同的线程之间快速切换,切换速度远远超过人们接受信息的速度,所以感觉好像多个任务在同时执行。而c++没有内置多线程机制,必须调用底层的操作系统的多线程功能来进行多线程程序设计。
      一、java中线程创建有两种方式:
            1、直接继承thread类。具体步骤如下:
                  A、定义类继承thread类;
                  B、复写Thread类中的run方法
                  C、调用线程的start方法(该方法的作用是启动线程,调用run方法)
            2、实现Runable接口。具体步骤如下:
                  A、定义类实现Runable接口;
                  B、覆盖Runable接口中的run方法;
                  C、通过Thread类创建线程对象;
                  D、将Runable接口的子类对象作为实际参数传递给THread类的构造函数;
                  E、调用Thread类的start方法开启线程并调用Runable接口子类的run方法。
       那实现和继承方法有什么区别呢?
       不难发现,实现一般比继承好。实现方式避免了java单继承的局限性。如果一个类继承了Thread类,那么它再也不能继承其他类了,而实现方式却可以再继承其他类。
       二、多线程运行时会出现安全问题。问题的原因在于当多条语句操作同一线程共享数据时,一个线程对多条语句只执行了一部分,还没执行完,而另一个线程参与进来执行,导致共享数据的错误。解决方法就是让多条操作共享数据的语句,只能让一个线程都执行完了,再执行其他线程。java对于多线程的安全问题提供了专业的解决方式。那就是同步代码块或者同步函数以及jdk1.5中提供升级解决方案:Lock操作
       1、同步代码块格式:
             synchronized(对象)
            { 
                  需要被同步的代码;

            }
       2、同步函数:
           public synchronized void fangfa()
            {
                  被同步的代码;
            }
       关于锁:synchronized后括号中的“对象”就是此同步代码块的锁。而函数需要被对象调用,那么函数都有一个所属对象引用,这就是this,所以同步函数使用的锁是this。如果同步函数被静态修饰后,使用的锁是什么呢?肯定不是this了,因为静态方法中不可以定义this。静态进内存时,内存中没有了累对象,但是一定有该类对应的字节码文件对象:类名.class。该对象的类型是class。因此静态同步函数使用的锁就是该方法所在类的字节码文件对象:类名.class。

       同步的前提:
         A、必须要有两个或两个以上的线程;
         B、必须是多个线程使用同一个锁;
         C、必须保证同步中只能有一个线程在运行;
       同步解决了多线程的安全问题,但多线程需要判断锁,较为消耗资源。
       JDK1.5中提供了多线程升级解决方案,将同步代码synchronized替换成了Lock操作;将object中的wait、notify、notifyAll,替换成了codition对象,该对象可以通过Lock锁进行获取。
       三、死锁:同步中嵌套同步,而锁却不同。
       四、等待唤醒机制:

        wait();

        notify();

        notifyAll();

       这些方法都使用在同步中,因为要对持有的监视器(锁)的线程操作,只有同步才具有锁。等待和唤醒必须是同一个锁。

           五、线程结束。查阅API知sotp方法已过时。停止线程只有一种——run方法结束。

实例:

public class CallableTest {
	public static void main(String[] args) {
		// 创建Callable对象
		CallableThread ct = new CallableThread();
		// 使用FutureTask来包装Callable对象
		FutureTask<Integer> task = new FutureTask<Integer>(ct);
		for (int i = 0; i < 100; i++) {
			System.out
					.println(Thread.currentThread().getName() + "   i的值:" + i);
			if (i == 20) {
				// 实质还是以Callable对象来创建、并启动线程
				new Thread(task, "callable线程").start();
			}
		}
		try {
			// 获取线程返回值
			System.out.println("子线程的返回值:" + task.get());
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}
}
class CallableThread implements Callable<Integer> {
	// 实现call方法,作为线程的执行体
	@Override
	public Integer call() throws Exception {
		// TODO Auto-generated method stub
		int i = 0;
		for (; i < 100; i++) {
			System.out
					.println(Thread.currentThread().getName() + "   i的值:" + i);
		}
		return i;
	}
}

线程池
线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象传给线程池,线程池就会启动一条线程来执行该对象的run方法,当run方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run方法。


使用线程池可以有效地控制系统中并发线程的数量,当系统中包含大量并发线程时,会导致系统性能急剧下降,甚至导致虚拟机崩溃。
使用Executors工厂类产生线程池,该类的静态方法如下
(1)newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。
(2)newFixedThreadPool(int  nThreads):创建一个可重用、具有固定线程数的线程池。
(3)newSingleThreadExecutor():创建一个只有单线程的线程池,相当于newFixedThreadPool(1)。
(4)newScheduledThreadPool(int   corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。corePoolSize指池中保存的线程数,即使线程是空闲的也被保存在线程池内。
(5)newSingleThreadScheduledExecutor():创建只有一条线程的线程池,它可以在指定延迟后执行线程任务。
上面五个方法中前三个方法返回一个ExecutorService对象,该对象代表一个线程池,它可以执行Runnable对象和Callable对象所代表的线程。而后两个方法返回一个ScheduledExecutorService线程池,它是ExecutorService的子类,它可以在指定延迟后执行线程任务。
ExecutorService提供如下三个方法
(1)Future<?>  submit(Runnable  task):将一个Runnable对象提交给指定的线程池。线程池将在有空闲线程时执行Runnable对象代表的任务。
(2)<T>   Future<T>  submit(Runnable task,T  result):将一个Runnable对象提交给指定的线程池,线程池将在有空闲线程时执行Runnable对象代表的任务,rusult显示指定线程执行结束后的返回值。
(3)<T>   Future<T>  submit(Callable<T>  task):将一个Callable对象提交给指定的线程池。线程池将在有空闲线程时执行Callable对象代表的任务,Future代表Callable对象里call方法的返回值。
ScheduledExecutorService代表可在指定延迟或周期性执行线程任务的线程池。提供如下四个方法:
(1)ScheduledFuture<V>  schedule(Callable<V>  callable,long  delay, TimeUnit  unit):指定callable任务将在delay延迟后执行。
(2)ScheduledFuture<?>  schedule(Runnable command,long  delay, TimeUnit  unit):指定command任务将在delay延迟后执行。
(3)ScheduledFuture<?>  scheduleAtFixedRate(Runnable commed,long  initialDelay,long period,  TimeUnit  unit):指定command任务将在delay延迟后执行,而且以设定频率重复执行,也就是说,在initialDelay后开始执行,依次在initialDelay+period、initialDelay+2*period、.....处重复执行,以此类推。
(4)ScheduledFuture<?>  scheduleWithFixedDelay(Runnable  commed,long initialDelay,long  delay,TimeUnit  unit) :创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每次执行终止和下一次执行开始之间都存在给定的延迟。如果任务的任一次执行时遇到异常,就会取消后续执行。否则,只能通过程序来显示取消或终止来终止该任务。
当用完一个线程池后,应该调用该线程池的shutdown()方法,该方法将启动线程池关闭序列,调用了shutdown方法后的线程池不再结束新任务,但会将以前所有已经提交任务执行完成。当线程池中的所有任务都执行完成后,池中所有线程都会死亡;另外也可以调用线程池的shutdownNow()方法来关闭线程池,该方法试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
总结使用线程池来执行线程任务步骤:
(1)调用Executors累的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。
(2)创建Runnable实现类或Callable实现类的实例,作为线程执行任务。
(3)调用ExecutorService对象的submit方法来提交Runnable实例或Callable实例。
(4)当不想提交任何任务时,调用ExecutorService对象的shutdown方法来关闭线程池。

public class ThreadTest {
	public static void main(String[] args) {
		// 创建一个具有固定线程数6的线程池
		ExecutorService pool = Executors.newFixedThreadPool(6);
		// 向线程池中提交两个线程
		pool.submit(new TestThread());
		pool.submit(new TestThread());
		// 关闭线程池
		pool.shutdown();
	}
}

// Runnale接口来实现一个简单的线程类
class TestThread implements Runnable {
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + "的i值为=" + i);
		}
	}
}



  ------- android培训java培训、期待与您交流! ----------

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值