线程与进程
2018-09-18 06:36:37来源:博客园 阅读 ()
什么是进程:
所谓进程(process)就是一块包含了某些资源的内存区域。操作系统利用进程把它的工作划分为一些功能单元。进程中所包含的一个或多个执行单元称为线程(thread)。进程还拥有一个私有的虚拟地址空间,该空间仅能被它所包含的线程访问。线程只能归属于一个进程并且它只能访问该进程所拥有的资源。当操作系统创建一个进程后,该进程会自动申请一个名为主线程或首要线程的线程。
什么是线程:
一个线程是进程的一个顺序执行流。同类的多个线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的堆栈。线程在切换时负荷小,因此,线程也被称为轻负荷进程。一个进程中可以包含多个线程。线程是程序执行的最小程序单元。
进程与线程的区别 :
一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存。线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个执行控制。从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。
什么时候使用到线程:
线程通常用于在一个程序中需要同时完成多个任务的情况。例如在游戏中我们会听到某些背景音乐,某个角色在移动,出现某些绚丽的动画效果等。多个线程在一起执行,进而就是我们说的多线程。
线程的优先级:
每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
线程的API 中展示的调用setproperty方法来设置线程的优先级:
线程优先级默认值 5,从1到10逐渐提高 .
线程的生命周期:
上面的图可以看出,线程的大概生命周期同时对于几种状态,哪几种呢?
1. 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t =new MyThread();
2. 就绪状态(Runnable):当调用线程对象的start()方法线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了start()此线程立即就会执行。就绪状态是线程进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中。
3. 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。
4. 阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。
5. 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
根据线程阻塞产生的原因不同,我们又可以将阻塞的状态分为三种:
1. 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态
2. 同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态
3. 其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
说了那么多 ,那 我们如何产生一个线程呢?
线程的产生有两种方法:
1. 继承线程 Thread 类
public class ThreadB extends Thread{ @Override public void run() { while(true){ System.out.println("this is B"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
2 . 实现Runnable 接口
1 public class ThreadB implements Runnable{ 2 @Override 3 public void run() { 4 while(true){ 5 System.out.println("this is B"); 6 try { 7 Thread.sleep(100); 8 } catch (InterruptedException e) { 9 e.printStackTrace(); 10 } 11 } 12 } 13 }
线程的开启方式:
public static void main(String[] args){
//继承Thread类的方法,直接开启线程 new ThreadA().start(); //实现Runnable的方法。new 一个线程类对象,传入new 的线程对象 再开启线程
通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
new Thread(new ThreadA()).start(); }
这里要注意了: 因为 Java 是单继承的,在我们日常开发中,一般会选择实现Runnable 接口的方式去开启线程, 避免无法继承其他类的情况。
线程安全问题:
多个线程访问出现的延迟和线程的随机性,会导致我们线程出现一系列问题,在理想情况下,线程安全问题不会存在,不容易出现,但是一旦出现,对我们软件的影响是非常大的。当多个线程并发读写同一个临界资源比如 多线程共享的实例变量,以及多线程共享的静态公共变量等 的时候会发生“线程并发安全问题”,由此我们产生了同步(synchronized)
操作。
同步操作有几种,介绍几种常用的方法:
1 . 同步方法
2. 同步代码块
3. 重入锁
同步的前提是 : 同步需要两个或两个以上的线程,多个线程使用的是同一个锁。 不满足这两个,就不能称为同步。
当然同步也不是万能的 ,它也有弊端: 比如当线程非常多的时候,因为每个线程都会去判断同步上的锁,这是很耗费资源的, 无形中会降低程序的执行效率。
Synchronized 用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。每个Java对象都可以用做一个实现同步的锁,线程进入同步代码块之前会自动获得锁,并且在退出同步代码块时释放锁。
同步方法:synchronized 加在方法上面。
1 public synchronized 返回值类型 方法名(参数列表){ 2 // 逻辑代码 3 }
同步代码块:synchronized 加在方法体之中,需要执行同步的代码块前面。
Synchronized(同步监视器){ //需要进行同步的逻辑代码块 }
重入锁 :
Java中的锁框架指的是java.util.concurrent.locks这个包里的,不同于对象的内置加锁同步以及java.lang.Object的等待/通知机制,包含锁框架的并发工具通过轮询锁、限时等待及其他方式改善了这种机制。 这里的锁指的是接口Lock。
类ReentranLock实现了接口Lock,这是一个可重入的互斥锁,这个锁是和一个持有量相关联的。当一条线程持有这个锁并且调用lock()、lockUnitinterruptibly()或者任意一个tryLock()方法重新获取锁,这个持有量就递增1。当线程调用unlock()方法,持有量就递减1。当持有量为0时,锁就会被释放。
1 Lock lock = new ReentrantLock();// 实例化接口
2 lock.lock();
3 try {
4 //使用锁获取到的资源
5 } catch (Exception e) {
6 //异常处理
7 } finally {
8 lock.unlock();
9 }
既然说到了锁 。我们就来谈谈 基本锁概念;
内置锁:
- java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
- java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
对象锁与类锁:
java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,
- 对象锁是用于对象实例方法,或者一个对象实例上的
- 类锁是用于类的静态方法或者一个类的class对象上的。
我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的。
守护线程:
用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当VM检测仅剩一个守护线程,而用户线程都已经退出运行时,VM就会退出,因为没有如果没有了被守护这,也就没有继续运行程序的必要了。如果有非守护线程仍然存活,VM就不会退出。
守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。用户可以用Thread的setDaemon(true)方法设置当前线程为守护线程。
虽然守护线程可能非常有用,但必须小心确保其他所有非守护线程消亡时,不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。 因此,不要在守护线程中执行业务逻辑操作(比如对数据的读写等)。
与用户线程的不同点:
当进程结束时,所有正在运行的守护线程都会被强制结束。而一个进程中所有前台线程都结束,进程就会结束
另外有几点需要注意:
1、setDaemon(true)必须在调用线程的start()方法之前设置,否则会抛出IllegalThreadStateException异常。
2、在守护线程中产生的新线程也是守护线程。
3、不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。
试想既然我们一个进程有多个线程组成,当我们组成执行这个程序时, 多个进程协调执行,如果进程需要其他进程的执行结果或者参数怎么办呢? 这就涉及到我们进程间的通讯了。
进程间的通讯有以下几种:
-
同步 即 多个线程通过synchronized关键字这种方式来实现线程间的通信。
依旧 举个栗子:
1 public class Demo { 2 3 synchronized public void methodA() { 4 //逻辑代码块 5 } 6 7 synchronized public void methodB() { 8 //逻辑代码块 9 } 10 } 11 12 public class ThreadA extends Thread { 13 14 private Demo demo; 15 //构造方法 16 @Override 17 public void run() { 18 super.run(); 19 demo.methodA(); 20 } 21 } 22 23 public class ThreadB extends Thread { 24 25 private Demo demo; 26 //构造方法 27 @Override 28 public void run() { 29 super.run(); 30 demo.methodB(); 31 } 32 } 33 34 public class Run { 35 public static void main(String[] args) { 36 Demo demo = new Demo(); 37 38 //线程A与线程B 持有的是同一个对象:demo 39 ThreadA one = new ThreadA(object); 40 ThreadB two = new ThreadB(object); 41 one.start(); 42 two.start(); 43 } 44 }
线程A和线程B持有同一个Demo类的对象demo,这两个线程需要调用不同的方法,但是它们是同步执行的,所以:线程B需要等待线程A执行完了methodA()方法之后,它才能执行methodB()方法。这样,线程A和线程B就实现了通信。
这种方式,本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(即对象,便获得了访问权限),谁就可以执行。
-
while轮询的方式 不停地通过while语句询问条件是否成立
还是举个栗子:
public class Test { private List<String> list = new ArrayList<String>(); public void add() { list.add("elements"); } public int size() { return list.size(); } } public class ThreadA extends Thread { private Test test; public ThreadA(Test test) { super(); this.test = test; } @Override public void run() { try { for (int i = 0; i < 10; i++) { test.add(); System.out.println(ThreadA.currentThread()+ "添加了" + (i + 1) + "个元素"); Thread.sleep(1000); } } catch (InterruptedException e) { e.printStackTrace(); } } } public class ThreadB extends Thread { private Test test; public ThreadB(Test test) { super(); this.test = test; } @Override public void run() { try { while (true) { if (test.size() == 5) { System.out.println("==5, 线程b准备退出了"); throw new InterruptedException(); } } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Run{ public static void main(String[] args) { Test service = new Test(); ThreadA a = new ThreadA(service); a.setName("A"); a.start(); ThreadB b = new ThreadB(service); b.setName("B"); b.start(); } }
线程A不断地改变条件,线程ThreadB不停地通过while语句检测这个条件(list.size()==5)是否成立 ,从而实现了线程间的通信。但是这种方式会浪费CPU资源。之所以说它浪费资源,是因为JVM调度器将CPU交给线程B执行时,它没做啥“有用”的工作,只是在不断地测试 某个条件是否成立。就类似于现实生活中,某个人一直看着手机屏幕是否有电话来了,而不是: 在干别的事情,当有电话来时,响铃通知TA电话来了。
这种方式还存在另外一个问题:
轮询的条件的可见性问题,关于内存可见性问题。线程都是先把变量读取到本地线程栈空间,然后再去再去修改的本地变量。因此,如果线程B每次都在取本地的 条件变量,那么尽管另外一个线程已经改变了轮询的条件,它也察觉不到,这样也会造成死循环。
-
wait/notify机制
import java.util.ArrayList; import java.util.List; public class MyList { private static List<String> list = new ArrayList<String>(); public static void add() { list.add("anyString"); } public static int size() { return list.size(); } } public class ThreadA extends Thread { private Object lock; public ThreadA(Object lock) { super(); this.lock = lock; } @Override public void run() { try { synchronized (lock) { if (MyList.size() != 5) { System.out.println("wait begin " + System.currentTimeMillis()); lock.wait(); System.out.println("wait end " + System.currentTimeMillis()); } } } catch (InterruptedException e) { e.printStackTrace(); } } } public class ThreadB extends Thread { private Object lock; public ThreadB(Object lock) { super(); this.lock = lock; } @Override public void run() { try { synchronized (lock) { for (int i = 0; i < 10; i++) { MyList.add(); if (MyList.size() == 5) { lock.notify(); System.out.println("已经发出了通知"); } System.out.println("添加了" + (i + 1) + "个元素!"); Thread.sleep(1000); } } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Run { public static void main(String[] args) { try { Object lock = new Object(); ThreadA a = new ThreadA(lock); a.start(); Thread.sleep(50); ThreadB b = new ThreadB(lock); b.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
线程A要等待某个条件满足时(list.size()==5),才执行操作。线程B则向list中添加元素,改变list 的size。
A,B之间如何通信的呢?
也就是说,线程A如何知道 list.size() 已经为5了呢? 这里用到了Object类的 wait() 和 notify() 方法。 当条件未满足时(list.size() !=5),线程A调用wait() 放弃CPU,并进入阻塞状态。---不像②while轮询那样占用CPU 当条件满足时,线程B调用 notify()通知 线程A,所谓通知线程A,就是唤醒线程A,并让它进入可运行状态。 这种方式的一个好处就是CPU的利用率提高了。 但是也有一些缺点:比如,线程B先执行,一下子添加了5个元素并调用了notify()发送了通知,而此时线程A还执行;当线程A执行并调用wait()时,那它永远就不可能被唤醒了。因为,线程B已经发了通知了,以后不再发通知了。这说明:通知过早,会打乱程序的执行逻辑。
-
管道通信就是使用java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信
具体就不介绍了。分布式系统中说的两种通信机制:共享内存机制和消息通信机制。感觉前面的①中的synchronized关键字和②中的while轮询 “属于” 共享内存机制,由于是轮询的条件使用了volatile关键字修饰时,这就表示它们通过判断这个“共享的条件变量“是否改变了,来实现进程间的交流。 而管道通信,更像消息传递机制,也就是说:通过管道,将一个线程中的消息发送给另一个。
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 5月到6月程序员到底经历了和什么,工资狂跌***元,你是否也 2020-06-10
- 为什么阿里巴巴Java开发手册中不允许魔法值出现在代码中? 2020-06-09
- 计算机基础到底是哪些基础?为什么很重要! 2020-06-08
- 最详细的java多线程教程来了 2020-06-08
- 系统化学习多线程(一) 2020-06-08
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash