java线程学习笔记

1.线程的基本概念

线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。线程有就绪、阻塞和运行三种基本状态。

要点:用户每启动一个进程,操作系统就会为该进程分配一个独立的内存空间。请注意是独立的内存空间。

2.线程的生命周期:

image

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

你可能感兴趣的:(java线程学习笔记)