Java多线程学习笔记(四)

线程的通信

例题:使用两个线程打印1-100。线程和线程2交替打印。

package demo02;
class Number implements Runnable{
    private int number = 1;
    @Override
    public void run() {
        while (true){
            synchronized (this) {
                notify();
                if (number <= 100){
                    System.out.println(Thread.currentThread().getName()+":"+number++);
                    try {
                        //使得调用如下wait方法得到线程进入阻塞状态
                        //一旦执行wait,会释放锁,这里和sleep不一样,sleep不会释放锁
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    break;
                }
            }
        }
    }
}
public class Communication {
    public static void main(String[] args) {
        Number number = new Number();
        new Thread(number,"线程1").start();
        new Thread(number,"线程2").start();
    }
}

输出结果:
在这里插入图片描述
这里我们用了wait和notify两个方法进行线程通信,注意notify是按优先级唤醒某个线程,notifyAll是唤醒所有线程,在这里我们只有两个线程,一个线程执行notify和notifyAll效果是一样的。而调用wait方法得到线程进入阻塞状态,一旦执行wait,会释放锁,这里和sleep不一样,sleep不会释放锁。所以逻辑就很清晰了,线程1先进来拿到锁,唤醒2(这里其实刚开始2没有wait),然后打印1,1进入wait状态,释放锁,线程2拿到锁,进来,唤醒1,1没有锁也进不来,线程2打印2,然后进入wait,周而复始。
wait,notify,notifyAll三个方法只能够出现在同步代码块或同步方法中,用lock都不行
这三个方法的调用者,必须是同步代码块或同步方法中的同步监视器,也即锁,所以用lock实现锁不能用这三个方法
所以我们上述代码中的wait();也就是this.wait();,因为锁用了this,而this又可以省略,如果用了别的锁,则不能省略。
这三个方法,是定义在java.lang.Object中的,所以任意对象可以当锁,可以调用wait等方法。

sleep和wait的异同

一旦执行方法,都会使得当前的线程进入阻塞状态。
区别:
1、来自不同的类:sleep是Thread的静态类方法,wait是Object类的方法。
2、有没有释放锁(释放资源):sleep不出让系统资源;wait是进入线程等待池等待,出让系统资源,其他线程可以占用CPU。
3、sleep可以在任何需要的场景下调用,而wait方法必须在同步代码块或同步方法中使用。
4、一般wait不会加时间限制,因为如果wait线程的运行资源不够,再出来也没用,要等待其他线程调用notify/notifyAll唤醒等待池中的所有线程,才会进入就绪队列等待OS分配系统资源。sleep(milliseconds)可以用时间指定使它自动唤醒过来,如果时间不到只能调用interrupt()强行打断。
5、sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。

经典例题:生产者/消费者问题

在这里插入图片描述

package demo02;

class Producer implements Runnable{
    private Clerk clerk;
    public Producer(){}
    public Producer(Clerk clerk){
        this.clerk = clerk;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始生产产品……");
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produceProduct();
        }
    }
}
class Consumer implements Runnable{
    private Clerk clerk;
    public Consumer(){}
    public Consumer(Clerk clerk){
        this.clerk = clerk;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始消费产品……");
        while (true){
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumeProduct();
        }
    }
}
class Clerk{
    public Clerk(){}
    public Clerk(int productCount){
        this.productCount = productCount;
    }
    //已经有的产品数量
    private int productCount;
    //生产产品给到店员
    public synchronized void produceProduct() {//锁是Clerk的实例对象
        if(productCount < 20){
            productCount++;
            System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
            //只要有一个产品存在,就可以唤醒消费者来消费了
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //消费者从店员处消费产品
    public synchronized void consumeProduct() {//锁是Clerk的实例对象
        if(productCount > 0){
            System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
            productCount--;
            //只要消费了,就必小于20了,可以唤醒生产者生产了
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Product {
    public static void main(String[] args) {
        Clerk clerk = new Clerk(0);
        Producer p1 = new Producer(clerk);
        Thread t1 = new Thread(p1,"生产者1");
        Consumer c1 = new Consumer(clerk);
        Consumer c2 = new Consumer(clerk);
        Thread t2 = new Thread(c1,"消费者1");
        Thread t3 = new Thread(c2,"消费者2");
        t1.start();
        t2.start();
        t3.start();
    }
}


执行结果:
在这里插入图片描述

JDK5.0新增线程创建方式

实现Callable接口

在这里插入图片描述

在这里插入图片描述
FutureTask同时实现Runnable接口和Future接口,所以可以放进Thread类构造器。
1.创建一个实现Callable的实现类。
2.覆写call方法,将此线程需要执行的操作写在call中,注意有返回值。
3.创建Callable接口实现类的对象。
4.将此Callable接口实现类的对象作为参数传递到FutureTask的构造器,创建FutureTask的对象。
5.将该FutureTask对象传入Thread构造器,创建Thread对象,并调用start方法。
如果对call返回值感兴趣,调用get方法获取返回值。

如何理解实现Callable接口的方式比实现Runnable接口方式更强大?
1.call可以有返回值。
2.call可以抛出异常,被外部捕获,获取异常的信息。
3.Callable支持泛型。

package demo02;

//实现Callable接口

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class NumThread implements Callable{
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 10; i++) {
            if(i % 2 == 0){
                sum += i;
                System.out.println(i);
            }
        }
        return sum;
    }
}
public class ThreadNew {
    public static void main(String[] args) {
        NumThread numThread = new NumThread();
        FutureTask futureTask = new FutureTask(numThread);
        //FutureTask实现了Runnable接口 所以可以放
        new Thread(futureTask).start();
        try {
            //get方法的返回值即为FutureTask构造器参数Callable实现类重写的call方法的返回值。
            //必须线程启动了之后才能get 要不然什么也get不到还无法结束程序
            Object o = futureTask.get();
            System.out.println("总和为"+o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

结果
在这里插入图片描述
使用泛型:

package demo02;

//实现Callable接口

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class NumThread implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 10; i++) {
            if(i % 2 == 0){
                sum += i;
                System.out.println(i);
            }
        }
        return sum;
    }
}
public class ThreadNew {
    public static void main(String[] args) {
        NumThread numThread = new NumThread();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
        //FutureTask实现了Runnable接口 所以可以放
        new Thread(futureTask).start();
        try {
            //get方法的返回值即为FutureTask构造器参数Callable实现类重写的call方法的返回值。
            //必须线程启动了之后才能get 要不然什么也get不到还无法结束程序
            Integer o = futureTask.get();
            System.out.println("总和为"+o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

使用线程池

在这里插入图片描述
在这里插入图片描述
下面只看创建一个可重用固定线程数的线程池

java ExecutorService service = Executors.newFixedThreadPool(n);
我们声明的ExecutorService类的service有两个方法,一个是execute方法,适用于Runnable接口的实现类,一个是submit方法,适用于Callable接口实现类。执行了后,用shutdown方法结束线程池。

package demo02;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class NumThread1 implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            if (i % 2 == 1) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
class NumThread2 implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }
    }
}
public class ThreadPool {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        service.execute(new NumThread2());
        service.execute(new NumThread1());
        service.shutdown();
    }
}

结果:
在这里插入图片描述
这个过程大致如下:
1.提供指定线程数量的线程池。
2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口的类的对象。
3.关闭线程池。
开发中一般都用线程池,因为有如下好处:
在这里插入图片描述
线程池的管理,因为ExecutorService是接口,我们没法设置属性,因为接口有属性也是常量,所以我们要找他的实现类,通过反射可以找到为ThreadPoolExecutor,此时我们把service强制转换为ThreadPoolExecutor类,就可以进行属性设置了
在这里插入图片描述

在这里插入图片描述

守护线程

java中的线程分为两种:守护线程(Daemon)用户线程(User)

任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(bool on);true则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常。

两者的区别:

唯一的区别是判断虚拟机(JVM)何时离开,Daemon是为其他线程提供服务,如果全部的User Thread已经撤离,Daemon 没有可服务的线程,JVM撤离。也可以理解为守护线程是JVM自动创建的线程(但不一定),用户线程是程序创建的线程;比如JVM的垃圾回收线程是一个守护线程,当所有线程已经撤离,不再产生垃圾,守护线程自然就没事可干了,当垃圾回收线程是Java虚拟机上仅剩的线程时,Java虚拟机会自动离开

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值