1.线程的基本概念
线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。线程有就绪、阻塞和运行三种基本状态。
要点:用户每启动一个进程,操作系统就会为该进程分配一个独立的内存空间。请注意是独立的内存空间。
2.线程的生命周期:
3.线程的实现方式
有两种实现方式:
- 1.继承Thread类
代码演示:
class MyThread extends Thread {//线程主体类
private String title;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {//线程的主体方法
for(int x = 0; x < 10 ; x++) {
System.out.println(this.title + "运行,x = " + x);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
//实例化继承了Thread的类
MyThread thread1 = new MyThread("Thread1");
//通过从Thread类中所继承的start()方法启动线程;
thread1.start();
}
}
- 2.实现Runable接口
代码演示:
class MyThread implements Runnable {//线程主体类
private String title;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {//线程的主体方法
for(int x = 0; x < 10 ; x++) {
System.out.println(this.title + "运行,x = " + x);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Thread threadA = new Thread(new MyThread("线程A"));
Thread threadB = new Thread(new MyThread("线程B"));
Thread threadC = new Thread(new MyThread("线程C"));
threadA.start();
threadB.start();
threadC.start();
}
}
4、两个实现方法的比较
1、继承Thread类有一个缺点就是单继承,而实现Runnable接口则弥补了它的缺点,可以实现多继承
2、继承Thread类必须如果产生Runnable实例对象,就必须产生多个Runnable实例对象,然后再用Thread产生多个线程;而实现Runnable接口,只需要建立一个实现这个类的实例,然后用这一个实例对象产生多个线程。即实现了资源的共享性
实现Runnable接口的方式,单独的Runnable对象,对应相对独立的业务内容,而Thread 线程对象则只是一个业务执行的容器。这样面向对象是更加合理的。而继承Thread类的方式,通过继承Thread ,覆盖run方法。这样最终将原本没有关系的两个类,揉到一体。这样耦合更高。
5、线程的停止
停止有两种方法:
1.使用退出标志,使线程正常退出,也就是当 run() 方法完成后线程中止。
2.使用 stop() 方法强行终止线程,但是不推荐使用这个方法,该方法已被弃用。
1. 使用标志位终止线程
public class ServerThread extends Thread {
//volatile修饰符用来保证其它线程读取的总是该变量的最新的值
public volatile boolean exit = false;
@Override
public void run() {
ServerSocket serverSocket = new ServerSocket(8080);
while(!exit){
serverSocket.accept(); //阻塞等待客户端消息
...
}
}
public static void main(String[] args) {
ServerThread t = new ServerThread();
t.start();
...
t.exit = true; //修改标志位,退出线程
}
}
2.stop方法停止
由于不安全,已经不使用了,因为stop会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,假如一个线程正在执行:synchronized void { x = 3; y = 4;} 由于方法是同步的, 多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 3;时,被调用了 stop()方法,即使在同步块中,它也会马上stop了,这样就产生了不完整的残废数据。
6、线程的中断
使用 interrupt 方法中断线程
interrupt() 方法并不会立即执行中断操作,这个方法只会给线程设置一个为true的中断标志。
设置之后,则根据线程当前的状态进行不同的后续操作。
(1)如果,线程的当前状态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已
(2)如果线程的当前状态处于阻塞状态,那么在将中断标志设置为true后,如果是 wait、sleep以及join 三个方法引起的阻塞,那么会将线程的中断标志重新设置为false,并抛出一个InterruptedException ,这样受阻线程就得以退出阻塞的状态。
举例:一个线程在运行状态中,其中断标志被设置为true之后,一旦线程调用了wait、join、sleep方法中的一种,立马抛出一个InterruptedException,且中断标志被程序会自动清除,重新设置为false
总结:调用线程类的interrupted方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。
public class TestThread1 {
public static void main(String[] args) {
MyRunnable1 myRunnable=new MyRunnable1();
Thread thread=new Thread(myRunnable,"子线程");
thread.start();
try{
//主线程休眠
Thread.sleep(3000);
//调用中断,true
thread.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class MyRunnable1 implements Runnable{
@Override
public void run() {
int i=0;
while(true){
System.out.println(Thread.currentThread().getName()+"循环第"+ ++i+"次");
try{
//判断线程的中断情况
boolean interruptStatus=Thread.currentThread().isInterrupted();
System.out.println(Thread.currentThread().getName()+"循环第"+ ++i+"次"+interruptStatus);
Thread.sleep(1000);
//非阻塞中断 只是设置标记位true
if(interruptStatus){
//如果中断为true则退出
break;
}
} catch (InterruptedException e) {
// 一个线程在运行状态中,其中断标志被设置为true之后,一旦线程调用了
// wait、join、sleep方法中的一种,立马抛出一个InterruptedException,且中断标志被程序会自动清除,重新设置为false
System.out.println("阻塞中断"+Thread.currentThread().isInterrupted());//显示false并抛异常
return;//不想返回还可继续写代码
}
}
}
}
7、thread的notify、join、yield方法说明
1.notify
方法介绍:
notify():唤醒,唤醒线程池等待线程其中的一个。
notifyAll():唤醒线程池所有等待线程。
与notify对应的是wait()方法:
wait():等待,如果线程执行了wait方法,那么该线程会进入等待的状态,等待状态下的线程必须要被其他线程调用notify()方法才能唤醒。
代码示例:
public class Twait {
public static void main(String[] args) {
TestThread testThread1 = new TestThread();
TestThread testThread2 = new TestThread();
TestThread testThread3 = new TestThread();
testThread1.start();
testThread2.start();
testThread3.start();
System.out.println("主线程休眠5秒");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
System.out.println("主线程 Interrupted");
}
System.out.println("唤醒 线程Thread-0");
testThread1.resumeByNotify();
try {
System.out.println("主线程再次休眠");
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
System.out.println("Main Thread Interrupted");
}
System.out.println("唤醒所有 By NotifyAll");
testThread1.resumeByNotifyAll();
}
}
class TestThread extends Thread {
private static Object obj = new Object();
@Override
public void run() {
System.out.println(getName() " 即将进入阻塞");
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
System.out.println(getName() " Test Thread Interrupted");
}
}
System.out.println(getName() " 被唤醒");
}
public void resumeByNotify() {
synchronized (obj) {
obj.notify();
}
}
public void resumeByNotifyAll() {
synchronized (obj) {
obj.notifyAll();
}
}
}
2.join
当A线程执行到了B线程的.join()方法时,A就会等待,等B线程都执行完,A才会执行。
join可以用来临时加入线程执行。
具体例子:
这是没加join的:
public class ThreadTest {
//private static final Long count = 10000L;
public static void main(String[] args){
long base = System.currentTimeMillis();
try {
ThreadJoin t1 = new ThreadJoin("线程1");
ThreadJoin t2 = new ThreadJoin("线程2");
t1.start();
t2.start();
} catch (Exception e) {
e.printStackTrace();
}
long time = System.currentTimeMillis() - base;
System.out.println("执行时间:"+time);
}
}
class ThreadJoin extends Thread{
private static final Long count = 10L;
public ThreadJoin(String name){
super(name);
}
@Override
public void run() {
//super.run();
for(int i = 1; i <= count; i ++){
System.out.println(this.getName()+":"+i);
}
}
}
没加join的结果:
线程1:1
线程2:1
线程2:2
线程2:3
线程2:4
线程2:5
线程2:6
线程2:7
线程2:8
线程2:9
线程2:10
线程1:2
线程1:3
线程1:4
线程1:5
线程1:6
线程1:7
线程1:8
线程1:9
线程1:10
加了join后:
public class ThreadTest {
//private static final Long count = 10000L;
public static void main(String[] args){
long base = System.currentTimeMillis();
try {
ThreadJoin t1 = new ThreadJoin("线程1");
ThreadJoin t2 = new ThreadJoin("线程2");
t1.start();
//这里加了join
t1.join();
t2.start();
} catch (Exception e) {
e.printStackTrace();
}
long time = System.currentTimeMillis() - base;
System.out.println("执行时间:"+time);
}
}
class ThreadJoin extends Thread{
private static final Long count = 10L;
public ThreadJoin(String name){
super(name);
}
@Override
public void run() {
//super.run();
for(int i = 1; i <= count; i ++){
System.out.println(this.getName()+":"+i);
}
}
}
加join后的结果:
线程1:1
线程1:2
线程1:3
线程1:4
线程1:5
线程1:6
线程1:7
线程1:8
线程1:9
线程1:10
执行时间:0
线程2:1
线程2:2
线程2:3
线程2:4
线程2:5
线程2:6
线程2:7
线程2:8
线程2:9
线程2:10
3.yield
yield(线程让步):让当前线程(调用yield()方法的线程)休息一会,即让当前线程由运行状态(Running)进入到可运行状态(Runnable),yield()方法在Thread类中定义,是一个本地方法,值得注意的是yield()并不释放对象锁,所以在同步块中使用yield(),其他线程仍然获取不到锁,需要等待当前线程执行完之后才能获取锁执行任务
代码示例:
public class YieldDemo {
public static void main(String[] args) {
MyRunnable2 r = new MyRunnable2();
Thread t1 = new Thread(r, "t1");
Thread t2 = new Thread(r, "t2");
t1.start();
t2.start();
}
}
class MyRunnable2 implements Runnable {
@Override
public void run() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "--" + i);
/**
* t1,t2在运行时会获取同一个对象锁,
* 当取模2为0时,会执行线程让步,但是线程没有释放锁,所以另外一个线程只能等待,直到第一个线程执行完毕才进行
*/
if (i % 2 == 0) {
Thread.yield();
}
}
}
}
}
输出结果:
t1--0
t1--1
t1--2
t1--3
t1--4
t2--0
t2--1
t2--2
t2--3
t2--4
join()与yield()的区别
yield()
暂停当前正在执行的线程对象,并执行其他线程
join()
线程实例的join()方法可以使得一个线程在另一个线程结束后再执行,即也就是说使得当前线程可以阻塞其他线程执行;
8、线程的异常处理
1.基于内部try,catch的方式
public void run(){
try{
....
}catch(Throwable e){
backgroundException = e;
}
}
在主线程,或者其他线程判断对应的backgroundException是否为null,如果不为null表示上次线程执行过程中出现了异常,然后执行异常处理逻辑。
2.基于异常处理器的方法
在Java中有一个异常处理器uncaughtExceptionHandler的接口,并且每一个线性对象Thread都有一个属性:uncaughtExceptionHandler。如果一个线程内部产生了异常,并且没有try,catch来处理,最终会被uncaughtExceptionHandler这个对象捕获。如果uncaughtExceptionHandler这个对象为null的话,这个异常就无法被捕获。
示例:
public class MyUncaughtExceptionHandler implements
Thread.UncaughtExceptionHandler {
private String name;
public MyUncaughtExceptionHandler(String name) {
this.name = name;
}
@Override
public void uncaughtException(Thread t, Throwable e) {
Logger logger = Logger.getAnonymousLogger();
logger.log(Level.WARNING, "线程异常终止了" + t.getName(), e);
System.out.println(name + "捕获了异常" + t.getName() + "异常" + e); }}
public class MyUncaughtExceptionHandler implements
Thread.UncaughtExceptionHandler {
private String name;
public MyUncaughtExceptionHandler(String name) {
this.name = name;
}
@Override
public void uncaughtException(Thread t, Throwable e) {
Logger logger = Logger.getAnonymousLogger();
logger.log(Level.WARNING, "线程异常终止了" + t.getName(), e);
System.out.println(name + "捕获了异常" + t.getName() + "异常" + e);
}
}
public class UseOwnUncaughtExceptionHandler implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler("捕获器1"));
new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-1").start();
Thread.sleep(300);
new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-2").start();
Thread.sleep(300);
}
@Override
public void run() {
throw new RuntimeException();
}
}
结果:
捕获器1捕获了异常MyThread-1异常java.lang.RuntimeException
Feb 27, 2020 8:09:21 PM uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: 线程异常终止了MyThread-1
java.lang.RuntimeException
at uncaughtexception.UseOwnUncaughtExceptionHandler.run(UseOwnUncaughtExceptionHandler.java:23)
at java.lang.Thread.run(Thread.java:745)
Feb 27, 2020 8:09:21 PM uncaughtexception.MyUncaughtExceptionHandler uncaughtException
警告: 线程异常终止了MyThread-2
java.lang.RuntimeException
at uncaughtexception.UseOwnUncaughtExceptionHandler.run(UseOwnUncaughtExceptionHandler.java:23)
at java.lang.Thread.run(Thread.java:745)
捕获器1捕获了异常MyThread-2异常java.lang.RuntimeException
Feb 27, 2020 8:09:21 PM uncaughtexception.MyUncaughtExceptionHandler uncaughtException
9、死锁的解决方案
什么是死锁?
首先是一个线程需要多把锁,并发的时候多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。
如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
例子:
public class DeadLockDemo {
private static Object resource1 = new Object();//资源 1
private static Object resource2 = new Object();//资源 2
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
}, "线程 2").start();
}
}
结果:
Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2
Thread[线程 1,5,main]waiting get resource2
Thread[线程 2,5,main]waiting get resource1
产生死锁的四个必要条件
- 互斥条件:该资源任意一个时刻只由一个线程占用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
避免死锁
- 破坏互斥条件: 这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
- 破坏请求与保持条件:
一次性申请所有的资源。- 破坏不剥夺条件:
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。- 破坏循环等待条件: 靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
解决方法:
改第二个线程
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 2").start();
结果:
Thread[线程 1,5,main]get resource1
Thread[线程 1,5,main]waiting get resource2
Thread[线程 1,5,main]get resource2
Thread[线程 2,5,main]get resource1
Thread[线程 2,5,main]waiting get resource2
Thread[线程 2,5,main]get resource2