Java Thread Basic

时间:2023-01-16 22:24:40

一、Java的多线程有三种实现方式。

  1、继承创建.

    a、定义子类,重写run方法

    b、创建Thread子类的实例(即现成对象)

    c、调用start() 方法启动现成

    特征:不可以共享变量。

 public class FirstThreadByExtends extends Thread {
private int i; public FirstThreadByExtends(){
super();
}
public FirstThreadByExtends(String name){
super(name);
}
public void run() {
for (; i < 100; i++) {
System.out.println(getName() + " " + i);
}
} public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println("当前线程的名字: " + Thread.currentThread().getName());
if (i == 20) {
new FirstThreadByExtends("线程" + i).start();
}
}
} }

    Note: 每次都是new出来的对象,没有共享变量。

  2、实现Runable接口.

    a、定义Runnable接口实现类,重写run()方法

    b、创建Runnable实现类实例,作为Thread实例的Target

    c、调用start() 方法启动现成

    特征:可以共享变量,因为可以用同一个Target创建多个线程。

 public class SecondThreadByImplementsRunnable implements Runnable {
private int i; public void run() {
for (; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("当前线程的名字a: " + Thread.currentThread().getName());
if (i == 5) {
SecondThreadByImplementsRunnable st = new SecondThreadByImplementsRunnable();
new Thread(st,"新线程1").start();
new Thread(st,"新线程2").start();
}
}
}
}

SecondThreadByImplementsRunnable

  3、使用Callable和future创建线程

    a、创建Callable实现类,实现call()方法

    b、创建Callable实现类的实例,用FutureTask实现类来包装

    c、使用FutureTask作为Thread的Target来创建对象

    d、通过调用FT的get()方法来获取线程的返回值

    特征:可以有返回值,可以抛出异常

 public class ThirdThreadByImplementsCallable implements Callable<Long> {
public Long call() throws Exception {
int i = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return System.currentTimeMillis();
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThirdThreadByImplementsCallable tt = new ThirdThreadByImplementsCallable();
FutureTask<Long> task = new FutureTask<Long>(tt);
for (int i = 0; i < 100; i++) {
System.out.println("当前线程的名字: " + Thread.currentThread().getName() +" "+ i);
if (i == 5) {
new Thread(task,"有返回值的线程1").start();
}
if (i == 6) {
System.out.println("----------------------test---------------------------------");
new Thread(task,"有返回值的线程2").start();
}
}
System.out.println("子线程的返回值:" + task.get());
}
}

    Note: 第二个线程实例没有跑起来,情况未知,待解决!

三种方式的对比

  采用实现Ruannable或Callable接口的方式创建:

    a、线程类只是实现了Runnable或Callable接口,还可以继承其他类

    b、多个线程可以共享一个Target对象,适合多个相同线程处理统一份资源,从而将CPU,代码,数据分开,形成清晰的模型,较好的体现了面向对象的思想

    c、劣势是 编程较复杂,如需访问当前现成还要调用 Thread.CurrentThread()方法.

  继承Thread类的方式创建多线程:

    a、优势,编写简单,可直接调用this获得当前线程

    b、劣势,因继承了Thread,无法再继承其它类

二、线程生命周期

  线程的生命周期包括5个状态:新建(new),就绪(Runnable),运行(Running),阻塞(Blocked),死亡(Dead)。

  a、新建和就绪

    新建:当使用了new关键字创建了一个线程,该线程就处于新建状态。

    就绪:当线程调用了start()方法,该线程就处于就绪状态,JVM会为其创建方法调用栈和程序计数器。该状态表示可以运行,准备被JVM线程调度器调度。

    注意:启动线程的方法是调用线程实例的start(),如果直接调用run()方法,系统会把线程对象,当成一个普通对象,run()方法也会被当成普通方法,而不是线程执行体。

 package lifecycle;

 public class InvokeRun extends Thread {
private int i; public void run() {
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
} public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 20) {
//直接调用线程对象的Run方法
//系统会把线程对象当作普通对象,把run方法当成普通方法,
//所以,一下两行代码不会启动两个线程,而是依次执行两个Run方法
new InvokeRun().run();
new InvokeRun().run();
}
}
}
}

InvokeRun

  b、运行和阻塞

    运行:就绪状态时,获得了CPU,该线程就处于运行状态,如果计算机只有一个CPU,那么在任何时刻只有一个线程处于运行状态。当然,在一个多处理器的机器上,将会有多个线程并行执行;但当线程数大于CPU数,也会出现轮换。具体线程调度细节取决于底层平台所采用的策略。

    运行==>阻塞:

          a、线程调用sleep()方法主动放弃所占用的资源

          b、线程调用了一个阻塞式IO,在该方法返回之前,该线程被阻塞

          c、线程试图获得一个同步监视器,但该同步监视器被其它线程所持有。关于同步监视器的知识、后面将有更深入的介绍

          d、线程在等待某个通知(notify)

          e、程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法

    阻塞==>运行:

          a、sleep()方法超过了指定的时间

          b、线程调用的阻塞式IO已经返回

          c、线程成功获得了试图获得的同步监视器

          d、线程正在等待某个通知时,其它线程发出了一个通知

          e、处于挂起状态的的线程被调用了resume()方法

  c、线程死亡

    a、run()或call方法执行完成,线程正常结束

    b、线程抛出一个未捕获的Exception或Error

    c、直接调用线程的Stop()方法结束线程,容易死锁,不推荐使用

    注意:不要试图对已经死亡的线程调用start()方法,会抛出不合法线程状态异常

 public class StartDead implements Callable<Long> {
private int i; public static void main(String[] args) throws InterruptedException, ExecutionException {
StartDead sd = new StartDead();
FutureTask<Long> ft = new FutureTask<Long>(sd);
Thread t1 = new Thread(ft, "马上会死的线程,哇哈哈哈哈");
//t1.setPriority(Thread.MAX_PRIORITY);
t1.start();
//get方法会阻塞main方法
System.out.println(ft.get());
for (int i = 0; i < 300; i++) {
if (i == 20) {
System.out.println(t1.isAlive() + "Love is forever!");
}
}
if (!t1.isAlive()) {
t1.start();
}
} public Long call() throws Exception {
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
return 100L;
} }

StartDead

三、线程控制

  

  1、join()线程

    Thread提供了一个线程等待另一个线程完成的方法 join(),当在某个程序执行流中调用了其它线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的线程执行完成为止。

    join()方法由使用线程的程序调用,将大问题划分成许多小问题,每一个小问题分配一个线程。当所有小问题都得到处理以后,再调用主线程进行进一步操作。

    join()方法有三种重载形式:

      join(),等待被join的线程执行完成。

      join(long millis),等待被join的线程的时间最长为millis毫秒。如果在millis毫秒之内被join的线程还没有执行结束,则不再等待。

      join(long millis,int nanos),比上面多了个微秒。

 public class JoinThread extends Thread {
public JoinThread(String name) {
super(name);
} public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.getName() + " " + i);
}
} public static void main(String[] args) throws InterruptedException {
new JoinThread("新线程").start();
for (int i = 0; i < 100; i++) {
if (i == 20) {
JoinThread jt = new JoinThread("被Join的线程");
jt.start();
jt.join(0);
}
System.out.println(Thread.currentThread().getName() + " " + i);
}
} }

JoinThread

  2、后台线程

    所谓的后台线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。因此当所有的非后台线程结束时,程序也就终止了,同时会杀死所有后台线程。反过来说,只要有任何非后台线程(用户线程)还在运行,程序就不会终止。后台线程在不执行finally子句的情况下就会终止其run方法。后台线程创建的子线程也是后台线程。

    下面是一个后台线程的示例:

 public class Daemon extends Thread {
public Daemon(){ }
public Daemon(String name){
super(name);
} public void run(){
for (int i = 0; i < 1000; i++) {
System.out.println(this.getName() + " " + i);
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} public static void main(String[] args) throws InterruptedException {
Thread t = new Daemon("守护线程");
t.setDaemon(true);
t.start();
for (int i = 0; i < 10; i++) {
sleep(200);
System.out.println(Thread.currentThread().getName() + " " + i);
}
//Program end here
//and Daemon is end follow it.
System.out.println("啊哦,守护线程应该死了了吧!");
}
}

Daemon

  3、线程睡眠:Sleep()

    让线程暂停,进入阻塞状态。它是Thread的静态方法。

 public class SleepThread {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
Thread.sleep(300);
}
}
}

SleepThread

  4、线程让步:yield()

    让线程进入就绪状态,等待CPU重新调度。不过在此期间,只有 相同或更高优先级的进程能被调用,不然的话会继续执行。

  5、线程优先级

    范围 1 ~ 10,三个静态常量  MAX_PRIORITY:10, MAX_PRIORITY:1, MAX_PRIORITY:5.

    t.setPriority(MAX_PRIORITY);

四、线程同步

  1、线程安全

    银行取钱问题。

      a. 用户输入帐号、密码,系统验证是否通过

      b. 用户输入取款金额

      c. 系统判断账户余额是否大于取款金额

      d. 如果余额大于取款金额,成功;  如果余额小于取款金额,取款失败。

    以下是没有同步控制的代码,也许有时候会正确,但只要有一次错误,那整个代码就是错误的。

  2、同步代码快

    synchronized (obj) {},  obj就是同步监视器,线程执行之前必须获得同步监视器的锁定。

    流程:加锁==>操作==>释放锁。

         synchronized (account) {
// If balance > drawAmount, check out!
if (account.getBalance() > drawAmount) {
System.out.println(this.getName() + "取钱成功!吐出钞票:" + this.drawAmount);
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
account.setBalance(account.getBalance() - this.drawAmount);
System.out.println("\t余额为:" + account.getBalance());
} else {
System.out.println(getName() + "余额不足,取款失败!");
}
}

  3、同步方法

    用同步方法可以非常方便的实现线程安全类。任意线程都可以安全访问。但无法显示指定同步监视器,同步监视器就是本身。

    代码如下:

      public synchronized void draw(double drawAmount) {
// If balance > drawAmount, check out!
if (this.getBalance() > drawAmount) {
System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" +
drawAmount);
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
this.setBalance(this.getBalance() - drawAmount);
System.out.println("\t余额为:" + this.getBalance());
} else {
System.out.println(Thread.currentThread().getName() + "余额不足,取款失败!");
}
}

  4、释放同步监视器的锁定

    a.同步方法、同步代码快执行结束,当前线程释放同步监视器。

    b.break、reutrn终止了该代码块,该方法的继续执行,当前线程会释放同步监视器。

    c. 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、该方法的异常结束时,当前线程将会释放同步监视器。

    d. 当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait方法,则当前线程暂停,并释放同步监视器。

    以下不会释放:

    a.sleep().yield();

    b.suspend(). resume();  容易弄成死锁,不推荐

  5、同步锁

    lock.lock(),  lock.unlock();

 lock.lock();
try {
// If balance > drawAmount, check out!
if (this.getBalance() > drawAmount) {
System.out.println(Thread.currentThread().getName() + "取钱成功!吐出钞票:" + drawAmount);
try {
Thread.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
this.setBalance(this.getBalance() - drawAmount);
System.out.println("\t余额为:" + this.getBalance());
} else {
System.out.println(Thread.currentThread().getName() + "余额不足,取款失败!");
}
} finally {
lock.unlock();
}

  6、死锁

    资源的相互依赖,你把我要用的给锁了,我把你要用的给锁了,我们各自拿着一块等待对方,谁都不肯释放。