2.什么是线程? 进程的一部分,进程中实际的任务执行者,必须依附于进程。线程对进程的依赖主要体现在:
线程不能脱离进程开启,必须在进程开启的前提下开启。
线程有时必须从进程中获取数据。
3.线程与进程的区别? 线程与进程是两个相对的概念,一个对象相对于它拥有的执行单位被称为进程,从自身所属的上级执行者来看,又被称作线程。
4.多线程的设计目的、用途、意义 cup在任何一个时间点都只能执行一个线程,多线程的本质是多个任务高速交替执行。如果多个线程间不存在数据交换,可以单独执行,采用多线程并不能减少总的执行时间。
多线程设计的主要目的不是为了提高执行速度,而是相对平均地执行每一个线程,不致使某一个线程长时间持有cpu时间片,其他线程长时间处于等待状态。由于cpu时间片在多个线程间切换迅速,超出了人类感官所能察觉的范围,所以感觉多个任务都是执行。
例如,当多个人访问同一个网站,每一个人都需要5分钟,如果不采用多线程,同时只允许一个人进入网站,其他多数人都要等待5分钟,用户体验很差。这是采用多线程,一个人进入以后,cpu转向其他用户,让其他用户陆续进入,用户体验就提高了,尽管总的执行时间并没有减少。
5.cpu调度模式分时调度模式:系统平均地为各个线程分配cpu时间片。
抢占式调度模式:各个线程抢夺cpu时间片,cpu时间片在线程间不均匀分配。
二 线程创建 1.java se api 提供了两种创建线程的方式:实现runnable接口,将实现类的对象作为参数传入thread的构造器中。
直接继承thread类。
2.无论采用哪种方式,需要执行的任务都必须放在run方法中。 3.两种创建方式的区别:⑴java采用单继承,即一个类只能继承一个父类,而允许一个类实现多个接口,采用继承thread的方式创建线程,就使本类失去了唯一的一次继承机会。
⑵实现资源共享的方式不同 首先需要明确的一点,通过继承thread创建线程的方式也可以实现资源共享,只是由于通过new关键字创建的多个线程是不同的对象,那么共享资源只能来自于外部,通常通过构造器注入。
而通过实现runnable接口的方式创建线程,可以利用同一个实现类对象创建多个线程,实现了资源共享,共享资源来自线程内部。
4.采用实现runnable接口的方式创建线程,不仅保留了唯一的继承机会,而且在实现资源共享的操作相对简单,所以一般采用该方式创建线程。
5.通过继承thread的方式实现资源共享:提供共享资源的外部类
package com.test.thread.extendsthread;public class myclass {public int count; }
thread线程子类
package com.test.thread.extendsthread;public class mythread extends thread {private myclass obj;public mythread() {super(); }public mythread(myclass obj) {super();this.obj = obj; } @overridepublic void run() { system.out.println(obj= + obj);while (true) {synchronized (obj) {if (obj.count > 0) {try { thread.sleep(1000); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(thread.currentthread().getname() + ----当前数量= + obj.count--); } elsereturn; } } } }
测试类
package com.test.thread.extendsthread;import org.junit.test;import com.test.thread.synchronizedtest.demo02.mytestrunnable;import net.sourceforge.groboutils.junit.v1.multithreadedtestrunner;import net.sourceforge.groboutils.junit.v1.testrunnable;public class threadextendstest {/** * junit单元测试不支持多线程测试,使用groboutils进行多线程测试(导入架包) * * @throws throwable */@testpublic void test01() throws throwable { myclass obj = new myclass(); obj.count = 10; mythread myth01 = new mythread(obj); mythread myth02 = new mythread(obj); mythread myth03 = new mythread(obj); mytestrunnable t01 = new mytestrunnable(myth01); mytestrunnable t02 = new mytestrunnable(myth02); mytestrunnable t03 = new mytestrunnable(myth03); testrunnable[] tr = new testrunnable[3]; tr[0] = t01; tr[1] = t02; tr[2] = t03; multithreadedtestrunner mttr = new multithreadedtestrunner(tr); mttr.runtestrunnables(); }// 放在主线程中测试public static void main(string[] args) { myclass obj = new myclass(); obj.count = 10; mythread t01 = new mythread(obj); mythread t02 = new mythread(obj); mythread t03 = new mythread(obj); t01.setname(t01); t02.setname(t02); t03.setname(t03); t01.start(); t02.start(); t03.start(); } }
三 线程生命周期 1.什么是线程的生命周期? 由不同阶段构成的线程从出生到死亡的整个过程,叫做线程的生命周期。
2.线程生命周期的意义 了解线程的生命周期能够更好地掌握线程的运行情况,比如线程的就绪状态,意味着不是调用start方法之后,线程立即执行。
3.生命周期的几个阶段:出生状态:线程创建完成,尚未开启前的状态。
就绪状态:调用start方法开启线程,线程尚未运行的状态。
运行状态:线程获取cpu时间片执行时的状态。
休眠状态:线程调用sleep方法后进入指定时长的休眠状态,时间结束进入就绪状态。
等待状态:监听对象在线程内部调用wait方法后,线程失去对象锁,进入等待状态。
阻塞状态:线程发出输入或者输出请求后进入阻塞状态。
死亡状态:run方法执行完毕,线程死亡。
四 线程的加入 一个线程a在另一个线程b内部调用join方法,b线程中止,a线程开始执行,a线程执行完毕,b线程才开始执行。
五 线程优先级 线程优先级设定了线程获取cpu时间片的概率,仅仅是一种概率,不能保证优先级高的线程一定优先获得cpu时间片。
线程优先级分为10个等级,从1-10,数值越大,优先级越高,通过setproprity(int)方法设置。
六 线程礼让 thread.yield,线程礼让只是通知当前线程可以将资源礼让给其他线程,并不能保证当前线程一定让出资源。
七 同步机制 1.什么是线程同步机制? 使得同一资源同一时刻只能有一个线程访问的安全机制,即一个线程访问完毕,其他线程才能访问。
2.同步机制的目的 由于目标资源同一时刻只有一个线程访问,解决了线程安全问题。
3.什么是线程安全问题?⑴线程安全问题产生条件多线程并发访问。
存在可修改的共享数据。
⑵第一个线程获取了共享数据,操作结束前,第二个线程修改了该数据,导致第一个线程运算时采用的不是获取时的数据。 4.同步机制解决线程安全问题的原理synchronized(共享对象){ 修改共享数据的代码 }
上述操作给修改共享数据的代码加了一把对象锁。任何一个对象只有一把对象锁,线程只有获得了对象锁才能访问加锁的资源。一个线程获取了对象锁,执行加锁的代码,执行完毕,归还对象锁,其他线程开始争夺对象锁,访问资源。
5.类锁 synchronized关键字加到静态方法上时,形成类锁,执行该方法上必须获取类锁。
类锁与对象锁是两种不同的锁,允许一个线程持有类锁,另一个线程持有对象锁。
6.synchronized关键字 synchronized关键字加在成员方法,该方法成为同步成员方法,由于一个对象只有一把对象锁,一个线程访问了一个同步成员方法,其他线程不能访问其他同步成员方法。
同步方法不可以被继承,同步方法在子类中失去同步机制。
7.判断条件的设置 在同步机制中,如果同步代码的执行需要满足一定条件,那么将判断条件放在锁内,保证当前获取了锁的线程在执行同步代码时满足执行条件。如果放在锁外,有可能出现当前线程获取了锁以后不满足执行条件的情况。
不存在线程安全问题的做法:public void run() { system.out.println(obj= + obj);while (true) {synchronized (obj) {if (obj.count > 0) {try { thread.sleep(1000); } catch (interruptedexception e) { e.printstacktrace(); } system.out.println(thread.currentthread().getname() + ----当前数量= + obj.count--); } elsereturn; } } }
如果将判断条件obj.count>0放在while语句中,可能出现某个线程进入while语句时count为1,满足条件,进入,等待获取对象锁。当前持有对象锁的线程执行完毕,count变为0,等待线程获取对象锁,在count=0的情况下执行同步块,判断条件失效。
八 死锁 1.什么是死锁? 线程a需要多把锁,线程b持有a缺少的锁,缺少a持有的锁,由于线程在获取到全部的锁之前不会释放持有的锁,这使得线程a与线程b陷入僵持,整个进程处于停滞状态。
2.怎么避免死锁? 减少同步机制中锁的数目,尽量避免同一把锁出现在多处。
九 守护线程 1.用户线程? 一般情况下创建的线程都是用户线程,即该线程未被显式设定为守护线程,未在守护线程内部创建。
2.主线程属于用户线程。 3.什么是守护线程? 运行在后台、为用户线程提供服务的线程。
4.守护线程创建 用户线程调用setdaemon(true)方法,或者在守护线程内部创建线程。
5.守护线程的作用 守护线程用于为用户线程提供服务,如垃圾回收器。
6.jvm在所有的用户线程执行完毕后终止,无论此时守护线程是否执行完毕。 7.守护线程运行在后台,所有用户线程结束后,自动结束。十 wait与sleep方法对比 1.存在范围wait方法是object级的,即java中的任何一个对象都拥有该方法,像tostring一样。
sleep方法只在thread及其子类中存在。
2.作用sleep使当前线程休眠,释放cpu时间片,不会释放持有的锁。
wait用于线程间通信,由对象管理所有以该对象为锁的全部线程。在同步代码中由锁对象调用,使当前线程释放持有的对象锁。
3.使用方法sleep方法是一个静态方法,直接通过thread调用,thread.sleep。
用在同步代码中,由锁对象调用。
4.相关方法obj.notify():随机唤醒对象监听器上的一个线程,该线程进入就绪状态,一旦获得对象锁与cpu时间片,从等待处接着执行,不是重新进入run方法或者同步代码中。
obj.notifyall():唤醒对象监听器上所有的等待线程,使它们全部进入就绪状态。
十一 threadlocal 1.线程局部变量,为每一个线程提供一个变量的副本,使得各个线程相对独立地操作变量,避免线程安全问题。
2.首先必须明确一点,threadlocal.get()获取的变量副本必须手动传入:
threadlocal.set(object obj)
初次获取时,判断线程局部变量中是否保存有变量副本,如果没有则手动传入,在该线程中下次获取的就是初次传入的对象。
3.threadlocal的目的是保证在一个线程内部,一次创建,多次获取。
4.基本原理:
将初次传入的变量与线程绑定,线程不变,变量不变。
十二 groboutils多线程测试 1.junit测试不支持多线程,groboutils提供了对多线程测试的支持,使用时需要导入架包。 2.几个比较重要的类:testrunnable:实现了runnable接口,run方法中运行的是runtest方法,runtest方法是一个抽象方法。
multithreadedtestrunner:负责管理并开启多个线程。
3.测试步骤⑴继承testrunnable,实现其中的抽象方法runtest,将需要运行的代码放入该方法中。通常为子类定义一个有参构造方法,方法形参为需要测试的线程,在runtest方法中调用测试线程的run方法,从而将将需要执行的代码注入runtest方法中。
⑵创建测试线程数组,将需要测试的testrunnable实现类传入其中:testrunnable[] tr=new testrunnable[len];
⑶根据测试线程数组创建线程管理与运行对象并开启多线程:multithreadedtestrunner mttr=new multithreadedtestrunner(tr); mttr.runtestrunnables();
以上就是什么是进程与线程?的详细内容。
