在一个进程中,线程是独立运行的处理单元,一个进程可以拥有多个线程,CPU为每个线程分配不同时间片用来运行程序,宏观上实现多个线程并发运行的效果。
Java中使用线程通常有四种方法
1.继承Thread类
2.实现Runable接口
3.实现Callable接口
4.使用线程池
1.继承Thread类
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
继承Thread类,然后重写run()方法实现程序逻辑,其中run()方法运行在子线程中。通过实例对象 myThread的start()方法开启线程。
2.实现Runable接口
Thread mThread=new Thread(new Runnable() {
@Override
public void run() {
System.out.print("other thread");
}
});
mThread.start();
在创建线程对象时传入一个Runable对象,Runnable是一个接口,实现Runable接口的run()方法来处理线程运行逻辑。run()方法的返回值为void,线程运行没有返回值。
3.实现Callable接口
Callable接口的call()方法同样用来编写线程运行逻辑,与run()方法相比多个返回值。因此可以在程序运行完返回数据。Callable接口需要与Future接口联合使用,Callable用来运行线程程序,Future用来管理线程,拿到线程返回结果。
RunnableFuture继承自Runnable,Future接口,FutureTask是实现RunnableFuture接口的类,可以通过FutureTask与Callable联合实现有返回值的线程。
Callable<Integer> mCallable=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 0;
}
};
FutureTask<Integer> mFutureTask=new FutureTask<>(mCallable);
Thread mThread=new Thread(mFutureTask);
mThread.start();
try {
System.out.print(mFutureTask.get()+"");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
首先实现Callable接口,然后创建FutureTask<V>对象,构造函数中传入Callable对象,之后将这个futuretask对象作为参数创建Thread对象,通过start()方法开启线程,则子线程开始运行,通过futuretask的get()方法可以拿到线程执行call()的返回值。
4使用线程池
Executors提供四种线程池,通过方法:
1.newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
2.newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
3.newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
4.newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
也可以通过重写ThreadPoolExecutor来自定义线程池。
5线程状态
1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。(注意,sleep是不会释放持有的锁)
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
每个线程都有自己的优先级,java线程的优先级取值范围1~10,优先级高的的线程并不一定会比优先级低的线程先执行,只是获得CPU时间片的概率更大些。
6.线程常用方法
sleep(long time) 持锁休眠一段时间。
wait() 放弃锁等待一段时间,直到设置的时间结束或者对象调用notify()或者notifyAll()唤醒该线程,然后重新竞争锁。
yield() 放弃当前执行机会,与其他线程竞争时间片,如果该线程竞争成功则继续执行,否则其他线程优先执行。
join() 当一个线程在另一个线程中执行该方法时,另一个线程阻塞,等待该线程执行完后继续执行。
setPriority()更改线程优先级
interrupt():不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!
7.线程同步
当多个线程访问主内存中同一数据时会涉及到同步问题,Java的内存模型分为主内与工作内存,每个线程直接与工作内存进行交互,工作内存与主内存进行交互。为了保证多个线程读写数据的一致性,需要加锁。通常通过synchronized来进行加锁,通过synchronized()内传入的是普通对象或者类对象,分为对象锁与类锁两种。前者针对类中普通成员有效,后者针对类中静态成员。