浅谈java中的线程,多线程

卑微小吴励志写博客第三天

在这里插入图片描述

多线程

1 什么是线程,线程与进程的关系

线程是程序的执行路径,或者可以说是程序的控制单元。
一个进程可能包含一个或多个线程,当一个进程存在多条执行路径时,就可以将该执行方式称为多线程。
线程的执行方式大致可分为就绪(wait),执行(run),阻塞(block)三个状态,而三个状态的转换实质上是在抢夺cpu资源过程中造成的,正常情况下cpu资源不会被线程独自占用,因此多个线程在运行中相互抢夺资源,造成线程在上述的三个状态之间不断的相互转换。而这也是多线程的执行方式。

2 同步和异步的区别

同步(Sync)
所谓同步,就是发出一个功能调用时,在没有得到结果之前,该调用就不返回或继续执行后续操作。
根据这个定义,Java中所有方法都是同步调用,应为必须要等到结果后才会继续执行。我们在说同步、异步的时候,一般而言是特指那些需要其他端协作或者需要一定时间完成的任务。
简单来说,同步就是必须一件一件事做,等前一件做完了才能做下一件事。
异步(Async)
异步与同步相对,当一个异步过程调用发出后,调用者在没有得到结果之前,就可以继续执行后续操作。当这个调用完成后,一般通过状态、通知和回调来通知调用者。对于异步调用,调用的返回并不受调用者控制。

举个例子简单说明下两者的区别:
同步:火车站多个窗口卖火车票,假设A窗口当卖第288张时,在这个短暂的过程中,其他窗口都不能卖这张票,也不能继续往下卖,必须这张票处理完其他窗口才能继续卖票。直白点说就是当你看见程序里出现synchronized这个关键字,将任务锁起来,当某个线程进来时,不能让其他线程继续进来,那就代表是同步了。

异步:当我们用手机下载某个视频时,我们大多数人都不会一直等着这个视频下载完,而是在下载的过程看看手机里的其他东西,比如用qq或者是微信聊聊天,这种的就是异步,你执行你的,我执行我的,互不干扰。比如上面卖火车票,如果多个窗口之间互不影响,我行我素,A窗口卖到第288张了,B窗口不管A窗口,自己也卖第288张票,那显然会出错了

3 创建线程的三种方式

  • 继承Thread类,重写父类run()方法
public class MyThread extends Thread{

    @Override
   public void run() {
   	// TODO Auto-generated method stub
   	//super.run();
   	 doSomething();
   }

   private void doSomething() {
   	// TODO Auto-generated method stub
   	System.out.println("我是一个线程中的方法");
   }
}
  • 实现runnable接口
public class RunnableThread implements Runnable{

   @Override
   public void run() {
   	// TODO Auto-generated method stub
   	doSomeThing();
   }

   private void doSomeThing() {
   	// TODO Auto-generated method stub
   	System.out.println("我是一个线程方法");
   }

}
  • 使用ExecutorService、Callable、Future实现有返回结果的多线程(JDK5.0以后)
public class CallableThread implements Callable<String>{
 
	@Override
	public String call() throws Exception {
		// TODO Auto-generated method stub
		doSomeThing();
		return "需要返回的值";
	}
 
	private void doSomeThing() {
		// TODO Auto-generated method stub
		System.out.println("我是线程中的方法");
	}
 
}
3.1 采用实现Runnable、Callable接口的方式创建多线程的优缺点。

优势:

  • 线程类只是实现了Runnable接口与Callable接口,还可以继承其他类。
  • 在这种方式下,多个线程可以共享一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

劣势:

  • 编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。
3.2 采用继承Thread类的方法创建多线程的优缺点。

优势:

  • 编写简单,如果需要访问当前线程,则无须使用Thread.currentThread()方法,直接使用this即可获得当前线程。

劣势:

  • 因为线程类已经继承了Thread类,所以不能再继承其他父类。

总结:一般推荐采用实现Runnable接口、Callable接口的方式来创建多线程。

4 线程池

4.1 线程池的作用

线程池作用就是限制系统中执行线程的数量。
根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程 排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待为什么要用线程池:。当一个新任务需要运行时,如果线程 池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

4.2为什么要用线程池

减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务
可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务
器累趴下(每个线程需要大约 1MB 内存,线程开的越多,消耗的内存也就越大,最后死机)

4.3 java中的四种线程池
  • newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor
    创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

5 多线程同步机制。

  • 在需要同步的方法的方法签名中加入synchronized关键字。
  • 使用synchronized块对需要进行同步的代码段进行同步。
  • 使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象。
    一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在 java里边就是拿到某个同步对象的锁(一个对象只有一把锁); 如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池 等待队列中)。 取到锁后,他就开始执行同步代码(被synchronized修饰的代码);线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中 等待的某个线程就可以拿到锁执行同步代码了。这样就保证了同步代码在统一时刻只有一个线程在执行。

6 线程的生命周期

线程在执行过程中,可以处于下面几种状态:
就绪(Runnable):线程准备运行,不一定立马就能开始执行。
运行中(Running):进程正在执行线程的代码。
等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束。
睡眠中(Sleeping):线程被强制睡眠。
I/O阻塞(Blocked on I/O):等待I/O操作完成。
同步阻塞(Blocked on Synchronization):等待获取锁。
死亡(Dead):线程完成了执行。
在这里插入图片描述

7 什么是守护线程?

守护线程(即daemon thread),是个服务线程,准确地来说就是服务其他的线程,这是它的作用——而其他的线程只有一种,那就是用户线程。所以java里线程分2种,
1、守护线程,比如垃圾回收线程,就是最典型的守护线程。

守护线程,专门用于服务其他的线程,如果其他的线程(即用户自定义线程)都执行完毕,连main线程也执行完毕,那么jvm就会退出(即停止运行)——此时,连jvm都停止运行了,守护线程当然也就停止执行了。
再换一种说法,如果有用户自定义线程存在的话,jvm就不会退出——此时,守护线程也不能退出,也就是它还要运行,干嘛呢,就是为了执行垃圾回收的任务啊。

2、用户线程,就是应用程序里的自定义线程。

应用程序里的线程,一般都是用户自定义线程。
用户也可以在应用程序代码自定义守护线程,只需要调用Thread类的设置方法设置一下即可

8 线程锁

多线程可以同时运行多个任务
但是当多个线程同时访问共享数据时,可能导致数据不同步,甚至错误!
so,不使用线程锁, 可能导致错误。
应用场景:
I/O密集型操作 需要资源保持同步
优缺点:
优点:保证资源同步
缺点:有等待肯定会慢

9 sleep方法和wait方法的区别?

  • 来自不同的类: wait()方法是Object类的方法,sleep方法是Thread类的方法。
  • 对于锁的占用情况不同:最主要是sleep方法没有释放锁,而 wait 方法释放了锁,使得其他线程可以使用同步控制块或者方法。
  • 使用范围: wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用)
  • 唤醒方式:调用sleep()方法的线程通常是睡眠一定时间后,自动醒来。对象调用wait()方法,必须采用notify()或者notifyAll()方法唤醒。
Sleep()方法

Sleep()方法属于Thread类中方法,表示让一个线程进入睡眠状态,等待一定的时间之后,自动醒来进入到可运行状态,不会马上进入运行状态,因为线程调度机制恢复线程的运行也需要时间,一个线程对象调用了sleep方法之后,并不会释放他所持有的所有对象锁,所以也就不会影响其他进程对象的运行。但在sleep的过程中有可能被其他对象调用它的interrupt(),产生InterruptedException异常,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。
注意sleep()方法是一个静态方法,也就是说他只对当前对象有效,通过t.sleep()让t对象进入sleep,这样的做法是错误的,它只会是使当前线程被sleep 而不是t线程。

Wait()方法

Wait()属于Object的成员方法,一旦一个对象调用了wait方法,必须要采用notify()和notifyAll()方法唤醒该进程;如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()方法的对象。wait()方法也同样会在wait的过程中有可能被其他对象调用interrupt()方法而产生InterruptedException,效果以及处理方式同sleep()方法。

10 什么是死锁(deadlock)?如何避免死锁?

两个进程都在等待对方执行完毕才能继续往下执行的时候就发生了死锁。结果就是两个进程都陷入了无限的等待中。
使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。

关于多线程与锁的一些知识,确实很难。每次看到这里都没弄的太懂,不过多看一些关于多线程的知识后还是有一些进步的。希望这篇博客能帮助小伙伴对线程,多线程有一定的了解。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值