什么是进程:进程就是某种意义上相互隔离、独立运行的程序。
什么是线程:线程就是进程执行的过程中的一个执行流程,一个线程可以由多个进程组成。他们可以分别执行不同的任务。多个线程同时运行称为并发。
两者的区别:
1、每个进程都有独立的代码和存储空间,进程的切换开销较大。
2、线程没有对立的代码和存储空间,和所属的进程中的其他线程共享代码和存储空间。但是每个线程都有独立的运行栈和程序计数器,线程的切换开销较小。
3、多进程—在操作系统中同时运行多个程序。
4、多线程—在一个进程中有多个线程同时执行。
1、线程的创建和启动
1.1、Thread类
Thread代表线程类,主要有两个方法。一个是run()方法,用于执行程序启动时所执行的代码。另一个是start()方法,用于启动线程。
创建线程类只需要继承Thread类,并覆盖run()方法即可。一个线程只能被启动一次。
public class MyThread extends Thread{
@Override
public void run(){
System.out.println("我是被创建的线程");
}
}
1.2、Runnable接口
由于java是单继承,继承Thread后就不能继承其他类。故提供了java.lang.Runnable接口。
public class MyThread implements Runnable{
@Override
public void run{
System.out.println("我是Runnable接口的实现类的run方法");
}
}
public static void main(String arg[]){
MyThread m1=new MyThread();//先创建Runnable实现类的对象
Thread t1=new Thread(m1);//创建线程将m1作为参数传入
t1.start;//启动线程
}
1.3、Callable接口
Callable接口用起来比较繁琐,但是可以提供线程的返回值。通过创建Callable的实现类重写call方法,在调用FutureTask方法将实现类传入。最后创建一个线程传入FutureTask对象才可以启动线程。启动线程后,通过FutureTask对象的get方法可以获取到创建的线程的返回值。
class MyThread implements Callable<Integer>{
@Override
public Integer call() throws Exception{
int sum=0;
for(int i=1;i<=100;i++){
sum+=i;
}
return sum;
}
public static void main(String args[]){
MyThread m1=new MyThread();
FutureTask<Integer> task=new FutureTask<>(m1);
Thread t1=new Thread(task);
t1.start;
int sum=task.get();
System.out.print(sum);
}
}
2、线程的状态转换
新建状态(New):创建一个线程,在堆中为其分配内存。
就绪状态(Runnable):当线程处于新建状态时,其他线程调用当前线程的start方式,该线程就进入了就绪状态,Java虚拟机会为其创建方法调用栈和程序计数器。处于这个状态的线程位于可运行池中,等待获取cup使用权。
运行状态(Running):当线程处于就绪状态且占用到cpu使用权,执行程序代码。在并发环境中,如果计算机只有一个cup,那么任何时刻只会有一个线程处于运行状态。如果计算机有多个cpu,可以同时分配不同的线程占用不同的cpu,使他们都处于运行状态。只有处于就绪状态的线程才有机会运转到运行状态。
阻塞状态(Blocked):当线程由于某些原因放弃cpu,暂停运行。当线程处于阻塞状态时,Java虚拟机不会为其分配cpu,直到其重新进入就绪状态后才有机会进入运行状态。
如果执行了wait,Java虚拟机会把线程放入等待池中。
当试图获得其他线程的同步锁时,如果已经被其他线程占用,会把线程放进锁池中。
当执行打印或读取方法时,会发出I/O请求,进入阻塞。处理完毕,恢复运行。
死亡状态(Dead):当程序退出run方法时,就进入死亡状态,表示该线程的生命周期结束。无论是否是正常死亡都不会影响其他线程的运行。
3、线程的调度
调度模式分两种:分时调度模式和抢占式调度模式。
分时式调度:平均分配,轮流获得cpu时间片。
抢占式调度:优先级高的先运行,优先级相同随机选择。
调整各个线程的优先级(Priority)
所有处于就绪状态的线程根据优先级放入可运行池中,优先级高获得较高的运行几率。通过setPriority和getPriority设置和获取优先级。
优先级最高为10,最低为1。
线程睡眠Thread.sleep()
当一个线程执行了sleep方法时,他就会放弃cpu,转到阻塞状态。线程的睡眠方法必须设置时间参数,当时间到了就会自动从阻塞状态转到就绪状态。如果采用了同步机制,当线程执行了sleep方法后不会释放掉锁。
线程等待wait()和唤醒notify()
这个方法与线程睡眠相似,用起来很麻烦。通常情况下会在线程定义时通过标记位的方式使其等待,可以通过设置等待的时间,当时间到了就会自动从阻塞状态转到就绪状态。也可以通过notify()或者notifyAll()方法将其唤醒。与sleep不同的是,同步机制下当线程执行了wait()方法后,会释放同步锁。
线程让步Thread.yield()
对于相同优先级的其他线程,如果其他线程处于就绪状态,那么yield方法把当前运行的线程放入可运行池中并使其他线程运行。如果没有相同级别的线程,yield方法什么也不做。
等待其他线程结束Thread.join()
当前运行的线程可以调用其他线程的join方法,当前运行方法进入阻塞状态,直至另一个线程运行结束,当前线程再恢复运行。
停止线程方法stop()
无论当前线程正在做什么,将其强制关闭。此方法已废弃。
中断线程interrupt()
设置线程的中断状态位,可以通过线程的运行状态控制线程结束、等待新的任务或者继续执行当前任务。
定时器TimerTask
public static void main(String args[]){
TimeTask task = new TimeTask(){
public void run(){
System.out.println("Time:"+new Date());
}
};
ScheduledExecutorService sec = Executors.newScheduledThreadPool(1);
//设置一次执行的任务
sec.schedule(task,5000,TimeUnit.MILLISECONDS);
//反复执行,固定频率的执行任务
sec.scheduleAtFixedRate(task,0,3000,TimeUnit.MILLISECONDS);
//反复执行,固定间隔的执行任务
sec.scheduleWithFixedDelay(task,0,3000,TimeUnit.MILLISECONDS);
}
4、线程的同步
线程同步:当一个线程运行时,其他线程不能运行。(安全)
线程异步:当一个线程运行时,其他线程也能运行。
4.1、同步代码块
在多线程的运行环境下,为了让共享数据可以正确的操作,同一时间只允许一个线程对共享数据进行修改。可以使用同步代码块(锁)。
public class MyThread implements Runnable{//创建一个Runnable接口的实现类
int tickets=100;
@Override
public void run() {
while (tickets>0){//共享代码块,同一时刻只有一个线程可以执行。
synchronized (this){
tickets--;
}
System.out.println(Thread.currentThread().getName()+":"+(tickets+1));
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {//测试main方法,模拟卖票
MyThread m1=new MyThread();
Thread t1=new Thread(m1);
Thread t2=new Thread(m1);
Thread t3=new Thread(m1);
t1.setName("1号售票员");
t2.setName("2号售票员");
t3.setName("3号售票员");
t1.start();
t2.start();
t3.start();
}
4.2、同步方法
使用synchronized方法修饰的方法为同步方法。同步方法和同步块一样都有线程安全的作用。
class mThread implements Runnable {
int tickets=100;
@Override
public void run() {
while (tickets>0){
sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void sale(){
if(tickets>0){
System.out.println(Thread.currentThread().getName()+":"+tickets);
tickets--;
}
}
}
public static void main(String[] args) throws InterruptedException {
mThread m1 = new mThread();
Thread t1=new Thread(m1);
Thread t2=new Thread(m1);
Thread t3=new Thread(m1);
t1.setName("1号售票员");
t2.setName("2号售票员");
t3.setName("3号售票员");
t1.start();
t2.start();
t3.start();
}
同步是共享竞争资源的有效手段,当一个资源已经在操纵共享资源时,其他线程只能等待。当操纵共享资源的线程执行同步代码后,其他线程再有机会操纵共享资源。为了提高性能,应尽可能减少共享代码块中的步骤,使得一个线程能尽快释放锁,减少其他线程的等待时间。
线程安全的类:能同时被多个线程访问,且访问结束后对象处于逻辑合理的状态。