多线程循环交替输出1-100的两种方式

交替循环,本质思想就是对方法体进行加锁,两个线程相互通信来获取锁、释放锁,达到1212的效果。

【1】extends Thread

实践目标:使用两个线程打印 1-100. 线程1, 线程2 交替打印

其实就是多线程之间的线程通信,使用wait、notify或者notifyAll。如下的三个关键字使用的话,都得在同步代码块或同步方法中。

  • ① wait():一旦一个线程执行到wait(),就释放当前的锁。

  • ② notify()唤醒wait的一个的线程;

  • ③ notifyAll():唤醒所有线程;

示例代码

class PrintNum extends Thread {
	static int num = 1;
	// 静态成员变量,保证锁的唯一
	static Object obj = new Object();
	public void run() {
		while (true) {
			//obj can instead of PrintNum.class
			synchronized (obj) {
				/*当前线程活动期间才能唤醒其他等待线程*/
				obj.notify();
				if (num <= 100) {
					try {
						Thread.currentThread().sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + ":"
							+ num);
					num++;
				} else {
					break;
				}
				
				try {
					obj.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}

}

public class TestCommunication {
	public static void main(String[] args) {
		Thread t1 = new PrintNum();
		Thread t2 = new PrintNum();
		
		t1.setName("thread1");
		t2.setName("thread2");
		t1.setPriority(Thread.MAX_PRIORITY);//10
		t2.setPriority(Thread.MIN_PRIORITY);//1
		
		t1.start();
		t2.start();
	}
}

调用start()方法,它 会新建一个线程然后执行run()方法中的代码。但是并不意味着多次调用t1.start()会创建多个线程并执行run()方法。Thread执行了start之后不可以再次执行start。

那么为什么不直接调用run()方法呢?

如果直接调用run()方法,并不会创建新线程,方法中的代码会在当前调用者的线程中执行。

另外查看Thread会发现,Thread实现了Runnable接口,实现了run方法并自定义了一系列方法。

【2】implements Runnable

package com.web.test;

public class TestThread implements Runnable {
	int i = 1;

	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
		/*指代的为 t,因为使用的是implements方式。若使用继承Thread类的方式,慎用this*/
			synchronized (this) {
			/*唤醒另外一个线程,注意是this的方法,而不是Thread*/
			notify();
			try {
			/*使其休眠100毫秒,放大线程差异*/
			//Thread,currentThread().sleep(0,100);使其休眠100纳秒
				Thread.currentThread().sleep(100);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
				if (i<=100) {
					
					System.out.println(Thread.currentThread().getName() + ":"+ i);
					i++;
					try {
					/*放弃资源,等待*/
						wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
		}
	}
	}

	public static void main(String[] args) {
		/*只有一个TestThread对象*/
		TestThread t = new TestThread();
		Thread t1 = new Thread(t);
		Thread t2 = new Thread(t);
		
		t1.setName("线程1");
		t2.setName("线程2");
		
		t1.start();
		t2.start();
		
	}
}

输出结果如下:

这里写图片描述

使用lambda实现Runnable

如下所示一个多线程爬虫实例:

Thread thread = new Thread(() -> {
    int currTotal=0;
    for (int j = beginPage; j <= finalEndPage; j++) {
        String sendUrl = note_list_url + j;
        Integer pageData = new CrawBookService().getPageData(sendUrl, new AtomicInteger(0));
        totalNum.getAndAdd(pageData);
        currTotal+=pageData;
    }
    logger.info("当前线程:{},抓取数量为:{}", Thread.currentThread().getName(),currTotal);
});

【3】释义

锁的唯一性

这里使用的锁如下:

static Object obj = new Object();

在Main方法里面我们创建了两个PrintNum对象,那么是不是有两个Object obj = new Object();?这样岂不是不能保证线程安全,同步失败?

这里就要掌握类的加载及类中成员变量的加载。

实例化对象的执行顺序

如果类还没有被加载:

1、先执行父类的静态代码块和静态变量初始化,并且静态代码块和静态变量的执行顺序只跟代码中出现的顺序有关。
2、执行子类的静态代码块和静态变量初始化。
3、执行父类的实例变量初始化(例如:int a;初始化就是0,引用类型就是null)
4、执行父类的构造函数
5、执行子类的实例变量初始化(例如:int a;初始化就是0,引用类型就是null)
6、执行子类的构造函数 。

如果类已经被加载:

则静态代码块和静态变量就不用重复执行,再创建类对象时,只执行与实例相关的变量初始化和构造方法。

即 虽然创建了两个PrintNum对象,但是静态成员变量obj只有一个!这就保证了同步控制。

基于锁的唯一性,那么我们还可以使用PrintNum.class来代理obj作为锁进行同步控制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流烟默

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值