进程和线程
进程
在计算机中,我们运行的每一个应用程序都可以看做一个进程,它是计算机中某个数据集合的一次运行活动。以单核cpu来讲,cpu在同一时间只能完成一个任务,也就是同一时间内只能执行一个进程。
线程
一个进程在运行过程中又包括很多个线程,这些线程共同完成一个任务(大概可以理解为把大问题化小逐个解决)。比如食堂卖饭(目的是卖完当天的库存),而食堂有很多个窗口麦不同的东西,但是他们访问的都是同样的库存(多个线程协同完成任务)。
线程的状态
一个线程的一生包括以下状态:
1.初始态:线程刚创建时的状态
2.就绪态:执行start()方法后线程进入就绪态
3.运行态:当cpu分配时间片给该线程时即进入运行台
4.阻塞态:当该线程的时间片结束时,线程会进入阻塞态,随后接触阻塞进入就绪态等待下一个时间片
5.销毁:线程执行完毕后销毁
线程的创建和启动
在java中,主要有以下几种创建线程的方式:
1.实现Runnable接口并实现其中的run方法
2.继承Thread类并重写其中的run方法
3.实现Callable接口
4.使用ExecutorService,Callable等相关接口
实际上这其中最常用的就是前两个Runnable和Thread,这里就只讲下这两种的用法(其实是我暂时只学到在这两种):
1.实现Runnable接口(实现run方法)
public class MyThread implements Runnable{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(name+":--->"+i);
}
}
public static void main(String[] args) {
MyThread mt1 = new MyThread("t1");
MyThread mt2 = new MyThread("t2");
//创建线程对象
Thread t1 = new Thread(mt1);
Thread t2 = new Thread(mt2);
//线程启动
t1.start();
t2.start();
}
}
2.继承Thread类(重写run方法)
public class MyThread2 extends Thread{
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(this.getName()+"---->"+i);
}
}
public static void main(String[] args) {
MyThread2 mt1 = new MyThread2();
MyThread2 mt2 = new MyThread2();
mt1.start();
mt2.start();
}
}
这两个本质上区别不大,反倒是用Runnable的时候启动线程时反而还要稍微麻烦一点点。但是Runnable的优势在于实现Runnable的同事还可以实现其他接口或者继承一个其他的类。
线程的终止
在线程执行过程中,我们往往需要在它达成特定条件后就将其终止,这就涉及到了以下两种方法:
1.标记中断法
2.异常中断法
标记中断法
其原理是声明一个标记变量(类型一般是整形或布尔型吧),在线程达到目的是将标记设置成可终止状态,然后用return终止进程:
public class ThreadEnd extends Thread{
@Override
public void run() {
boolean isOver = false;
int i = 0;
while(!isOver){
System.out.println(this.getName()+"-->"+i);
i++;
if(i >= 50000){
isOver = true;
return;
}
}
System.out.println("确定结束了么?这是线程内部的输出!");
}
public static void main(String[] args) {
ThreadEnd te = new ThreadEnd();
te.start();
}
}
异常中断法
这种方法就像名字一样,在满足条件后抛出异常终止进程
public class ThreadEnd2 extends Thread{
@Override
public void run() {
try {
for (int i = 0; i < 1000000; i++) {
System.out.println(this.getName()+"-->"+i);
if(this.isInterrupted()){
throw new InterruptedException("当前线程被中断!!!");
}
}
} catch (InterruptedException e) {
System.out.println("线程终止");
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
ThreadEnd2 te2 = new ThreadEnd2();
te2.start();
Thread.sleep(2000);
//中断线程
te2.interrupt();
}
}
另外Thread中的stop()方法也可以终止进程,但是这种方法因为不安全已经淘汰了,不建议使用。
守护线程
守护线程就是为其他线程提供服务的线程,也可用于监视某一线程的进度之类的,而且主进程结束时守护进程也会随之结束。
public class BackService extends Thread{
@Override
public void run() {
int i = 1;
while(true){
try {
System.out.println("后台线程(守护线程)正在执行:"+i);
i++;
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
主线程:
public class ThreadDeamon extends Thread{
@Override
public void run() {
//创建线程对象
BackService bs = new BackService();
//设置当前线程为守护线程
bs.setDaemon(true);
bs.start();
try {
for (int i = 0; i < 1000; i++) {
sleep(2);
System.out.println("执行线程---->"+i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new ThreadDeamon().start();
}
}
多线程编程
在实际应用中,通常会遇到两个或多个线程要对同一条数据进行修改,例如银行取钱,两个地方用同一账号取钱,账号上只有10000而两个进程并发都要取10000,如果不做处理的话两边都会取到10000,而银行反亏10000,这极不合理。因此在遇到这一类问题时,通常通过synchronized给两个线程共享的缓冲区上锁,这样在一个线程占用资源时另一个线程会进入等待状态,等到前一个线程释放资源时再调用资源,这样就能解决多线程并发问题。
private Object obj;
private String name;
public WaitThread(Object obj, String name) {
super();
this.obj = obj;
this.name = name;
}
@Override
public void run() {
System.out.println(name+"进入run...");
//CAS锁(乐观锁)
//悲观锁
synchronized(obj){
try {
System.out.println(name + "等待...");
//只有当前线程拥有指定对象的锁(对象监视器)才能调用wait方法,并且wait一旦执行
//该对象的锁就会被释放
obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(name+"执行结束...");
}