java thread 线程
1、 创建线程
1.1、 Thread
继承 Thread,子类复写 run()
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if(i % 2 == 0 ) {
                System.out.println(Thread.currentThread().getName() + "" + i);
            }
        }
    }
}
Thread方法:
run(): 定义线程任务
start() : 启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务
currentThread(): 获取当前线程
getName() : 获取线程名
setName(String name) : 设置线程名
yield() : 当前获得的CPU时间片,回到就绪状态,这时与其他进程处于同等竞争状态
join(): 让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行
sleep(long millis) :  线程休眠
// 优先级高的线程会更高的概率使用cpu
// public final static int MIN_PRIORITY = 1;   最小优先级
// public final static int NORM_PRIORITY = 5;  默认优先级
// public final static int MAX_PRIORITY = 10;  最大优先级
getPriority() : 获取线程优先级
setPriority(int priority) : 设置线程优先级 
1.2 Runnable
实现 Runnable 接口, 复写 run() , 将该实现类的对象实例, 传给构造方法Thread(Runnable runnable)
class MyThread implements Runnable{ // 实现Runnable接口,作为线程的实现类
   private String name ;       // 表示线程的名称
   public MyThread(String name){
       this.name = name ;      // 通过构造方法配置name属性
   }
   public void run(){  // 覆写run()方法,作为线程 的操作主体
       for(int i=0;i<10;i++){
           System.out.println(name + "运行,i = " + i) ;
       }
   }
}
public class RunnableDemo01{
   public static void main(String args[]){
       MyThread mt1 = new MyThread() ;    // 实例化对象
       Thread t1 = new Thread(mt1) ;       // 实例化Thread类对象
       Thread t2 = new Thread(mt1) ;       // 实例化Thread类对象
        t1.setName("thread1")
        t2.setName("thread2")
       t1.start() ;    // 启动多线程
       t2.start() ;    // 启动多线程
   }
};
2 线程生命周期(状态)
1)新建:线程被创建出来,为调用 start()
2)就绪:调用start(),进入线程队列等待 cpu
3)运行:获取cpu使用权,进入运行状态
4)阻塞:在特殊情况下,被人为挂起或执行输入输出操作时,让出cpu 并暂时中断自己的执行,进入阻塞状态
5)死亡:线程完成了全部任务,或 被提前强制中止 或 出现异常 而结束线程。对于已经死亡的线程,无法再使用start方法令其进入就绪。
状态变化图:

3 线程安全
当操作共享数据时,有可能出现线程安全问题,解决方法: 使用同步,使得响应代码在同一时间只能一个线程执行。
三种方式:
1)同步代码块: synchronized(同步监视器) { /操作数据代码/ }
2)同步方法: 使用synchronized 修饰方法
3)使用Lock (ReentrantLock)
显而易见,使用同步后,总体执行效率会变低。
3.1 同步代码块
同步监视器,俗称,同步锁。同步锁可以是任何对象,但此对象必须相同唯一,即每个线程的锁是同一个。获取到锁的线程执行相应代码
class MyThread implements Runnable{
    private int ticket = 5 ;    // 假设一共有5张票
    public void run(){
        for(int i=0;i<100;i++){
            synchronized(this){ // 要对当前对象进行同步, 同步对象可以是任何对象。可以使用 this , 或 类对象 如 MyThread.class
                if(ticket>0){   // 还有票
                    try{
                        Thread.sleep(300) ; // 加入延迟
                    }catch(InterruptedException e){
                        e.printStackTrace() ;
                    }
                    System.out.println("卖票:ticket = " + ticket-- );
                }
            }
        }
    }
};
public class SyncDemo02{
    public static void main(String args[]){
        MyThread mt = new MyThread() ;  // 定义线程对象
        Thread t1 = new Thread(mt) ;    // 定义Thread对象
        Thread t2 = new Thread(mt) ;    // 定义Thread对象
        Thread t3 = new Thread(mt) ;    // 定义Thread对象
        t1.start() ;
        t2.start() ;
        t3.start() ;
    }
};
3.2 同步方法
当同步代码刚好是一个完整的部分时,可以使用 同步方法。同步方法的 同步对象不需要显示设置,非静态同步方法的同步对象是 this, 静态同步方法同步对象是 该类对象本身(MyThread.class)
class MyThread implements Runnable{
    private int ticket = 5 ;    // 假设一共有5张票
    public void run(){
        for(int i=0;i<100;i++){
            this.sale() ;   // 调用同步方法
        }
    }
    public synchronized void sale(){    // 声明同步方法
        if(ticket>0){   // 还有票
            try{
                Thread.sleep(300) ; // 加入延迟
            }catch(InterruptedException e){
                e.printStackTrace() ;
            }
            System.out.println("卖票:ticket = " + ticket-- );
        }
    }
};
public class SyncDemo03{
    public static void main(String args[]){
        MyThread mt = new MyThread() ;  // 定义线程对象
        Thread t1 = new Thread(mt) ;    // 定义Thread对象
        Thread t2 = new Thread(mt) ;    // 定义Thread对象
        Thread t3 = new Thread(mt) ;    // 定义Thread对象
        t1.start() ;
        t2.start() ;
        t3.start() ;
    }
};
同步的代码 不能多也不能少,要恰好。少了不能解决线程安全问题,多了降低效率
注意 通过 继承 Thread 创建的线程 和 实现 Runnable 的线程 在同步是略有不同的。要注意同步锁唯一。
通过 继承 Thread 创建的线程 的 同步方法需要是静态的static,这样才能保证 锁 唯一。
一个同步锁在同一个时刻只能供一个线程使用。
如果多个不同的线程有不同的同步代码但共用一个同步锁,那么谁先抢到锁谁运行,即这些不同的代码同一个时刻只能允许一个在执行。这线程不管是不是同一个类产生。
如下,Demo 中的 method1() method2() 在 t1 t2 线程启动后,只能有一个在运行
class Demo {
      public synchronized void method1(){    // 声明同步方法
           // 同步内容
      }
      public synchronized void method2(){    // 声明同步方法
           // 同步内容
      }
}
class Thread1 implements Runnable{
    private  Demo  demo;
    public Thread1 (Demo  demo) {
        this.demo= demo;
    }   
    public void run(){
          demo.method1()
   }
}
class Thread2 implements Runnable{
    private  Demo  demo;
    public Thread2 (Demo  demo) {
        this.demo= demo;
    }   
    public void run(){
          demo.method2()
   }
}
public class SyncDemo{
    public static void main(String args[]){
        Demo demo = new Demo()
        Thread1 mt1 = new Thread1 (demo) ; 
        Thread2 mt2 = new Thread2 (demo) ;  
        Thread t1 = new Thread(mt1) ;   
        Thread t2 = new Thread(mt2) ;   
        t1.start() ;
        t2.start() ;
     
    }
};
3.3 ReentrantLock 锁
ReentrantLock 是 Lock 接口的实现类。 主要是 调用 lock.lock() 上锁, lock.unlock() 释放锁,处于二者之间的代码就是同步的
与 synchronized 修饰的 代码块 和 方法 区别:是需要手动释放锁
class MyThread extends Thread {
    private ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            lock.lock();
            if(i % 2 == 0 ) {
                System.out.println(Thread.currentThread().getName() + "" + i);
            }
            lock.unlock();
        }
    }
}
3.4 死锁
就是两个线程都在等待对方先完成,造成程序的停滞,都处于阻塞状态
一个同步锁在同一个时刻只能供一个线程使用
一般出现死锁的情况: 线程中使用了有多个不同的锁,多个线程都在等在对方释放锁
尽量减少同步资源, 避免同步嵌套
4 线程通信
主要是相关方法的配套使用:wait() notify() notifyAll()
wait() : 执行此方法后,当前线程进入阻塞状态,并释放同步锁
notify(): 唤醒一个被 wait 的线程。 如果有多个被 wait 的线程, 唤醒优先级高的线程
notifyAll(): 唤醒所有被 wait 的线程
注意:wait() notify() notifyAll()
这三个方法是定义在 java.lang.Object 类中的,即所有对象都有此方法
这三个方法必须在 同步代码块 或 同步方法中使用
这三个方法的调用者是 同步监视器,即 同步锁
wait() sleep() 方法区别:都会使当前线程进入阻塞状态,但 sleep() 方法是 线程Thread的方法,可以在任何位置调用,并且如果在同步代码中调用,不会释放同步锁
class MyThread extends Thread {
    private int ticket = 100;
    private Object obj = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (obj) {
                obj.notify();
                if(ticket > 0 ) {
                    System.out.println(Thread.currentThread().getName() + ":" + ticket);
                    ticket--;
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                } else {
                    break;
                }
            }
        }
    }
}
public class ThreadTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread t1 = new Thread(myThread);
        Thread t2 = new Thread(myThread);
        t1.setName("线程1");
        t2.setName("线程2");
        t1.start();
        t2.start();
    }
}
还有 一对方法: suspend() 和 resume()方法, 两个方法配套使用
 suspend() : 使得线程进入阻塞状态,并且不会自动恢复
 resume():使得被suspend()挂起的线程重新进入可执行状态
但这两个 API 是过期的,也就是不建议使用的。
不推荐使用 suspend() 去挂起线程的原因,是因为 suspend() 在导致线程暂停的同时,并不会去释放任何锁资源。其他线程都无法访问被它占用的锁。直到对应的线程执行 resume() 方法后,被挂起的线程才能继续,从而其它被阻塞在这个锁的线程才可以继续执行。
但是,如果 resume() 操作出现在 suspend() 之前执行,那么线程将一直处于挂起状态,同时一直占用锁,这就产生了死锁。而且,对于被挂起的线程,它的线程状态居然还是 Runnable。
5 使用Callable 接口创建线程
java5开始,提供了Callable接口,是Runable接口的增强版。同样用Call()方法作为线程的执行体,增强了之前的run()方法。因为call方法可以有返回值,也可以声明抛出异常。
Future接口: java5提供了Future接口来代表Callable接口里的call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口,所以这样可以作为Thread的target。
步骤
1.创建Callable的实现类,并冲写call()方法,该方法为线程执行体,并且该方法有返回值
2.创建Callable的实例,并用FutuerTask类来包装Callable对象,该FutuerTask封装了Callable对象call()方法的返回值
3.实例化FutuerTask类,参数为FutuerTask接口实现类的对象来启动线程
4.通过FutuerTask类的对象的get()方法来获取线程结束后的返回值
public class NewThread  {
    public static void main(String[] args) {
 
        //先使用Lambda表达式创建Callable<Integer>对象,使用泛型
        //并使用FutureTask来包装Callable对象
        FutureTask<Integer> task=new FutureTask<Integer>((Callable<Integer>)()->{
           
             //这里相当于call方法执行体。
            int i=0;
            for (i = 0; i <10 ; i++) {
                System.out.println(Thread.currentThread().getName()+"====="+i);
            }
            return  i;
        });
 
        //创建一个线程,并start启动它。
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"======="+i);
            if(i==0){
                Thread t1=new Thread(task,"我是fu线程");
                t1.start();
            }
        }
 
       //得到返回值,注意这个必须要有显示抛出异常
        try{
            System.out.println("子线程的返回值"+task.get());
        }catch (Exception e){
            e.printStackTrace();
        }
 
    }
}
6 Executors 线程池
参考
https://www.cnblogs.com/java1024/archive/2019/11/28/11950129.html
https://www.cnblogs.com/jijijiefang/articles/7222955.html
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号