一、进程与线程的概念
操作系统可以同时运行多个任务,一个任务就是一个程序,运行任务的时候,也就是在内存中运行程序的时候,就是变成了一个进程。
一个进程可以同时执行多个任务,每个任务就是线程。
这里的“同时”只是我们的感觉,进程和线程实际都是并发执行的,对于操作系统的CPU来说某一个时间点只能执行一个进程,CPU在多个进程间快速轮换执行,达到多个进程同时执行的效果。对于进程来说也是如此,线程的执行也是在多个线程之间快速轮换执行以达到多个线程同时执行的效果。
二、线程
创建线程的三种方式
1.子类继承Thread类,重写Thread类run方法,new Thread子类创建线程对象,调用线程对象的start()方法。
public class MyThread extends Thread{
public void run(){
for(int i =0 ;i<3;i++)
System.out.println(this.getName()+" "+i); // getName()该方法返回调用该方法的名字
}
public static void main(String[] args){
new MyThread().start();
}
}Thread-0 0
Thread-0 1
Thread-0 2
2.实现类实现Runnable接口,重写Runnable接口run()方法,实例化实现类并将此实例作为Thread类的target来创建线程对象,调用线程对象的start()方法。
public class MyRunnable implements Runnable{
public void run(){
for(int i = 0;i<3;i++)
System.out.println(Thread.currentThread().getName()+" "+i); //Thread.currentThread()返回当前正在执行的线程对象
}
public static void main(String[] args){
MyRunnable mr = new MyRunnable();
new Thread(mr).start();
}
}Thread-0 0
Thread-0 1
Thread-0 2
3.实现类实现Callable接口,重写Callable带有返回值的call()方法,用FutureTask包装Callable实现类的实例,将FutureTask的实例作为Thread类的target来创建线程对象,调用线程对象的start()方法
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<Integer>{
public Integer call(){
int i =0;
for(;i<3;i++)
{
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
public static void main(String[] args){
MyCallable mc = new MyCallable();
FutureTask<Integer> f = new FutureTask<Integer>(mc);
new Thread(f).start();
try{
System.out.println(f.get());
}catch(Exception e){
e.printStackTrace();
}
}
}Thread-0 0
Thread-0 1
Thread-0 2
3
创建线程的三种方式的对比
继承Thread类创建线程类,不能继承其他父类。实现类实现Runnable或者Callable接口创建线程类,还可以继承其他父类。
继承Thread类创建线程类,在run方法中可以通过this得到当前线程。实现类实现Runnable或者Callable接口在run方法中通过Thread.currentThread()方法得到当前线程。
使用子类继承Thread类创建线程,每个线程都拥有自己的资源,不能共享。使用实现类实现Ruannable接口或者Callable接口创建线程,只有一个target对象,多个线程可以共享target对象的资源。
线程的生命周期
新建(New): 实例化一个Thread类或其子类创建线程,该线程就处于新建(New)状态。
就绪(Runnable):当线程调用了start()方法后,线程就处于就绪(Runnable)状态,等待获得CPU的执行机会。
运行(Running): 处于就绪状态的线程获得了CPU的执行机会后,开始执行run方法,线程就处于运行(Running)状态。
阻塞(Blocked): 当处于运行状态的线程发生 调用sleep()方法,调用了一个阻塞式IO方法并且等待return,调用wait()方法的时候,试图获得一个对象锁但这个对象锁被其他线程持有,该线程就会进入阻塞(Blocked)状态。
当处于阻塞状态的线程发生 sleep()方法指定的时间到了、阻塞式IO方法return、线程被notify(),获得了这个对象锁, 可以解除阻塞,该线程重新进入就绪状态。
死亡(Dead):线程的run()或call()方法执行完,或抛出一个未捕获的Exception或者Error后,该线程处于死亡状态(Dead)。
控制线程的一些方法
1.join()
一个线程等待另一个调用了join()方法的线程执行完成后才能进行继续执行,比如在A线程中调用了B线程的join()方法,必须等到B线程执行完后才能继续执行A线程。
2.sleep()
调用sleep()方法,当前正在执行的线程进入阻塞状态,在睡眠时间内,该线程不会执行,如果该程序只有这一条线程,那么程序将在睡眠时间内暂停执行。
3.yield()
当前正在执行的线程调用yield()方法后线程暂停,进入就绪状态,将CPU的执行机会让给优先级相同(当前线程也有可能马上再次获取CPU的执行机会)或者更高的线程。
4. setPriority(),getPriority()
可以通过setPriority(),getPriority()来设置和得到线程的优先级,其中优先级的范围为1~10。
线程同步
线程的同步就是保证在某一时刻只能有一条线程能访问共享资源,避免多个线程同时访问共享资源而发生冲突。
线程同步的原理:java中的每个对象都有一个锁,某一时刻只有获得对象的锁的线程才能访问共享资源。
1.synchronized关键字
当一条线程执行synchronized代码块或者synchronized方法的时候,会获得一个对象锁,其他线程没有获得对象锁都不能执行synchronized代码块或者synchronized方法,在该对象上阻塞。
同步代码块:
synchronized(obj){
}
同步方法:
public synchronized void methodName(){
}
同步代码块可以指定获取某一个对象的锁,同步方法只能获取当前这个对象的锁。
释放对象锁:
同步代码块或者同步方法执行结束。
同步代码块或者同步方法遇到break,return或者未捕获的Exception,Error。
线程执行同步代码块或者同步方法,上锁的对象调用wait()方法。
线程执行同步代码块或者同步方法,调用sleep(),yield()方法不会释放锁。
2.同步锁
Lock是比synchronized更强大的线程同步机制。
ReentrantLock拥有synchronized的功能,并且还多了非块结构锁tryLock(),定时等待锁tryLock(long time,TimeUnit unit),中断锁lockInterruptibly(),公平锁new ReentrantLock(true)等功能。
ReentrantReadWriteLock允许多个读线程持有readLock,但只允许一个线程持有writeLock。
线程通信
1.使用wait()和notify()
调用某个obj的wait()方法,当前线程必须要获得这个obj的锁,因此只能在synchronized同步代码块或者synchronized同步方法内使用wait()和notify()进行线程间的通信。
wait():当前线程进入阻塞状态,释放对象的锁。
notify():唤醒一个正在wait该对象的锁的线程,该线程进入就绪状态。被唤醒的线程不会立刻执行,它需要等当前线程释放锁后,获得CPU的执行机会并且再次获得对象的锁才能继续执行wait()方法之后的代码。
2.使用Condition
使用Condition进行线程间的通信,Lock相当于同步方法或者同步代码块的作用,Condition相当于obj作用,通过Condition cond = lock.newCondition()将Condition和Lock联系起来。await()相当于wait()的作用,signal相当于notify()的作用。