Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

时间:2024-04-09 15:04:03

1、Java使用Thread类代表线程。

    所有的线程对象必须是Thread类或其子类的实例。

当线程继承Thread类时,直接使用this即可获取当前线程,Thread对象的getName()方法返回当前线程的名字,因此可以直接调用getName()方法返回当前线程的名字。

Thread.currentThread():该方法总是返回当前正在执行的线程对象。

2、创建线程方式1:继承Thread类创建线程类

这种方式创建线程的步骤一般为:

1》定义Thread类的子类,并重写该类的run()方法,该方法作为线程的线程执行体。

2》创建Thread子类的实例,即线程对象。

3》调用线程对象的start()方法启动线程。

举个例子:

public class extendsThread extends Thread{

  ...
@Override
public void run(){ ...
//do something
System.out.println(this.getName());
} public static void main(String[] agrs){ //创建并启动第一个线程
new extendsThread().start();
//创建并启动第二个线程
new extendsThread().start();
}
}

3、创建线程方式2:实现Runnable接口

这种方式创建线程的步骤一般为:

1》定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

2》创建Runnable接口实现类的实例,并将该实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。本质是Thread对象负责执行实现类对象的run()方法体。

3》调用线程对象的start()方法来启动该线程。

举个例子:

public class ImplRunnable implements Runnable{

   ...
@Override
public void run(){ ...
//do something
System.out.println(Thread.currentThread().getName());
   }

   public static void main(String agrs){

      ...
System.out.println(Thread.currentThread().getName()); ImplRunnable target=new ImplRunnable();
//创建并启动第一个线程
new Thread(target,"Thread1").start();
//创建并启动第二个线程
new Thread(target,"Thread2").start();
}
}

4、使用Callable和Future创建线程:创建有返回值的线程

这种方式创建线程的步骤一般为:

1》创建Callable接口的实现类,并实现call()方法,该方法作为线程执行体,且该方法有返回值。然后创建该实现类的实例。

2》使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

3》使用FutureTask对象作为Thread对象的target创建并启动新线程。

4》调用FutureTask对象的get()方法获取子线程执行结束后的返回值。

举个例子:

public class ImplCallable implements Callable<Integer>{

   ...
@Override
public Integer call(){ ...
//do something
System.out.println(Thread.currentThread().getName());
//return a value.
return 1;
} public static void main(String[] agrs) throws InterruptedException, ExecutionException{ ...
//创建并启动一个线程
FutureTask<Integer> target=new FutureTask<Integer>(new ImplCallable());
new Thread(target,"Thread3 with value.").start();
//获取返回值
System.out.println(target.get());
}
}

5、线程的生命周期

Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

线程进入阻塞状态:

Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

线程的死亡:

Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

当主线程结束时,其他线程不受任何影响,并不会随之结束。一旦子线程启动起来之后,它就拥有和主线程相同的地位,它不会受主线程的影响。可以调用线程对象的isAlive()方法,当线程处于就绪、运行、阻塞三种状态时,该方法返回true;当线程处于新建、死亡两种状态时,该方法返回false。不要试图对一个已经死亡的线程调用start()方法使它重新启动,死亡的线程不能再次作为线程执行。

6、控制线程

1》join():线程。Thread的静态方法。

A线程调用B线程的join()方法,A线程将被阻塞,直至B线程执行结束。join()方法有如下三种重载方式:

1>join():等待被join的线程执行完毕。

2>join(long millis):等待被join的线程的事时间最长为millis毫秒,如果millis毫秒被join的线程依然没有结束,则不再等待。

3>join(long millis,int nanos):等待被join的线程的时间最长为millis毫秒加nanos毫秒。(一般计算机硬件、操作系统本身无法做到这么精确)。

2》setDaemon(true):后台线程、守护线程、精灵线程

如果所有的前台线程都死亡了,后台线程也会自动死亡。调用Thread对象的setDaemon(true)方法可以将指定线程设置成后台线程。

Thread还提供了isDaemon()方法,用于判断指定线程是否为后台线程。

前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。

3》sleep():让线程睡眠。Thread的静态方法。

Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

在指定的睡眠时间内,即使系统中没有其他线程可执行,处于sleep()中的线程也不会执行。sleep()方法通常用来暂停线程。

4》yield():线程让步。Thread的静态方法。可以让线程暂停,但不会阻塞线程,线程会进入就绪状态,有可能立即又得到执行。当线程执行yield()方法后,优先级与之相等或者比它高的线程才会得到执行的机会。

比较:

Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

5》改变线程的优先级

每个线程默认的优先级与创建它的父线程的优先级相同。默认情况下,main线程具有普通优先级。

Thread提供了setPriority(int newPriority)、getPriority()方法来设置和返回指定线程的优先级,其中setPriority(int newPriority)方法的参数可以是一个整数,范围1~10,值越大优先级越高。Thread也提供了三个静态常量设置的值:

Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

7、线程同步:方式(1)synchronized的同步代码块、同步方法;方式(2)同步锁(Lock或ReadWriteLock接口的实现类)。

1》同步代码块:

//其中的obj就是同步监视器。
//线程在执行到下面的代码块的时候,必须先获得对同步监视器的锁定。
//任何时刻只能有一个线程可以获得同步监视器的锁定,同步代码块执
//行结束,同步监视器自动被释放锁定。
sychronized(obj){
...
}

2》同步方法:

使用sychronized修饰的某个方法(可能是实例方法,也可能是静态方法)。

1>修饰实例方法:无需显式指定同步监视器,同步方法的同步监视器就是this,即调用该方法的实例对象。

2>修饰静态方法:。。。

Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

 ==》注意:sychronized可以修饰代码块、成员方法。但是不能修饰构造器、成员变量等。

==》为了减少线程安全问题带来的性能影响:

1>不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源的方法进行同步(可以参考哪些资源会被改变)。

2>如果可变类有两种环境:单线程、多线程环境。可以考虑对该类提供两个版本,在不同的环境中使用不同的版本。

(典型单线程、多线程线程安全版本:StringBuilder(非线程安全版本)是单线程环境下使用;StringBuffer(线程安全版本)在多线程环境下使用)

释放同步监视器的锁定,这个不能显式地控制,一般的时机为:

        可大概总结为线程要从同步代码块、同步方法的代码块中退出(*或正常执行结束),这时候就会自动释放同步监视器。

Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

3》同步锁(Lock):

Lock对象充当同步对象。

1)Java 5提供了Lock、ReadWriteLock两个根接口,并为:

1>Lock==>ReetrantLock(可重入锁)实现类:

2>ReadWriteLock==>ReetrantReadWriteLock(可重入读写锁)实现类:提供了Writing、ReadingOptimistic、Reading三种锁模式。

可重入锁,指的是一个线程可以对已被加锁的ReetrantLock / ReetrantReadWriteLock对象再次加锁,ReetrantLock / ReetrantReadWriteLock对象会维持一个计数器来追踪lock()方法的嵌套调用。线程在每次调用lock()加锁后,必须显式调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。

 2)Java 8新增StampedLock类,在大多数场景中可以替代ReetrantReadWriteLock(可重入读写锁)。

常用的锁是ReetrantLock(可重入锁),其一般使用格式:

public class A{
//定义锁对象
private final ReentrantLock lock=new ReentrantLock();
...
public void method(){
//加锁对象
lock.lock();
try{
//需要保证线程同步的代码
...
}
finally{
//释放锁
lock.unlock();
}
}
}

总结:同步代码块、同步方法使用的是与资源竞争相关的、隐式的同步监视器,且强制要求加锁和锁释放要出现在一个块结构中,而且获得了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有锁。

==》死锁:

两个线程相互等待对方释放同步监视器时就会发生死锁,简单的例子A等待B的筷子吃面然后才能空出盘子,但B在等待A的盘子盛面吃了才可以给A筷子,相互之间等待需要的资源,就形成了死锁。出现了死锁,所有线程处于阻塞态,无法继续。

8、线程通信

1》传统的线程通信

Object类提供了wait()、notify()、notifyAll()三个方法,这三个方法并不属于Thread类,而是属于Object类。且三个方法必须由同步监视器对象来调用,可分为两种情况:

Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

关于上面的wait()、notify()、notifyAll()三个方法:

Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

使用举例:

public class A{
...
public sychronized void getMoney(){ try{ if(!flag){ wait();
}
else{ ...
flag=false;
notifyAll();
}
}
catch{
...
}
} public sychronized void setMoney(){ try{ if(!flag){ wait();
}
else{ ...
flag=true;
notifyAll();
}
}
catch{
...
}
}
}

2》使用Condition控制线程通信

这种情况针对不使用sychronized关键字来保证同步(即不是同步代码块或者同步方法的方式),而是使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就不能使用上面的三个方法(wait()、notify()、notifyAll())进行线程间通信了。

针对使用Lock对象保证同步的情况,Java提供了Condition类来保持协调。

使用Condition可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,也可以唤醒其他处于等待的线程。

Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

使用举例:

public class A{
//定义锁对象
private final Lock lock=new ReentrantLock();
//指定Lock对象的对应的Condition
private final Condition cond=lock.new Condition();
...
public void getMoney(){
//加锁
lock.lock();
try{ if(!flag){ cond.await();
}
else{ ...
flag=false;
cond.signalAll();
}
}
catch{
...
}
finally{
//释放锁
lock.unlock();
}
} public void setMoney(){ //加锁
lock.lock();
try{ if(!flag){ cond.await();
}
else{ ...
flag=true;
cond.signalAll();
}
}
catch{
...
}
finally{
//释放锁
lock.unlock();
}
}
}

3》使用阻塞队列(BlockingQueue)控制线程通信

Java 5提供了一个BlockingQueue接口,Queue的子接口,主要用作线程同步工具。特征:当生产者试图往BlockingQueue放入元素时,如果队列已满,则线程被阻塞;如果消费者线程试图从BlockingQueue取元素时,如果队列已空,则线程被阻塞。

Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

。。。详细使用参考相关的API。

9、线程组合未处理的异常