线程通信的例子:使用两个线程打印1-100.线程一、线程二交替打印
- 涉及到的三个方法:
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的wait
notifyAll():一旦执行此方法,就会唤醒被wait的所有线程 - 注意:
1.上述三个方法只能使用在同步代码块或者同步方法中
2.上述方法的调用者必须是同步代码块或同步方法中的同步监视器,否则出现IllegalMonitorStateException异常
3.三种方都是定义在Object类中 - 面试题:sleep()和wait()的异同?
1.相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
2.不同点:
①两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait()
②调用方法不同:sleep()可以在任何需要的场景下使用,wait()必须使用在同
③关于是否释放同步监视器:如果两个方法都是在用在同步代码块或同步方法中,sleep()不会释放同步监视器,wait()会释放同步监视器(锁)
class Number implements Runnable{
private int number = 1;
private Object obj = new Object();
//任何一个类的对象都像(唯一)
@Override
public void run() {
while (true) {
// synchronized (this) {
synchronized (obj) {
//唤醒
// this.notify();
obj.notify();
if (number <= 100) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
//使得调用如下wait()方法的线程进入阻塞状态
// this.wait();
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("线程一");
t2.setName("线程二");
t1.start();
t2.start();
}
}
线程通信的应用:经典问题:生产者/消费者问题
-
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20)。如果生产者试图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,店员会叫消费者等一下,如果店中有产品了再通知消费者来取走产品。
-
分析:
1.多线程问题:生产者线程,消费者线程
2.存在共享数据:店员(或产品)
3.同步机制解决线程安全问题
4.涉及线程的通信
class Clerk{
private int productCount = 0;
//生产产品
public synchronized void produceProduct() {
if (productCount < 20){
productCount++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "几个产品");
notify();
}else {
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//消费产品
public synchronized void consumerProduct() {
if(productCount > 0){
System.out.println(Thread.currentThread().getName() + "开始消费第" + productCount + "个产品");
productCount--;
notify();
}else {
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{//生产者
private Clerk clerk;
public Producer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + "开始生产产品");
while (true){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.produceProduct();
}
}
}
class Consumer extends Thread{//消费者
private Clerk clerk;
public Consumer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + "开始消费产品");
while (true){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.consumerProduct();
}
}
}
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
p1.setName("生产者1");
Consumer c1 = new Consumer(clerk);
c1.setName("消费者1");
Consumer c2 = new Consumer(clerk);
c2.setName("消费者2");
p1.start();
c1.start();
c2.start();
}
}
创建线程的方式三:实现Callable接口 -->jdk5.0新增
- 如何理解Callable接口的方式创建多此线程比Runnable接口的方式更强大
1.call()有放回值
2.call()可以抛出异常,被外围操作捕获,获取异常的信息
3.callable支持泛型
//创建一个实Callable的实现类
class NumThread implements Callable{
//实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
int sum = 0;
for(int i = 1; i <= 100; i++){
if (i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
//return null;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为传递值到FutureTask构造器中,创建FutureTask对象
FutureTask futureTask = new FutureTask(numThread);
//5。将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象
Thread t1 = new Thread(futureTask);
//6.调用start方法
t1.start();
try {
//6.获取Callable中call方法的返回值
//get方法返回值即为FutureTask构造器参数Callable实现类重写的call()返回值
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
创建线程的方式四:使用线程池*
- 好处:
1.提高响应速度
2.降低资源消耗
3.便于线程管理
setCorePoolSize();
setKeepAliveTime();
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for(int i = 0; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//设置线程池的属性
// System.out.println(service.getClass());//class java.util.concurrent.ThreadPoolExecutor
// ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
// service1.setCorePoolSize(15);
// service1.setKeepAliveTime();
//2.执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象
service.execute(new NumberThread());//适合用于Runnable
service.execute(new NumberThread1());
// service.submit();//适合用于Callable
//3.关闭连接池
service.shutdown();
}
}