线程:cpu调度的最小单位
进程:操作系统分配资源的最小单位(线程共享进程内部的资源)
java 中提供了Thread 类,而这个类有几个方法
yield,start,run(来自接口runnable),sleep,
yield: 欲罢能否?
告诉调度器自己当前可以让出资源(但是不一定有效)
A hint to the scheduler that the current thread is willing to yield
* its current use of a processor. The scheduler is free to ignore this
* hint.
测试代码:
(测试效果基本看不出)
Thread[] threads = new Thread[10];
threads[0] = new Thread(() -> {
// ReadThreadLocal readThreadLocal2 = new ReadThreadLocal();
//readThreadLocal2.begin();
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
threads[0].setName("-1");
threads[0].start();
for (int i = 1; i < 10; i++) {
final Thread pre = threads[i - 1];
final int index = i;
threads[i] = new Thread(() -> {
try {
// 这里尝试1,2,号线程让出cpu
if (index < 3) {
Thread.yield();
}
// 以下逻辑 可以保证 6,7,8,9 线程是有序执行
else if (index > 6) {
pre.join();
}
// ReadThreadLocal readThreadLocal2 = new ReadThreadLocal();
// readThreadLocal2.begin();
Thread.sleep(500);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threads[i].setName(i + "");
threads[i].start();
}
效果:(这三次输出只是看到了join 接力棒的效果,始终保证6,7,8,9之间是严格有序的)
第一次:
-1 1 3 2 5 4 6 7 8 9
第二次
-1 1 6 5 2 4 3 7 8 9
第三次
-1 3 1 4 2 6 5 7 8 9
start:线程启动入口
启动线程(线程进入就绪态:(也就是等待被cpu调度,并不是说先调用start,就先执行run方法)
run: 线程真正的执行体,线程需要处理的业务逻辑全在这里
join:接力棒
join(othrerThread)
Waits for this thread to die// 等待到参数中的thread 死亡,自己才开始
(可以类比为线程进行接力赛,下一棒需要等上一棒到了才能开始跑),也就是可以达到有序执行的目的
代码测试
看例子程序:
Thread[] threads = new Thread[10];
threads[0] = new Thread(() -> {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() );
});
threads[0].setName("-1");
threads[0].start();
for (int i = 1; i < 10; i++) {
final Thread pre = threads[i - 1];
threads[i] = new Thread(() -> {
try {
pre.join();
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() );
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threads[i].setName(i + "");
threads[i].start();
}
sleep: 抱锁入睡
注意不会丢失monitors,监视器,也就是不会释放锁
Causes the currently executing thread to sleep (temporarily cease
* execution) for the specified number of milliseconds, subject to
* the precision and accuracy of system timers and schedulers. The thread
* does not lose ownership of any monitors.
代码测试:
线程1,线程2 都去获取同一个对象的monitor,各自都在获取到之后输出 “sleep start”+id;sleep(500);输出"sleep end "+id
如果不释放锁的话,要么是线程1 连续输出两条,然后线程2连续输出两条
否则是反过来
代码使用10个线程
public class TestSleep {
public static void main(String[] args) {
final Object lock=new Object();
for (int i=0;i<10;i++) {
Thread t=new Thread(()->{
synchronized (lock){
try {
System.out.println("sleep start"+Thread.currentThread().getName());
Thread.sleep(500);
System.out.println("sleep end"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.setName(i+"");
t.start();
}
}
}
效果:
Object 的方法:wait VS notify
wait:等待,会释放锁,将当前位置保存,暂停执行
norify:唤醒,会释放锁,但是会将synchronized修饰代码块执行完之后才会释放
伪代码描述
以下列伪代码为例:
第一块代码块
synchonized(helloObject){// 这里相当于是尝试获取对象helloObject 的锁
输出:我是hello1
helloObject.wait();
输出:我是hello2
}
第二块代码块
synchronized (helloObject){
输出:我是word1:
helloObject.notifyAll();
输出:我是word2
}
- 假设上述代码:线程1进入到第一块代码块,此时线程2阻塞到第二行代码块的synchronized 处,线程2没有获取到锁
- 线程1调用helloObject.wait();挂起线程,进入waiting 状态;// 已经输出 hello1
- 线程2获取到helloObject 的锁, //输出我是word1
- 线程2调用helloObject.notifyAll() //唤醒所有处于该对象的waiting 状态的线程
- 线程2退出synchronized 同步块;//已输出 我是word2
- 线程2释放helloObject 的monitor(看成锁)
- 线程1继续// 输出我是hello2
以下附上java并发编程艺术中的一个例子:等待-唤醒机制
/**
* @author wangwei
* @date 2019/3/10 9:15
* @classDescription 场景:一个线程修改了一个对象的值,而另一个线程感觉到了变化,然后进行相应的操作
* 整个过程开始于一个线程,作用于另一个线程`,前者是生产者,后者是消费者
* <p>
* 这种生产者消费者模式隔离了"做什么" 和"怎么做"
* java 中的实现方式
* while(value!=desire){
* Thread.sleep(1000);
* }
* doSomething();
* <p>
* 上述代码:在条件不满足时,sleep一段时间,避免过快的"无效尝试"
* <p>
* 存在的问题:
* 1.难以确保及时性:睡眠时基本不占用处理器资源,但是如果睡眠过久,难以及时发现条件已经变化
* 2.难以降低开销:如果降低睡眠时间,消费者能及时发现条件变化,但是,却可能需要浪费更多的资源
* <p>
* 两点矛盾,但是java内置了等待/通知机制,可以解决这个问题
*/
public class WaitNotify {
static boolean flag = true;
static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread waitThread = new Thread(new Wait(), "WaitThread");
waitThread.start();
TimeUnit.SECONDS.sleep(1);
Thread notifyThread = new Thread(new Notify(), "NotifyThread");
notifyThread.start();
}
static class Wait implements Runnable {
@Override
public void run() {
synchronized (lock) {
// 当条件不满足时,继续wait,但是会释放锁lock
while (flag) {
try {
System.out.println(Thread.currentThread() + " flag is true,wait @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//条件满足时,完成工作
System.out.println(Thread.currentThread() + " flag is false,running@ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
}
static class Notify implements Runnable {
@Override
public void run() {
//加锁,拥有lock 的monitor
synchronized (lock) {
// 获取lock锁,然后进行通知(不会释放锁)
// 直到当前线程释放lock锁之后,WaitThread 才能在wait方法上返回
System.out.println(Thread.currentThread() + " hold lock ,notify @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
SleepUtil.second(5);
}
// 再次加锁
synchronized (lock) {
System.out.println(Thread.currentThread() + " hold lock again ,sleep @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtil.second(5);
}
}
}
}
输出:
等待唤醒机制应用–线程池
奉上并发编程艺术一书中简易web服务器
可前往github 获取
https://github.com/TopForethought/java/tree/master/src/top/forethought/concurrency/threads/httpserver
这里就不再粘贴代码了