Java线程内容
一:线程的创建
- 继承Thread类 重写run方法
线程不能够启动多次,如果再次调用start()方法,会抛出异常IllegalThreadStateException。如果要创建多个线程,需要创建多个Thread对象。
public class MyTest {
public static void main(String[] args) {
MyJob mj = new MyJob();
mj.start();
}
}
class MyJob extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("MyJob:" + i);
}
}
}
- 实现Runnable接口 重写run方法(用的最多的)
public class MiTest {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("MyRunnable:" + i);
}
}
}
最常用的方式是
匿名内部类方式:
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("thread:" + i);
}
}
}).start();;
lambda方式:
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("thread:" + i);
}
}).start();
- 实现Callable接口 重写call方法,配合FutureTask
特点:可以返回结果
public class MiTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask futureTask = new FutureTask(myCallable);
//创建Thread线程
Thread t1 = new Thread(futureTask);
//启动线程
t1.start();
//获取结果
Object count = futureTask.get();
System.out.println("返回结果:" + count);
}
}
class MyCallable implements Callable{
@Override
public Object call() throws Exception {
int count = 0;
for (int i = 0; i < 10; i++) {
count += i;
}
return count;
}
}
- 基于线程池构建线程
可以控制运行的线程的数量
优点:- 降低资源消耗,重复利用已经创建好的线程,减少线程创建和销毁的消耗
- 提高响应速度,可以减少创建和销毁线程的次数
- 提高线程的可管理性,指定最大线程个数,防止因为消耗过多内存导致服务器崩溃
Runnable接口和实现Callable接口的区别:
- Runnable接口需要重写的是run()方法,Callable接口需要重写的是call()方法
- Runnable的任务没有返回值,Callable的任务执行后可返回值
- run()方法不能抛出异常,call()方法可以抛出异常
- 加入线程池运行时,Runnable使用ExecutorService的execute方法,Callable使用的submit方法
二:Java中线程的6种状态
说明:
NEW(新建状态):Thread对象被创建出来了,但是还没有执行start方法。
RUNNABLE(就绪状态):Thread对象调用了start方法,就为RUNNABLE状态(CPU调度/没有调度)
BLOCKED(阻塞状态):synchronized没有拿到同步锁,被阻塞的情况
WAITING(等待状态):调用wait方法就会处于WAITING状态,需要被手动唤醒
TIME_WAITING(超时等待):调用sleep方法或者join方法,会被自动唤醒,无需手动唤醒
TERMINATED(结束状态):run方法执行完毕,线程生命周期到头了
BLOCKED、WAITING、TIME_WAITING:都可以理解为是阻塞、等待状态,因为处在这三种状态下,CPU不会调度当前线程
三:线程内容
线程的强占:
使用Thread类的非静态方法join(),join() 方法允许一个线程等待另一个线程完成
join方法可以指定等待时间,如果子线程在等待时间内结束,则主线程自动变为就绪状态
默认情况下,线程都是非守护线程
JVM会在程序中没有非守护线程时,结束掉当前JVM。主线程默认是非守护线程,如果主线程执行结束,需要查看当前JVM内是否还有非守护线程,如果没有JVM直接停止。当对象被回收的时候,会执行finalize方法,执行finalize方法的线程是守护线程,所以不一定会执行成功。
在调用wait方法和notify以及norifyAll方法时,必须在synchronized修饰的代码块或者方法内部才可以,因为要操作基于某个对象的锁的信息维护。
线程调用wait方法后,当前线程会释放锁资源
线程的结束方式:
- 让线程的run方法执行结束,无论是return结束,还是抛出异常结束,都可以的
- 使用标志位flag,修改flag的值true/false来退出
- 使用中断标志位,thread.interrupt()方法中断线程,用这种共享变量方式,改变interrupt标记位,线程默认情况下标记位是false的(不常用)
- 使用中断异常interruptedException。通过打断WAITING或者TIMED_WAITING状态的线程,从而抛出异常自行处理。(如果线程因为join()、sleep()、wait()导致阻塞,可以调用interrupt(),程序会抛出异常,在catch内使用break退出。)
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while(true){
// 执行任务
// 没有任务,让线程休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("基于打断形式结束当前线程");
return;
}
}
});
thread.start();
Thread.sleep(500);
thread.interrupt();
}
- 使用thread.stop()方法中止线程,比较暴力。
四、Java并发编程三大特性:
1> 原子性:原子性指一个操作是不可分割的,不可中断的,一个线程在执行时,另一个线程不会影响到他。
JMM(Java Memory Model)规定了所有变量都会存储在主内存中,每个线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝。在操作的时候,需要从主内存复制一份到线程内存(CPU内存),在线程内部做计算,再写回主内存中(不一定立即写)
原子性:多个线程操作临界资源时候,预期结构要与最终结果一致。
问:如何保证原子性呢?
使用synchronized可以让避免多线程同时操作临界资源,同一时间点,只会有一个线程正在操作临界资源
使用Lock锁,推荐ReentrantLock锁,性能会更好,ReentrantLock底层是基于AQS实现的,有一个基于CAS维护的state变量来实现锁的操作。
使用ThreadLocal,他保证原子性的方式,是不让多线程去操作临界资源,让每个线程去操作属于自己的数据
ThreadLocal内存泄漏问题:
- 如果ThreadLocal引用丢失,key因为弱引用会被GC回收掉,如果同时线程还没有被回收,就会导致内存泄漏,内存中的value无法被回收,同时也无法被获取到。
- 只需要在使用完毕ThreadLocal对象之后,及时的调用remove方法,移除Entry即可
2> 可见性
3> 有序性