文章目录
易错练习题
- 本文是2021/04/13整理的笔记
- 赘述可能有点多,还请各位朋友耐心阅读
- 本人的内容和答案不一定是最好最正确的,欢迎各位朋友评论区指正改进
练习1(第8题)
class MyThread1 extends Thread{
Object lock;
public MyThread1(Object lock){
this.lock = lock;
}
public void run(){
synchronized(lock){ //1
for(int i = 0; i<=10; i++){
try{
Thread.sleep( (int)(Math.random()*1000) );
}catch(Exception e){}
System.out.println(“$$$”);
}
}
}
}
class MyThread2 extends Thread{
Object lock;
public MyThread2(Object lock){
this.lock = lock;
}
public void run(){
synchronized(lock){ //2
for(int i = 0; i<=10; i++){
try{
Thread.sleep((int)(Math.random()*1000) );
}catch(Exception e){}
System.out.println(“###”);
}
}
}
}
public class TestMyThread{
public static void main(String args[]){
Object lock = new Object();
Thread t1 = new MyThread1(lock);
Thread t2 = new MyThread2(lock);
t1.start();
t2.start();
}
}
问:在//1 和//2 处加上的synchronized 起什么作用?如果不加synchronized,
运行程序有什么不同的地方?
答案
作用:使线程同步
区别:不加synchronized,两个线程会抢占cpu,$$$和###会交替输出。
练习2(第9题)
class MyThread extends Thread{
private String data;
public void run(){
synchronized(data){
for(int i = 0; i<10; i++){
try{
Thread.sleep((int)(Math.random()*1000) );
}catch(Exception e){}
System.out.println(data);
}
}
}
}
public class TestMyThread {
public static void main(String args[]){
Thread t1 = new MyThread(“hello”);
Thread t2 = new MyThread(“world”);
t1.start();
t2.start();
}
}
问:上述代码输出的结果是什么?
A. 先输出100 个hello,然后是100 个world
B. 先输出100 个world,然后是100 个hello
C. 线程不同步,因此交替输出hello 和world
答案
C
两个线程的锁的对象不同,所以不能同步。一个是hello,一个是world
线程死锁
- 定义:
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要
的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状
态,无法继续 - 比喻:
面试官提问:什么是线程死锁?
大学生:你给我发offer,我就给你解释
面试官:你不给我解释,我就不给你发
这就是死锁
解决死锁:
- 避免嵌套同步
- 减少同步资源
- 打破死锁的条件
线程通讯
- 定义:线程之间可以通讯,能够互相发送信号。
忙等待(使用了共享变量)
- 忙等待的效率比较低
public class EggTest {
//判断有没有蛋。初始没有蛋
boolean hasEggs = false;
//创建人类(捡蛋)线程对象
Thread human = new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(!hasEggs){//没有蛋
System.out.println("人在等待蛋");
}else {
System.out.println("人捡到蛋了");
hasEggs = false;
}
}
}
}
);
//创建鸡(下单)线程对象
Thread hen = new Thread(new Runnable() {
@Override
public void run() {
while(true){
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("鸡下完蛋了");
hasEggs = true;
}
}
});
}
使用wait和notify() 等待,唤醒
介绍
- 介绍一下wait notify notifyAll三种方法
- wait():在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
- notify(): 唤醒在此对象监视器上等待的单个线程。(如果有多个线程,则随机唤醒)
- notifyAll():唤醒在此对象监视器上等待的所有线程。
wait关键字的语法
wait
synchronized(obj){
while(条件){
obj.wait();//卡在这里不动,释放锁
}
}
notify关键字的语法
- 由于notifyAll()方法和notify()方法使用类似,这里只对notify关键字的使用进行描述。
- synchronized(obj){
obj.notify();//唤醒obj.wait等待线程,继续执行wait后的代码
}
注意
- 调用wait和notify的线程必须是锁的拥有者,因此需要在同步代码块中调用wait和notify方法,JVM会判断线程是不是拥有者,不是则会抛出异常
鸡下蛋 调用wait和notify方法 有歧义
- 一个线程调用wait方法时,会将锁打开,等待其他该锁的线程调用notify方法来唤醒自己。
wait和sleep的区别
- sleep 方法由 Thread 类提供,它不会释放线程锁。
- wait 方法由 Object 类提供,这就意味着所有的 Java 类都具备 wait 方法,wait 方法调用后会释放线程具备的锁,同一个对象的 notify和 notifyAll 方法能够唤醒线程,wait 方法和 notify 方法需要在同步块中调用
管道流实现线程通讯
Send类
import java.io.IOException;
import java.io.PipedWriter;
import java.util.Random;
public class Send extends Thread{
Random r = new Random();
//创建一个管道对象,PipedWriter
PipedWriter out = new PipedWriter();
public PipedWriter getOut() {
return out;
}
@Override
public void run() {
//向管道流中写字符
for (char ch ='A';ch<='z';ch++){
try {
out.write(ch);
} catch (IOException e) {
e.printStackTrace();
}
//线程睡觉
try {
Thread.sleep((r.nextInt(10)+1)*100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Receive类
import java.io.IOException;
import java.io.PipedReader;
public class Receive extends Thread{
PipedReader in;
public Receive(Send send) {
try {
this.in = new PipedReader(send.getOut());
} catch (IOException e) {
e.printStackTrace();
}
}
public Receive() {
}
@Override
public void run() {
while(true){
try {
System.out.println((char) in.read());
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
TestMain类
public class TestMain {
public static void main(String[] args) {
//创建发送线程对象
Send send = new Send();
//创建接收线程对象
Receive receive = new Receive(send);
//启动线程
send.start();
receive.start();
}
}
线程池
定义
- 在容器里放多个创建好的线程对象,这些线程对象随时可以执行.并且可以重复使用. 还可以随时扩充线程.当闲置时,可以随时减少线程的数目.
作用
- 线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复
使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存
在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应
更快 - 通过适当的调整线程中的线程数目可以防止出现资源不足的情况
- 当一个服务器接受到大量短小线程的请求时,使用线程池技术是非常合适的,它可
以大大减少线程的创建和销毁次数,提高服务器的工作效率.
包含的内容
- 线程池管理器
- 工作线程
- 任务队列
- 任务接口
信号量
概念
- 信号量是进行资源协调调度的工具
作用
- 解决多个线程对多个资源的并发访问的问题。