线程
一.什么是进程?什么是线程?
**进程(Process)**是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
**线程(thread)**是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call
stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
一个进程可以有很多线程,每条线程并行执行不同的任务。
CPU轮询:CPU轮询是指的在一个时间周期内,CPU会访问执行每个就绪的线程,有于CPU的计算速度非常快,所以看起来就像是多个线程同时进行.
二.线程
1.分类
系统级线程:又称核心级线程,负责管理调度不同进程之间的多个线程,由操作系统直接管理。
用户级线程:仅存于用户空间,在应用程序中控制其创建,执行和消亡。
2.守护线程
守护线程是特殊的线程,一般用于在后台为其他线程提供服务。
例如:我们启动一个杀毒软件360,然后同时在启动一个守护进程。监控360杀毒程序,如果这个程序被关闭,则自动重新启动这个程序。
3.线程死锁
死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。
4.线程的状态
三.线程的创建
1.继承thread类
注意:
1.重写其中的run()方法,这个方法没有返回值,并且不能处理异常.
2.新建线程传入我们创建的这个类的实例对象为target参数,表示执行目标对象中的run方法.
3.一般我们创建自己的线程任务类就去继承Tread,因为这个类有很多的方法,我们可以用.
public class TicketThread extends Thread{
//这是全局变量,让所有的线程可以共享这个成员变量
private int ticket = 30;
@Override
public void run() {
//while(true)循环能快速的,让OS系统调度本线程
while(true) {
//加上一个同步锁,使同步锁内的代码,要么执行完,要么一行代码都不执行
//this代表的调用本方法的对象
synchronized (this) {
if(ticket <= 0) {
break;
}
//因为线程睡眠会出现异常,但是又不能抛出异常,所以这里我们自己捕捉异常
try {
//主动让线程运行慢一点,好看结果一点,再一个是线程运行速度过快
//避免让一个线程直接抢完票了
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//输出与卖出票的窗口,顺便该窗口说一声余票还有多少张.
System.out.println("窗口:" + Thread.currentThread().getName()
+ " 余票:" + ticket--);
}
}
}
public static void main(String[] args) {
Thread target = new TicketThread();
//创建一个新的线程,把我们写的线程的对象作为参数传入到新线程的构造方法中去
//此构造方法的原理是对同一个线程做操作.及操作参数线程的所有的东西.
//相当于将一个线程分成了3个线程来执行.
new Thread(target).start();//新建的线程执行target目标线程的run方法
new Thread(target).start();
new Thread(target).start();
}
}
2.实现Runnable接口
注意:
1.重写其中的run()方法,这个方法没有返回值,并且不能处理异常
2.我们实现runnable接口其实就是实现了线程的接口,但是我们不常用这个,因为这个相对于Thread类少了很多提供的方法.
public class TestRunnable implements Runnable{
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
//调用Runnable,创建一个Runnable的对象.
Runnable target = new TestRunnable();
for(int i =0;i < 10 ; i ++) {
//调用Thread的带参数构造方法,参数是Runnable的实现类.
new Thread(target,"窗口" + (i+1)).start();
}
}
}
3.实现Callable接口
注意:
1.重写其中call()方法,这个方法可以接受返回值,和处理异常
2.如果想要获取线程的返回值,则需要用一个中间对象Future对象去包装一下目标线程对象.
//创建一个TestCallable对象实现Callable接口,类型为String类型
public class TestCallable implements Callable<String>{
//重写call方法
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
return Thread.currentThread().getName() + "我执行完了";
}
public static void main(String[] args) throws Exception {
//创建一个Callable的对象.
Callable callable = new TestCallable();
//将Callable的对象包装成FutureTask<String>的对象,
//这个对象有个get()方法,可以获取call()方法的返回值
FutureTask<String> ft = new FutureTask<String>(callable);
//创建一个线程,并让它准备好.
new Thread(ft).start();
//调用ft的get()方法,获取返回值.
System.out.println(ft.get());
}
}
4.利用线程池的方式
注意:
1.创建一个线程池.
2.submit(new runnable())向线程池提交一个线程执行任务.
public class TestThreadPool {
public static void main(String[] args) throws Exception {
//创建一个线程池es,通过Excutors类方法,生成10个线程的线程池
ExecutorService es = Executors.newFixedThreadPool(10);
//运行线程池中的线程,参数是一个Runnable接口类型的参数
//这里用匿名内部类的形式定义了一个Runnable的实现子类.
es.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
//调用Callable类型的参数,submit创建一个带有返回值的线程,该线程是Callable类型的
//下面的这个方法的返回值是Future类型的,线程的返回值就封装在这个Future对象中
Future<String> f = es.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName());
return Thread.currentThread().getName() + "我执行完了e";
}
});
//调用Future对象的get()方法,获取Callable线程Call()方法的返回值.
System.out.println(f.get());
//关闭线程池
es.shutdown();
}
}
四.线程的方法
1.Sleep()
阻塞线程,但是不会释放线程锁
2.Wait()-notify()
这两个方法在Object类定义的.阻塞线程,但是放弃对象.唤醒阻塞
3.Join()
join 多线程线程,a线程join b线程,表示a线程必须得等b线程先执行完才结束.这主要是线程排序的.
4.Start()
线程启动进入就绪状态.
5.Yeild()
暂停当前方法,释放自己拥有的CPU,线程进入就绪状态。它能让当前线程由“运行状态”进入到“就绪状态”
面试问题:
1.sleep()和wait()之间的区别.
a.sleep()是Thread的方法,二wait()是Object类定义的方法.
b.sleep()不释放锁,wait()放弃锁.
2.java中为什么要把wait方法定义在Object类中而不是线程中呢?
sleep()是睡眠当前线程,wait()是对当前对象放弃锁,因为锁可能加在任何的对象上,所以sleep()方法在Object类中定义.