一,初识
1,什么是线程呢
是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行
- 线程是轻量级的进程
- 线程没有独立的地址空间(内存空间)
- 线程是由进程创建的(寄生在进程)
- 一个进程可以拥有多个线程-->这就是我们常说的多线程编程
2,线程五种状态
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()方法,该线程结束生命周期。
3,线程的属性
优先级:java中通过setPriority(int)设置(1-10之间)设置优先级,默认优先级为5。特别的,虽然cpu一般将资源分配给优先级较高的线程,但是有一定的随机性。
守护线程:可以通过setDaemon(boolean) 设置线程的Daemon模式。线程级别较低,一般为其它线程提供服务,其它线程退出时它才退出。如jvm中的垃圾回收线程。
4,线程与进程区别
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
5,线程调度
https://www.cnblogs.com/yjd_hycf_space/p/7526608.html
5.1调整线程优先级:Java线程有优先级,优先级高的线程会获得较多的运行机会。
5.2 线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
5.3线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。这个两个唤醒方法也是Object类中的方法,行为等价于调用 wait(0) 一样。
5.4、线程让步:Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
5.5、线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
5.6、线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。
线程通过调用其中一个 wait 方法,在对象的监视器上等待。 直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;
例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。类似的方法还有一个notifyAll(),唤醒在此对象监视器上等待的所有线程。
注意点:
- 从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内;
- Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。
sleep(): 强迫一个线程睡眠N毫秒。
isAlive(): 判断一个线程是否存活。
join(): 等待线程终止。
activeCount(): 程序中活跃的线程数。
enumerate(): 枚举程序中的线程。
currentThread(): 得到当前线程。
isDaemon(): 一个线程是否为守护线程。
setDaemon(): 设置一个线程为守护线程。(用户线程和守护线程的区别在于,是否等待主线程依赖于主线程结束而结束)
setName(): 为线程设置一个名称。
wait(): 强迫一个线程等待。
notify(): 通知一个线程继续运行。
setPriority(): 设置一个线程的优先级
6 ,线程同步
6.1、synchronized关键字的作用域有二种:
1)是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
6.2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;
6.3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
二,窥探
1,如何实现两种方式:
- 直接继承Thread类
- 实现Runnable接口
- 实现Callable接口,并与Future、线程池结合使用
2,Thread和Runnable的区别
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。
总结:
- 实现Runnable接口比继承Thread类所具有的优势:
- 适合多个相同的程序代码的线程去处理同一个资源
- 可以避免java中的单继承的限制
- 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
- 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
//继承Thread
public class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i=0;i<5000;i++){
System.out.println(name +"获取到i=="+i);
try {
sleep((int)Math.random());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
MyThread threadA = new MyThread("A");
MyThread threadB = new MyThread("B");
threadA.start();
threadB.start();
}
}
//结果:多线程间资源共享,随机获取资源和cpu执行权
B获取到i==2787
B获取到i==2788
B获取到i==2789
A获取到i==411
B获取到i==2790
A获取到i==412
B获取到i==2791
//实现Runnable接口
//在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
public class MyThread implements Runnable{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i=0;i<5000;i++){
System.out.println(name +"获取到i=="+i);
try {
Thread.sleep((int)Math.random());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread threadC = new Thread(new MyThread("C"));
Thread threadD = new Thread(new MyThread("D"));
threadC.start();
threadD.start();
}
}
//结果:多线程间资源共享,随机获取资源和cpu执行权
C获取到i==3447
C获取到i==3448
D获取到i==1097
C获取到i==3449
C获取到i==3450
注意:main方法其实也是一个线程。在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个jVM实习在就是在操作系统中启动了一个进程。
上面两种方法都要实现run()方法,最终都会生成一个Thread对像;一般推荐使用第二种办法,因为线程只是实现了Runnable接口,还可以继承其他类。很适合多个相同线程处理同一份资源,能很好的将cpu、代码和数据分开,形成清晰模型,很好的体现了面向对象编程的思想.
3,Callable和Runnable的区别
- Callable规定的方法是call(),而Runnable规定的方法是run()。其中Runnable可以提交给Thread来包装下,直接启动一个线程来执行,而Callable则一般都是提交给ExecuteService来执行。
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
- call() 方法可抛出异常,而run() 方法是不能抛出异常的。
- 运行Callable任务可拿到一个Future对象, Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。 通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。
- Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。
//通过线程池创建线程,实现Runnable接口
public class RunableThreadDemo implements Runnable{
private static final int POOL_SIZE = 20;//线程池数量
@Override
public void run() {
System.out.println("通过线程池创建线程,实现Runnable接口,当前线程: " + Thread.currentThread().getName());
}
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
RunableThreadDemo runableThreadDemo = new RunableThreadDemo();
for(int i=0;i<POOL_SIZE;i++){
Thread.sleep(1000);//阻塞时间
executorService.execute(runableThreadDemo);
}
executorService.shutdown();//关闭线程池
}
}
//结果
通过线程池创建线程,实现Runnable接口,当前线程: pool-1-thread-1
通过线程池创建线程,实现Runnable接口,当前线程: pool-1-thread-2
通过线程池创建线程,实现Runnable接口,当前线程: pool-1-thread-3
通过线程池创建线程,实现Runnable接口,当前线程: pool-1-thread-1
通过线程池创建线程,实现Runnable接口,当前线程: pool-1-thread-2
//通过线程池创建线程,实现Callable接口
public class CallableThreadDemo implements Callable {
private static final int POOL_SIZE = 20;//线程池数量
@Override
public Object call() throws Exception {
System.out.println("通过线程池创建线程,实现Callable接口,当前线程: " + Thread.currentThread().getName());
return 12306;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i<POOL_SIZE; i++){
CallableThreadDemo callableThreadDemo = new CallableThreadDemo();
//执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是Future、Runnable接口的实现类
FutureTask futureTask = new FutureTask(callableThreadDemo);
Thread.sleep(1000);//阻塞时间
executorService.submit(futureTask);
//实现Callable接口,执行run方法后可以通过FutureTask对象的get()方法获取返回值
System.out.println(futureTask.get());
}
executorService.shutdown();//关闭线程池
}
}
//结果
通过线程池创建线程,实现Callable接口,当前线程: pool-1-thread-1
12306
通过线程池创建线程,实现Callable接口,当前线程: pool-1-thread-2
12306
通过线程池创建线程,实现Callable接口,当前线程: pool-1-thread-3
12306
通过线程池创建线程,实现Callable接口,当前线程: pool-1-thread-1
12306
通过线程池创建线程,实现Callable接口,当前线程: pool-1-thread-2
Executors类:提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。
- public static ExecutorService newFixedThreadPool(int nThreads) :创建固定数目线程的线程池。
- public static ExecutorService newCachedThreadPool() :创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
- public static ExecutorService newSingleThreadExecutor() :创建一个单线程化的Executor。
- public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) :创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。
FutureTask类图