一、Java中有三种线程创建方式,分别为实现Runnable接口的run方法,继承Thread类并重写run方法,使用了FutureTask方式。
1、Thread:创建完thread对象后该线程并没有被启动执行,直到调用了start方法后才真正启动了线程。其实调用start方法后线程并没有马上启动,而是处于就绪状态,这个就绪状态 是指该线程已经获取了除CPU资源外的其他资源,等待获取CPU资源后才会真正处于运行状态。在run方法中获取当前线程直接使用this就可以,无需使用Thread.currentThread()方法。但由于java不支持多继承,如果继承了Thread类就不能再继承其他类。另外任务与代码没有分离。
Thread也可以共享变量的,但是就是需要在main线程定义一个变量,然后把变量通过构造函数传到Thread里面,这样各个线程就可以共享该变量了。
2、Runnable:两个线程可以并发共用一个任务,Runnable任务里面定义的变量是各个线程共享的。
public static void main(String[] args) {
System.out.println("Hello World!");
// 其实就是开五个线程来运行一个任务,是并发式的
LiftOff liftOff = new LiftOff();
for(int i=0; i<5; i++){
Thread thread = new Thread(liftOff);
thread.start();
}
}
3、Callable:跟Runnable一样,重写call方法,而且支持有返回值
二、Java中的Object类是所有类的父类,其中就包含wait()以及notify()函数。
1、wait()函数:一个线程调用一个共享变量的wait方法时,该调用线程会被阻塞挂起。直到发生下面几件事情之后才会返回:(1)其他线程调用了该共享对象的notify()或者notifyAll()方法;(2)其他线程调用了该线程的interrupt()方法,该线程就抛出InterruptedException异常返回。但是呢调用wait()方法之前该线程需要事先获取该对象的监视器锁才可以。 那么一个线程如何获取一个共享变量的监视器锁呢? (1)执行synchronized同步代码块时,使用该共享变量作为参数
synchronized(obj){
....
//此处的代码就是同步代码块
}
括号里面的obj就是同步监视器(锁),上面代码的含义:线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完后,该线程会释放对该同步监视器的锁定。虽然我们可以允许使用任何对象作为同步监视器,但想一下同步监视器的目的就是阻止两个线程对同一个共享资源进行并发访问,因此通常
我们使用可能被并发访问的共享资源充当同步监视器。如果是账户取钱的问题,那么我们通常使用账户对象作为同步监视器。 (2)调用该共享变量的方法,并且该方法使用了synchronized修饰
public Class Account{
...
...
//同步关键字
public synchronized void draw(double drawAmount){
//账户余额大于取钱数目
if(balance >= drawAmount){
//吐出钞票
....
}
}
}
上面draw方法使用了synchronized关键字修饰该方法,把该方法变成同步方法。所以我们可以加一个线程,然后重写run方法
直接调用Account对象的draw方法来进行取钱操作。同步方法的同步监视器是this,而this总代表调用该方法的对象,所以线程
并发修改同一份account之前都必须先对account对象加锁,这也符合“加锁->修改->释放锁”等。
我们只需要对那些有可能改变竞争资源的方法进行同步。我们也可以有两中运行环境,一种是单线程(线程不安全)以及一种
多线程(线程安全)的版本。jdk提供的StringBuilder(不安全但性能好)、StringBuffer(线程安全)就是为了照顾两种情形。 下面展示一个例子:展示消费者和生产者之间的关系
//共享类
public class WaitService {
//队列的最大容量
private final static int MAX_SIZE = 2;
//队列
private LinkedList list = new LinkedList<>();
/**
* 生产者
* @param val
*/
public void product(Integer val){
synchronized (list){
while (list.size() == MAX_SIZE){
try{
//挂起当前线程,并释放通过同步块获取queue上的锁,让消费者线程
//可以获取该锁,然后获取队列里面的元素
list.wait();
}catch (Exception e){
e.printStackTrace();
}
}
System.out.println("生产元素:"+val);
//空闲则生成元素,并通知消费者线程
list.add(val);
list.notifyAll();
}
}
public void consume(Integer val){
synchronized (list){
while (list.size() == 0){
try {
//挂起当前线程,并释放通过同步块获取queue上的锁,让生产者线程
//可以获取该锁,将生产元素放入队列
list.wait();
}catch (Exception e){
e.printStackTrace();
}
}
System.out.println("消费元素:"+val);
//消费元素
list.remove();
list.notifyAll();
}
}
}
//生产者
public class Product implements Runnable {
private WaitService waitService;
private int i;
public Product(WaitService waitService, int i){
this.waitService = waitService;
this.i = i;
}
@Override
public void run() {
waitService.product(i);
}
}
//消费者
public class Consume implements Runnable {
private WaitService waitService;
private int i;
public Consume(WaitService waitService, int i){
this.waitService = waitService;
this.i = i;
}
@Override
public void run() {
waitService.consume(i);
}
}
public class WaitThread1 {
public static void main(String[] args){
WaitService waitService = new WaitService();
ExecutorService executorService = Executors.newCachedThreadPool();
//分别循环执行10次生产者和消费者线程,生产者向列表中存入数字,但是列表每次最多只能存2个,超
//过两个就只能阻塞等待,通知其他阻塞的线程
for(int i=0; i<10; i++){
Product product = new Product(waitService, i);
Consume consume = new Consume(waitService, i);
//执行生产者线程
executorService.execute(product);
//执行消费者线程
executorService.execute(consume);
}
}
}
如上代码中假如生产者线程首先通过synchronized获取到了list上的锁,那么后续所有企图获取list上的锁都将被阻塞挂起,当线程A发现列表已满会调用wait()方法阻塞挂起,然后释放list上的锁,这里为啥要释放锁呢??你要是不释放锁,那么其他生产者线程或者消费者线程由于都想获取list上的锁而被阻塞挂起,而线程A也挂起,那岂不是死锁了???所以这里必须释放锁。还有必须注意的是当前线程调用共享变量的wait()方法只会释放当前共享变量上的锁,如果当前线程还有其他共享变量的锁,是不会释放的。
public class WaitThread2 {
private static volatile Object resourceA = new Object();
private static volatile Object resourceB = new Object();
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try{
//获取resourceA共享资源的监视器锁
synchronized (resourceA){
System.out.println("threadA get resourceA lock");
//获取resourceB共享资源的监视器锁
synchronized (resourceB){
System.out.println("threadA get resourceB lock");
//线程A阻塞,并释放锁
System.out.println("threadA release resourceA lock");
resourceA.wait();
}
}
}catch (Exception e){
e.printStackTrace();
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
//获取resourceA共享资源的监视器锁
synchronized (resourceA) {
System.out.println("threadB get resourceA lock");
System.out.println("threadB try get resourceB lock....");
//获取resourceB共享资源的监视器锁
synchronized (resourceB) {
System.out.println("threadB get resourceB lock");
//线程B阻塞,并释放锁
System.out.println("threadB release resourceA lock");
resourceA.wait();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
threadA.start();
threadB.start();
//等待两个线程结束
threadA.join();
threadB.join();
System.out.println("main over");
}
}
运行结果是:
threadA get resourceA lock
threadA get resourceB lock
threadA release resourceA lock
threadB get resourceA lock
threadB try get resourceB lock....
上面的代码,在main函数中启动了线程A和线程B,为了让线程A获取到锁,让线程B休眠了1S,线程A先后获取到共享变量resourceA和共享变量resourceB上的锁,然后调用了resourceA的wait()方法阻塞自己,阻塞自己后释放了resourceA上的锁。线程B休眠后就尝试获取resourceA上的锁,如果线程A此时已经调用wait方法释放该锁,那么线程B就可以获取到resourceA的锁,然后尝试获取resourceB上的锁,由于线程A只是调用recourceA上的wait()方法,所以线程A挂起自己后并没有释放resourceB上的锁,这时线程B尝试获取resourceB上的锁就会被阻塞了。
当一个线程调用共享对象的wait()方法被阻塞挂起后,如果其他线程中断了该线程,则线程会抛出异常并返回。
public class waitThread3 {
private static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("begin interrupt");
synchronized (object){
object.wait();
}
System.out.println("ended");
}catch (Exception e){
e.printStackTrace();
}
}
});
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
运行结果:
begin interrupt
java.lang.InterruptedException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.example.demo.通知与等待.waitThread3$1.run(waitThread3.java:13)
at java.lang.Thread.run(Thread.java:748)
Process finished with exit code 0
总结一下:任何线程进入同步代码块、同步方法之前,都要对同步监视器加锁,但是他是无法显式的释放对同步监视器的锁定。 【1】当前线程的同步方法、同步代码块执行结束就释放同步监视器
【2】遇到了break、return也会释放同步监视器
【3】抛出异常
【4】程序执行了同步监视器对象的wait方法,则当前线程暂停,并释放同步监视器。
但是呢,执行下面的情况是不会释放同步监视器的:
【1】程序调用Thread.sleep()、Thread.yeild()方法来暂停当前线程的执行,是不会释放的
【2】当线程执行同步代码块的时候,别的线程调用了该线程的suspend()方法将该线程挂起,也不会释放同步监视器的。
2、wait(long timeout)函数 该方法相比wait()方法多了一个超时参数,如果一个线程调用共享变量的该方法挂起后,没有在指定的timeout ms时间内被其他线程调用该共享变量的notifyAll()方法唤醒,那么该函数还是会因为超时而返回。
3、notify()函数 一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的锁的线程,可能一个共享变量上有多个线程在等待,具体唤醒哪个等待的线程是随机的,其他的就只能继续阻塞了。唤醒之后的线程并不能马上就执行,而是必须等待那个调用了notify()方法的线程释放锁之后,才有机率获取到这个共享变量的锁。为什么说有机率呢?因为他还需要跟其他线程竞争该锁呢,只有获取到该共享变量的锁才可以继续执行。
4、notifyAll()函数 不同于notify()只能唤醒一个线程,notifyAll()可以唤醒所有在该共享变量上由于调用wait()方法而被挂起的线程
public class waitThread4 {
private static volatile Object resourceA = new Object();
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(()->{
synchronized (resourceA){
try {
resourceA.wait();
System.out.println("threadA end wait");
}catch (Exception e){
e.printStackTrace();
}
}
});
executorService.execute(()->{
synchronized (resourceA){
try {
resourceA.wait();
System.out.println("threadB end wait");
}catch (Exception e){
e.printStackTrace();
}
}
});
Thread.sleep(1000);
executorService.execute(()->{
synchronized (resourceA){
try {
System.out.println("threadC notify");
resourceA.notifyAll();
}catch (Exception e){
e.printStackTrace();
}
}
});
}
}
运行结果:
threadC notify
threadB end wait
threadA end wait
Process finished with exit code 0
还需要注意的是notifyAll()方法只会唤醒调用这个方法之前调用了wait()方法而被挂起的线程,假如上面的代码线程B放在了线程C后面执行,那么线程B是不会被唤醒的。