25.Java中的Thread线程


Thread实现方式一:


1.继承Thread,创建Thread的子类
2.重写子类的run方法
3.创建该子类的对象
4.启动线程(对象),对象调用 start()方法
 
注意事项:
1. 一个Thread类(Thread子类)对象 代表 一个线程
2. 为什么我们重写Thread类中的run方法
只有Thread run()方法中的代码,才会执行在子线程中。为了保证子线程中运行的是我们想要在子线程中运行的代码.
3. 但是,如果想要让代码在子线程中运行,代码并非一定要写在run方法方法体中。 对于定义在该Thread子类中其他方法方法体中的代码,也可以运行在子线程。换句话说,一个方法被哪个线程中的代码调用,被调用的方法,就运行在调用它的线程中。
4,启动线程,必须使用start()方法来启动,这样才能使Thread中的run方法运行在子线程中。如果通过调用run方法,来执行Thread的run方法代码,这仅仅只是普通的方法调用,无法启动线程。
5. 同一个Thread或Thread子类对象(代表同一个线程),只能被启动一次。如果,我们要启动多个线程,只能创建多个线程对象,并启动这些线程对象一次且仅一次。

public class Demo1 {

  public static void main(String[] args) {
    //3. 创建该子类对象(该Thread子类对象,代表一个线程)
    MyFirstThread thread = new MyFirstThread();

    //4. 启动线程(对象)
    thread.start();

    // 错误的启动线程的方式,如果直接调用run方法,这仅仅相当于,在main所在线程中,调用了一下run方法
    //thread.run();

    // 试图将一个线程启动多次
    //thread.start();  // IllegalThreadStateException
    //要启动多个线程,必须创建多个线程对象
    MyFirstThread second = new MyFirstThread();
    second.start();
    System.out.println("second start");
  }

}

//1. 定义Thread的子类
class MyFirstThread extends Thread {

  //2. 在子类中重写Thread的run方法(要在线程中执行的代码,都写在run方法中)
  @Override
  public void run() {
    // 在该run方法中,写我们自己要运行在子线程中的代码
    System.out.println("hello thread");

    // 在run方法中,调用的其他方法,也会运行在子线程中
   testCall();
  }


  private void testCall() {
    System.out.println("testCall");
  }
}

线程信息API

1.线程名
设置或获取线程的名称
public final String getName()
public final void setName(String name)
 

2.static Thread currentThread()
返回对当前正在执行该方法的线程对象的引用。
对于currentThread()方法而言,该方法在哪个线程中被调用,哪个线程就是该方法的当前线程。

public class Demo1Name {

  public static void main(String[] args) {

    NameThread nameThread = new NameThread();
    // 获取线程名称
    System.out.println(nameThread.getName());  //Thread-0

    //修改线程名称
    nameThread.setName("NameThread");
    System.out.println(nameThread.getName());

    //启动线程
    //nameThread.start();

    // 利用Thread类中到的静态方法currentThread(),获取到了main所在线程的名称
    Thread thread = Thread.currentThread();
    System.out.println(thread.getName());


  }

}

class NameThread extends Thread {

  @Override
  public void run() {
    System.out.println(this.getName() + ": hello");
  }
}

3.优先级
多线程的优先级
public final int getPriority()
public final void setPriority(int priority)
 
注意事项:
1.多线程的优先级的取值范围 1 <= priority <=10
2.线程的默认优先级为5
3.然而, 给线程对象设置优先级,并没有什么用。我们在java语言中设置的线程优先级, 它仅仅只能被看做是一种"建议"(对操作系统的建议),实际上,操作系统本身,有它自己的一套线程优先级 (静态优先级 + 动态优先级)
4.结论,千万不要试图通过设置线程的优先级,控制线程的执行的先后顺序

线程控制API

1.public static native void sleep(long millis)
被native修饰的方法,称之为本地方法,本地方法都不是由java语言实现的。
1. 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
2. 指定的休眠时间是以毫秒为单位的休眠时间

public class Demo1Sleep {

  public static void main(String[] args) {
    SleepThread sleepThread = new SleepThread();
    sleepThread.start();
  }



}

class SleepThread extends Thread {

  @Override
  public void run() {
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("hello, sleep");
  }
}

2. public final void join()
当前线程(谁)等待该线程(谁)执行终止。
线程对象.join()
该线程:在哪个线程对象上调用join方法,该线程指的就是哪个线程对象所表示的线程
(1). 谁等待? 当前线程等待, join方法,在哪个线程中被调用,哪个线程就等待
(2). 等待谁? 该线程,在哪个线程对象上调用join方法,等待的就是哪个线程对象的线程。

public class Demo2Join {

  public static void main(String[] args) throws InterruptedException {

    // 创建线程对象
    JoinThread joinThread = new JoinThread();
    // 启动线程对象所表示的线程
    joinThread.start();

    // 使用join方法
    // 在main线程中被调用,main线程就是join方法本次调用的当前线程
    // 该线程指的就是joinThread线程
    joinThread.join();

    //begin
    //runnung
    //end
    //main end

    System.out.println("main end");

}

}

class JoinThread extends Thread {


  @Override
  public void run() {
    System.out.println("begin");

    try {
      Thread.sleep(5000);
      System.out.println("runnung");
    } catch (InterruptedException e) {
      e.printStackTrace();
    }


    System.out.println("end");
  }
}

3. public static native void yield()
暂时让当前线程放弃cpu的执行权
(1).暂停当前正在执行的线程对象(自己放弃cpu的使用权,yield方法可以实现)
( 2).并执行其他线程。(并执行其他线程, yield方法并未实现)

public class Demo3Yield {

  public static void main(String[] args) {
    YieldThread yieldThread1 = new YieldThread();
    yieldThread1.setName("yield-1");

    YieldThread yieldThread2 = new YieldThread();
    yieldThread2.setName("YIELD-2");

    yieldThread1.start();
    yieldThread2.start();


  }

}

class YieldThread extends Thread {

  @Override
  public void run() {
    for (int i = 0; i < 100; i++) {
      System.out.println(getName() + ": i = " + i);

      //礼让一下 cpu的使用权,但是下次谁使用cpu未确定,还是需要公平竞争
      Thread.yield();
    }
  }
}

4.public void interrupt()
中断线程的阻塞过程。
(1).如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的
join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法,在这个过程中受阻,调用
interrupt方法会打断当前的阻塞
(2).线程将收到一个 InterruptedException。

public class Demo5Interrupt {

  public static void main(String[] args) {
    // 创建线程对象
    InterruptThread thread = new InterruptThread();
    //启动线程
    thread.start();
    
    // 打断 子线程的阻塞
    thread.interrupt();

	//结果:
	//begin
	//被打断了
	//after interrupted

  }

}

class InterruptThread extends Thread {

  @Override
  public void run() {
    System.out.println("begin");

    try {
      Thread.sleep(5000);
      System.out.println("end");
      Thread.sleep(5000);
      //还有一堆代码
    } catch (InterruptedException e) {
      System.out.println("被打断了");
    }

    System.out.println("after interrupted");


  }
}


Thread实现方式二


1.定义实现Runnable接口的子类
2.实现Runnable接口的run方法
3.创建该子类对象
4.创建Thread对象,将创建好的Runnable子类对象作为初始化参数,传递给Thread对象
5.启动Thread对象(启动线程)
 
注意事项:
1. Runnable接口子类的run()方法代码,会运行在子线程当中。
2. 在线程的第二种实现方式中,我们自己定义子类,实现Runnable接口的run方法,
将要在子线程中执行的代码放在run()方法中
3. 但是,Runnable子类对象并不代表线程,它只代表要在线程中执行的任务。 把线程(Thread对象代表线程) 和在线程上执行的任务(Ruannable子类对象)分开描述。

public class Demo1Second {

  public static void main(String[] args) {
    //3. 创建Runnable接口子类对象
    MyRunnable myRunnable = new MyRunnable();

    // 4. 创建Thread类对象,并将Runnable 子类对象作为初始化参数,传递给Thread对象
    Thread thread = new Thread(myRunnable);
    // 5. 启动线程
    thread.start();
  }

}

// 1. 定义实现Runnable接口的子类
class MyRunnable implements Runnable {

  // 2. 实现接口的run方法
  @Override
  public void run() {
    System.out.println("hello, runnable");
  }
}

多线程的数据安全问题
两种思路构造原子操作(一个线程对共享数据的访问一次完成):
第1种思路:阻止线程切换,就不会发生多线程数据安全问题。思路没问题,但是我们做不到,这涉及到线程调度,在抢占式线程调度中做不到,线程调度有系统决定。
第2种思路:我们无法阻止线程切换,但是我们换个思路,我们给共享变量加一把锁,利用锁来实现原子操作。 从而保证:
a. 只有加锁的线程,能够访问到共享变量
b. 而且在加锁线程没有完成对共享共享变量的一组操作之前,不会释放锁
c. 只要不释放锁.其他线程即使被调度执行也无法访问共享变量。
所以,解决多线程数据安全问题 ——> 如何在java语言层面,加锁构造出原子操作?
 
同步代码块
synchronized (锁对象) {
一组要作为原子操作的代码(需要访问共享数据)
}

 
同步代码块的细节:
a. synchronized代码块中的 锁对象,可以是java语言中的任意对象(java语言中的任意一个对象,都可以充当锁的角色,仅限于synchronized代码块中):
1)因为java中所有对象,内部都存在一个标志位,表示加锁和解锁的状态
2)所以其实锁对象,就充当着锁的角色, 所谓的加锁解锁,其实就是设置锁对象的标志位,来表示加锁解锁的状态。
b. 我们的代码都是在某一条执行路径(某一个线程中运行),当某个线程执行到synchronized代码块时,
会尝试在当前线程中,对锁对象加锁
1) 此时,如果锁对象处于未加锁状态,jvm就会设置锁对象的标志位(加锁),并在锁对象中记录,是哪个线程加的锁。 然后,让加锁成功的当前线程,执行同步代码块中的代码
2) 此时,如果锁对象已经被加锁,且加锁线程不是当前线程,系统会让当前线程处于阻塞状态(等着), 直到加锁线程,执行完了对共享变量的一组操作,并释放锁
c. 加锁线程何时释放锁?
当加锁线程,执行完了同步代码块中的代码(对共享变量的一组操作),在退出同步代码块之前, jvm自动清理锁对象的标志位,将锁对象变成未上锁状态(释放锁)。
 
其实,加锁是在完成线程同步。
所以,最终我们是通过线程同步,来解决多线程的数据安全问题的。
线程同步的优缺点:
优点:解决了多线程线程安全问题。
缺点:相比于异步,因为等待锁资源而引发的阻塞,降低了程序运行效率。

public class Demo1 {

  public static void main(String[] args) {
    // 表示售票任务
    SalesTask salesTask = new SalesTask();

    Thread window1 = new Thread(salesTask);
    Thread window2 = new Thread(salesTask);
    Thread window3 = new Thread(salesTask);

    window1.start();
    window2.start();
    window3.start();
  }

}



// 线程的第二种方式,很好的实现了多线程的数据共享
class SalesTask implements Runnable {

  int tickets = 100;

  // 定义一个锁对象
  Object lockObj = new Object();

  @Override
  public void run() {


    while (tickets > 0) {
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

      // 一次完整卖票过程
      synchronized (lockObj) {
        // synchronized代码块中的代码,是一组原子操作
        if (tickets > 0) {  // double check, 双检查机制
          System.out.println(Thread.currentThread().getName() + "售出了第" + this.tickets-- + "张票");
        }
      }

    }
  }
}


注意,对同一个共享变量的访问,必须使用同一个锁对象。
 
同步方法
整个方法体是一个同步代码块,其效果等价下面的同步代码块
synchronized(锁对象对象) {
}

同步方法的锁对象是谁呢?
对于普通成员方法而言: 同步方法的锁对象是 this,当前对象(其锁对象是隐式给出的)
对象名.方法()
静态方法,可以不可以是同步方法呢? 可以
类名.静态方法() 静态方法依赖于类而存在,而jvm中一个类对应一个Class对象,所以一个静态方法锁对象就是表示静态方法所属类的Clsss对象(隐式给出)
*

public class Demo1 {

  public static void main(String[] args) {


    // 表示售票任务
    SalesTask salesTask = new SalesTask();

    Thread window1 = new Thread(salesTask);
    Thread window2 = new Thread(salesTask);
    Thread window3 = new Thread(salesTask);

    window1.start();
    window2.start();
    window3.start();

  }

}



class SalesTask implements Runnable {

  int tickets = 100;

  // 定义一个锁对象
  Object lockObj = new Object();

  @Override
  public void run() {


    while (tickets > 0) {
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

      // 有票就买一张票
      this.salesingleTicket();


    }
  }

  // 同步方法: 整个方法体是一个同步代码块
  private synchronized void salesingleTicket() {
    // 一次完整卖票过程
      // synchronized代码块中的代码,是一组原子操作
      if (tickets > 0) {  // double check, 双检查机制
        System.out.println(Thread.currentThread().getName() + "售出了第" + this.tickets-- + "张票");
      }
  }

  // 定义静态的同步方法
  public static synchronized void testStatic() {}
}

Lock锁对象 VS synchoronized 锁对象

区别
1. synchronized 锁对象,只提供了用来模拟锁状态的标志位(加锁和释放锁),但是加锁和释放锁的行为,都是由jvm隐式完成(和synchronized 锁对象没关系), 所以synchronized 锁对象不是一把完整的锁 。
2.一个Lock对象,就代表一把锁,而且还是一把完整的锁。 Lock对象,它如果要实现加锁和释放锁,不需要synchronized关键字配合,它自己就可以完成
Lock(接口):
lock() 加锁
unlock() 释放锁
 
联系:
都可以实现线程同步
1. synchronized(锁对象) {
需要同步的代码
}
2. lock.lock()
需要同步的代码
lock.unlock()

利用线程同步,解决多线程的数据安全问题,有两种方式

  1. synchronized + java中任意对象
  2. Lock锁对象

当我们要实现线程同步的时候,究竟选择哪种方式呢? 推荐使用 synchronized + java对象完成线程同步
3. 两种方式,实现的效果是相同,因为synchronize + java对象简单,易用。
4. 虽然,在jdk1.5, 1.6等早期版本中,Lock锁效率高一些,synchronized方式效率低一些,现在两种方式加锁,效率上的区别,几乎可以忽略。
 
Lock锁比较标准的用法
Lock lock = new ReentrantLock();
lock.lock();
try {
// access the resource protected by this lock
} finally {
// 不管异常还是正常执行,都可以正确的释放锁
lock.unlock();
}

public class Demo1 {

  public static void main(String[] args) {
    // 表示售票任务
    SalesTask salesTask = new SalesTask();

    Thread window1 = new Thread(salesTask);
    Thread window2 = new Thread(salesTask);
    Thread window3 = new Thread(salesTask);

    window1.start();
    window2.start();
    window3.start();
  }

}


// 线程的第二种方式,很好的实现了多线程的数据共享
class SalesTask implements Runnable {

  int tickets = 100;

  // 定义一个Lock锁对象
  Lock lock = new ReentrantLock();

  @Override
  public void run() {


    while (tickets > 0) {
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

      // 利用Lock锁完成线程同步
       lock.lock();
       try {
         if (tickets > 0) {  // double check, 双检查机制
           System.out.println(Thread.currentThread().getName() + "售出了第" + this.tickets-- + "张票");
         }
       } finally {
         lock.unlock();
       }



    }
  }
}

同步另一个弊端:如果出现了嵌套锁,可能产生死锁

比如: 某个线程要同时持有两把锁lockA 和 lockB两把锁,换个说法, 该线程,成功持有lockA锁的情况下,在持有lockB锁:
synchronized (lockA) {
//当某线程的代码,执行到这里
synchronized (lockB) {
// 执行到这里,意味着当前线程在持有lockA锁的情况下,又持有了lockB这把锁,所以此时当前线程就同时持有两把锁
}
}
 
解决死锁问题的第一种方式:调整获取锁的顺序,让多线程获取锁的顺序相同

public class Demo1 {
  public final static Object lockA = new Object();
  public final static Object lockB = new Object();

  public static void main(String[] args) {

    ABThread abThread = new ABThread();
    BAThread baThrad = new BAThread();

    abThread.start();
    baThrad.start();
  }


}

// 向访问共享变量,在访问打印机,将对共享变量计算的结果发送到打印机
class ABThread extends Thread {

  @Override
  public void run() {
    // 首先,尝试访问共享变量
    synchronized (Demo1.lockA) {

      try {
        Thread.sleep(100);
        System.out.println("ABThread 加 lockA锁");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }

      // 访问共享变量做计算,得到一些结果
      // 卡在这里
      synchronized (Demo1.lockB) {
        // 把共享变量计算的结果发送到打印机来打印
        System.out.println("ABThread 加 lockB锁");
      }

    }

  }
}

// 先获取打印机的访问权,在尝试访问共享变量,将共享变量计算结果发送到打印机打印
class BAThread extends Thread {

  @Override
  public void run() {
    // 首先,尝试访问打印机
    synchronized (Demo1.lockA) {
      //访问共享变量, 计算出结果,把结果发送到打印机打印

      try {
        Thread.sleep(100);
        System.out.println("BAThread 加 lockA锁");
      } catch (InterruptedException e) {
        e.printStackTrace();
      }


       synchronized (Demo1.lockB) {
         // 获取打印机的访问权
         System.out.println("BAThread 加 lockB锁");
       }
    }
  }
}

解决死锁问题的第二种方式:要么一个线程要么同时持有所有需要的多把锁,要么一把锁都不加。所以,第二种解决方案的实质,就是把加多把锁作为一个原子操作
在定义一把新的锁,利用这把锁(synchronized + 该对象),实现将加多把锁的操作变成一个原子操作。

public class Demo1 {
  public final static Object lockA = new Object();
  public final static Object lockB = new Object();
  public final static Object allLock = new Object();

  public static void main(String[] args) {

    ABThread abThread = new ABThread();
    BAThread baThrad = new BAThread();

    abThread.start();
    baThrad.start();
  }


}

// 向访问共享变量,在访问打印机,将对共享变量计算的结果发送到打印机
class ABThread extends Thread {

  @Override
  public void run() {


    synchronized (Demo1.allLock) {
      // 首先,尝试访问共享变量
      synchronized (Demo1.lockA) {
        try {
          Thread.sleep(100);
          System.out.println("ABThread 加 lockA锁");
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        // 访问共享变量做计算,得到一些结果
        // 卡在这里
        synchronized (Demo1.lockB) {
          // 把共享变量计算的结果发送到打印机来打印
          System.out.println("ABThread 加 lockB锁");
        }

      }
    }

  }
}

// 先获取打印机的访问权,在尝试访问共享变量,将共享变量计算结果发送到打印机打印
class BAThread extends Thread {

  @Override
  public void run() {

    synchronized (Demo1.allLock) {
      // 首先,尝试访问打印机
      synchronized (Demo1.lockB) {
        // 获取打印机的访问权
        try {
          Thread.sleep(100);
          System.out.println("BAThread 加 lockB锁");
        } catch (InterruptedException e) {
          e.printStackTrace();
        }

        synchronized (Demo1.lockA) {
          //访问共享变量, 计算出结果,把结果发送到打印机打印
          System.out.println("BAThread 加 lockA锁");
        }
      }
    }

  }
}

wait()方法
作用:
导致当前线程等待 。在哪个线程中调用wait()方法,当前线程就会处于阻塞状态。
其他线程调用此对象的 notify() 方法或 notifyAll() 方法,才可以唤醒被wait()阻塞的线程。
调用条件:
当前线程必须拥有此对象监视器(锁对象 Monitor) ,即当前线程必须持有该对象的锁
调用时发生了什么:
该线程发布对此对象监视器的所有权并等待,。当前线程首先放弃对锁对象的持有,阻塞自己,让自己处于等待状态。
唤醒条件:
直到其他线程通过调用 notify 方法或 notifyAll 方法 通知 在此对象的监视器上(锁对象) 等待的线程 醒来。
如果要唤醒,在某对象上阻塞的线程,必须在其他线程中在同一(线程阻塞的)对象上,调用 notify() 方法 或 notifyAll()方法唤醒该线程

public class Demo1 {

  public static void main(String[] args) throws InterruptedException {

    Demo1 demo1 = new Demo1();
    // 不是任意对象的wait方法都可以正常执行的
    //demo1.wait();

    // 正确的用法,在当前线程持有的锁对象上,调用该wait方法才能正常执行
    synchronized (demo1) {

      if (true) {
        // 某种条件下克制自己
        demo1.wait();
      }

    }

    // 自己无法唤醒自己,只能是别人来唤醒,wait方法之后的代码,在线程被唤醒之前,不会执行
    demo1.notify();

    System.out.println("after wait");

  }

}

notify(),notifyall()
通知别人:
对象(锁对象).notify()
public final void notify():
1.唤醒在此对象监视器上等待的单个线程
2.如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。
3. 选择是任意性的,并在对实现做出决定时发生

对象(锁对象).notifyAll()
public final void notifyAll()
唤醒在此对象监视器上等待的所有线程.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值