Thread类实现了Runnable接口,所以Thread对象也是可运行Runnable对象,同时Thread类也是线程类
构造器
Thread()//一般用于在Thread类中覆盖定义run方法,可以使用匿名内部类进行定义
Thread(Runnable)//使用最多的情况,run方式是由Runnable参数对象提供
Thread(String name) //自定义线程名称
Thread(Runnable,String name)
… …
//常见简化写法
Thread t = new Thread(()->{
System.out.println(Thread.currentThread());
});
t.start();
Runnable接口只定义了一个方法public void run(),这个方法要求实现Runnable接口的类实现,Runnable对象称为可运行对象,一个线程的运行就是执行该对象的run()方法
run()方法没有返回值void,而且不能抛出异常
class MyRunnable implements Runnable{
@Override
public void run()throws Exception {//语法报错,这里不允许抛出异常,如果其中有异常则需要使用
try.catch处理
//没有返回值,如果需要记录处理结果,需要自己编程处理
}
}
//简化写法
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("左手画一条龙...");
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
继承Thread或实现Runnable接口这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。
如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,这样使用起来就比较麻烦。
call()方法有返回值,这个返回值可以通过泛型进行约束,允许抛出异常
class MyRunnable implements Callable<Number> {
// <>中用于指定返回值类型,必须使用引用类型,不能使用简单类型
public Number call() throws Exception {//允许抛出异常
return null;
}
}
//简化写法
new Thread(new FutureTask<>(()->{
for(int i=0;i<10;i++){
System.out.println("右手画彩虹");
Thread.sleep(30);//因为call方法允许抛出异常
}
return null;
})).start();
Future表示一个任务的生命周期,并提供了方法来判断是否已经完成或取消以及获取任务的结果和取消任务等
Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
Future f = new FutureTask(() -> {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread() + "...start..." + i);
Thread.sleep(10);
System.out.println(Thread.currentThread() + "...end..." + i);
}
return null;
});
if (f instanceof Runnable){
new Thread((Runnable) f).start();
int counter=0;
while (true) {
Thread.sleep(20);
System.out.println("任务是否被取消:" + f.isCancelled()+"--"+counter);
System.out.println("任务是否执行完毕:" + f.isDone());
counter++;
if(counter>10)
f.cancel(true);//取消任务的执行
if(counter>12)
break;
}
}
Future f = new FutureTask(() -> {
int res=0;
for (int i = 0; i < 1000; i++) {
Thread.sleep(10);
res+=i;
}
return res;
});
if (f instanceof Runnable){
new Thread((Runnable) f).start();
int counter=0;
long start=System.currentTimeMillis();
// Object obj=f.get();
Object obj=f.get(5,TimeUnit.SECONDS);//参数1为超时时长,参数2为时长的单位,是一个枚举类型数据,超时TimeoutException
long end=System.currentTimeMillis();
System.out.println("get...执行时间为:"+(end-start)+"ms");
System.out.println("线程执行结果为:"+obj);
}
FutureTask
具体使用
FutureTask<Integer> future = new FutureTask<Integer>(callable);
new Thread(future).start();
FutureTask实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
FutureTask是一个可取消的异步计算,FutureTask 实现了Future的基本方法,提供start cancel 操作,可以查询计算是否已经完成,并且可以获取计算的结果。结果只可以在计算完成之后获取,get方法会阻塞当计算没有完成的时候,一旦计算已经完成, 那么计算就不能再次启动或是取消。
一个FutureTask 可以用来包装一个 Callable 或是一个Runnable对象。因为FurtureTask实现了Runnable方法,所以一个 FutureTask可以提交(submit)给一个Excutor执行(excution). 它同时实现了Callable, 所以也可以作为Future得到Callable的返回值。
ThreadPoolExecutor
ThreadPoolExecutor是线程池框架的一个核心类,线程池通过线程复用机制,并对线程进行统一管理
线程池的运行状态总共有5种,其值和含义分别如下:
1.corePoolSize
线程池中的核心线程数。当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。
2.maximumPoolSize
线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize。
3.keepAliveTime
线程空闲时的存活时间。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,keepAliveTime参数也会起作用,直到线程池中的线程数为
0。
4.unit
keepAliveTime参数的时间单位。
5.workQueue
任务缓存队列,用来存放等待执行的任务。如果当前线程数为corePoolSize,继续提交的任务就会被保存到任务缓存队列中,等待被执行。
一般来说,这里的BlockingQueue有以下三种选择:
6.threadFactory
线程工厂,创建新线程时使用的线程工厂。
7.handler
任务拒绝策略,当阻塞队列满了,且线程池中的线程数达到maximumPoolSize,如果继续提交任务,就会采取任务拒绝策略处理该任务,线程池提供了4种任务拒绝策略:
当然也可以根据应用场景实现RejectedExecutionHandler接口自定义饱和策略,如记录日志或持久化存储不能处理的任务。
newCachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收重用时则新建线程
newFixedThreadPool 创建一个固定大小的定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行
可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景
public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor implements ScheduledExecutorService
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
newWorkStealingPool:创建一个拥有多个任务队列的线程池,可以减少连接数
提交任务的方式
线程池框架提供了两种方式提交任务,submit()和execute(),通过submit()方法提交的任务可以返回任务执行的结果,通过execute()方法提交的任务不能获取任务执行的结果。
关闭线程池
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(()->{
for(int i=0;i<10;i++){
System.out.println("Hello "+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("main......");
ExecutorService es = Executors.newFixedThreadPool(2);
Future f = es.submit(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("Hello " + i);
Thread.sleep(200); }
return 100;
});
Object obj=f.get(); 阻塞当前main线程
System.out.println("main......");
Java内存模型定义了一种多线程访问Java内存的规范。
多个线程之间是可以使用PipedInputStream/PipedOutputSteam互相传递数据通信的,它们之间的沟通只能通过共享变量来进行
public class T1 {
private int num;
public static void main(String[] args) {
T1 t=new T1();
t.pp();
}
public void pp(){
//通过对num的操作实现了t1和t2之间的通信,这里目前不保证输出的正确性.可以通过同步锁
synchronized保证数据的正确性
Thread t1=new Thread(()->{
for(int i=0;i<100;i++)add();
});
new Thread(()->{
for(int i=0;i<100;i++)sub();
}).start();
t1.start();
}
public void add(){
num++;
System.out.println(Thread.currentThread()+"加法:"+num);
}
public void sub(){
num--;
System.out.println(Thread.currentThread()+"减法:"+num);
}
}
Java中堆和栈有什么不同
每个线程都有自己的栈内存(栈帧),用于存储本地变量,方法参数和栈调用,一个线程中存储的变量对其它线程是不可见的。
而堆是所有线程共享的一片公用内存区域
JDK1.6+引入了逃逸分析,对象都在堆里创建,为了提升效率线程会从堆中弄一个缓存到自己的栈,如果多个线程使用该变量就可能引发问题,这时volatile 变量就可以发挥作用了,它要求线程从主存中读取变量的值
如何在Java中获取线程堆栈
对于不同的操作系统,有多种方法来获得Java进程的线程堆栈。当获取线程堆栈时,JVM会把所有线程的状态存到日志文件或者输出到控制台。在Windows可以使用Ctrl + Break组合键来获取线程堆栈Linux下用kill -3命令。也可以用jstack这个工具来获取,它对线程id进行操作,可以用jps这个工具找到id。
通过使用 jps 检查当前正在运行的JAVA进程的 PID。jps –lvm
使用明确的 PID 作为 jstack 的参数来获取 thread dumps。jstack -f 5824
一般用于死锁的分析和线程执行速度很慢时的分析
JVM中哪个参数是用来控制线程的栈堆栈小的
线程操作某个对象的执行顺序
volatile是java提供的一种同步手段,只不过它是轻量级的同步,为什么这么说,因为volatile只能保证多线程的内存可见性,不能保证多线程的执行原子性。而最彻底的同步要保证有序性、可见性和原子的synchronized
任何被volatile修饰的变量,都不拷贝副本到工作内存,任何修改都及时写在主存。因此对于volatile修饰的变量的修改,所有线程马上就能看到,但是volatile不能保证对变量的修改是原子的
public class VolatileTest{
public volatile int a;
public void add(int count){
a=a+count; }
}
volatile存在的意义是,任何线程对a的修改,都会马上被其他线程读取到,因为直接操作主存,没有线程对工作内存和主存的同步。所以,volatile的使用场景是有限的,在有限的一些情形下可以使用 volatile 变量替代锁
要使 volatile 变量提供理想的线程安全,必须同时满足两个条件
volatile特性
volatile的认识
public class Test1 {
private static boolean flag=false;
private static int i=0;
public static void main(String[] args) {
new Thread(()->{
try {
TimeUnit.MILLISECONDS.sleep(100);//Thread.sleep(100)
flag=true;
System.out.println("flag changed...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
while(!flag){
i++;
}
System.out.println("progress end...");
}
}
程序不能执行结束,会进入死循环状态。
解决方案:flag上添加关键字volatile
Java内存模型规定和指引Java程序在不同的内存架构、CPU和操作系统间有确定性地行为。它在多线程的情况下尤其重要。Java内存模型对一个线程所做的变动能被其它线程可见提供了保证,它们之间是先行发生关系。这个关系定义了一些规则让程序员在并发编程时思路更清晰。
1、线程内的代码能够按先后顺序执行,这被称为程序次序规则。
2、对于同一个锁,一个解锁操作一定要发生在时间上后发生的另一个锁定操作之前,也叫做管程锁定规则。
3、前一个对volatile的写操作在后一个volatile的读操作之前,也叫volatile变量规则。
4、一个线程内的任何操作必需在这个线程的start()调用之后,也叫作线程启动规则。
5、一个线程的所有操作都会在线程终止之前,线程终止规则。
6、一个对象的终结操作必需在这个对象构造完成之后,也叫对象终结规则。
7、可传递性。如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论
1、新建状态(New):新创建了一个线程对象。new Thread()
2、就绪状态(Runnable):线程对象创建后,其它线程调用了该对象的start()方法。只能针对处于新建状态的线程对象调用start方法,否则IllegalThreadStateException
该状态的线程位于可执行线程池中,变得可执行,等待获取CPU的使用权。
3、执行状态(Running):就绪状态的线程获取了CPU。执行程序代码。注意在一个多处理器的机器上会有多个
线程并行执行
现在大部分桌面和服务器操作系统都采用时间片轮转法的抢占式调度策略,在选择下一个执行线程时系统会考虑线程的优先级
调用yield方法可以让运行状态的线程转入就绪
4、堵塞状态(Blocked):堵塞状态是线程由于某种原因放弃CPU使用权。临时停止执行。直到线程进入就绪状态,才有机会转到执行状态。线程切换是由底层平台控制的,具有一定的随机性
堵塞的情况分三种:
(一)、等待堵塞:执行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步堵塞:执行的线程在获取对象的同步锁时,若该同步锁被别的线程占用。则JVM会把该线程放入锁
池中。
(三)、其它堵塞:执行的线程执行sleep()或join()方法,或者发出了I/O请求时。JVM会把该线程置为堵塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完成时。线程又一次转入就绪状态。
5、死亡状态(Dead):线程运行完了或者因异常退出了run()方法,该线程结束生命周期。
直接调用该线程的stop方法也可以结束线程,但是这个方法容易导致数据不一致的问题,通常不推荐使用
当主线程结束时,其它线程不受任何影响,并不会随之结束,一旦子线程启动后则拥有和主线程相同的地位,并不受主线程的影响
注意:不要试图对一个已经死亡的线程调用start方法使其重新启动,该线程将不可再次作为线程执行,否则异常
不要用stop方法来停止一个线程。因为stop方法太极端,会出现同步问题,使数据不一致。所以可以考虑通过设置标志,通过return, break,异常等手段来控制流程自然停止
Thread t1=new Thread(() -> {
for(int i=0;i<100;i++)
System.out.println(Thread.currentThread()+"--"+i);
});
t1.start();
TimeUnit.MICROSECONDS.sleep(10);
t1.stop();
//可以通过其它方式实现线程的停止
class MyThread extends Thread {
private boolean flag = true;
@Override
public void run() {
for (int i = 0; i < 100 && flag; i++)
System.out.println(Thread.currentThread() + "--" + i);
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
Thread类中定义的
public static native void sleep(long millis)throws InterruptedException
让当前线程休眠指定时间。休眠时间的准确性依赖于系统时钟和CPU调度机制。如果需要可以通过调用interrupt()方法来唤醒休眠线程
public void interrupt() {
if (this != Thread.currentThread()) checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
目前流行写法:TimeUnit.SECONDS.sleep(1);
不释放已获取的锁资源,如果sleep方法在同步上下文中调用,那么其他线程是无法进入到当前同步块或者同步方法中的。
练习:实现一个时间显示:每隔一秒钟更新一次(例如倒计时)
自定义格式的时间显示 DateFormat---SimpleDateFormat
DateFormat df=new SimpleDateFormat("yyyy-MM-ddE hh:mm:ss");
for (;;) { //相当于while(true){}
Date now = new Date();
System.out.println(df.format(now));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
当线程进入休眠态,它会定时结束休眠。如果需要提前唤醒,则需要通过interrupt方法实现。所谓的interrupt方法实际上会产生一个异常InterruptedException
public static void main(String[] args) throws Exception {
DateFormat df=new SimpleDateFormat("yyyy-MM-ddE hh:mm:ss");
Thread t1=new Thread(()->{
while(true){
Date now=new Date();
System.out.println(df.format(now));
try {
//TimeUnit.SECONDS.sleep(10);
Thread.sleep(10000);//子线程进入休眠阻塞状态,当超时后自动进入就绪状态,等待下次调度执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
Thread.sleep(200);
t1.interrupt(); //唤醒处于休眠状态的线程,在子线程中会导致InterruptedException
System.out.println("main.....");
}
让当前线程进入等待状态,当别的其他线程调用notify()或者notifyAll()方法时,当前线程进入就绪状态
wait方法必须在同步上下文中调用,例如:同步方法块或者同步方法中,这也就意味着如果你想要调用wait方法,前提是必须获取对象上的锁资源
当wait方法调用时,当前线程将会释放已获取的对象锁资源,并进入等待队列,其他线程就可以尝试获取对象上的锁资源。
// 创建锁对象,保证唯一性
Object obj = new Object();
new Thread() {
public void run() {
// 保证等待和唤醒只能执行一个,需要使用同步技术
synchronized (obj) {
System.out.println("点外卖");
// 调用wait方法,放弃CPU的执行权,进入WAITING状态(无限等待)
obj.wait(1000);
// 唤醒之后的代码
System.out.println("外卖已到达");
}
}.start();
sleep vs wait
Java程序中wait 和 sleep都会造成某种形式的暂停,它们可以满足不同的需要。wait()方法用于线程间通信,如果等待条件为真且其它线程被唤醒时它会释放锁,而sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。
主要作用是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。调用这个方法的线程将被阻塞
方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。但是sleep(long)不释放锁。
练习:编写10个线程,第一个线程从1加到10,第二个线程从11加到20…第十个线程从91加到100,最后再把十个线程结果相加
使用Callable接口,因为future.get()可以阻塞当前线程的执行
FutureTask[] fs=new FutureTask[10];
for(int i=0;i<10;i++){
fs[i]=new FutureTask<>(new MyCallable(i*10+1,(i+1)*10));
new Thread(fs[i]).start();
}
int res=0;
for(FutureTask ft:fs) res+=(Integer)ft.get();
System.out.println(res);
使用Runnable接口,必须通过join使子线程执行结束后才在主线程中累加10个子线程的结果
MyRunnable[] arr=new MyRunnable[10];
Thread[] ts=new Thread[10];
for(int i=0;i<10;i++){
MyRunnable mr=new MyRunnable(i*10+1,(i+1)*10);
arr[i]=mr;
ts[i]=new Thread(mr);
ts[i].start();
}
int res=0;
for(int i=0;i<10;i++){
ts[i].join();
res+=arr[i].getRes();
}
System.out.println("计算结果为:"+res);