选择题
题目1(加强训练)
关于同步代码块说法错误的是:()
A: 同步锁对象必须唯一
B: 将要一起同步执行的代码放置到同步锁的代码块中
C: 添加同步代码块增加了程序的效率
D: 添加同步代码块增加程序的安全性
答案:C
解析:同步代码块并没有增加程序效率,反而降低了程序的效率。多线程情况下,遇到同步代码,多个线程会依次进入。
题目2(加强训练)
下列说法错误的是:()
A:多个线程修改同一个数据时,有可能出现错误的操作结果,叫做线程安全问题;
B:线程安全问题发生的需要有多个线程修改同一个数据;
C:要发生线程安全问题,需要多个线程对同一个数据进行修改操作时,CPU可以在不同的线程间来回切换;
D:多个线程操作同一个数据,一定会出现线程安全问题;
答案:D
解析:线程安全问题必须是多个线程对同一个数据发生了修改操作;如果多个线程对同一个数据的操作是读取操作,没有修改操作,则一定不会出现线程安全问题;
题目3(加强训练)
下列说法错误的是:()
A:要避免线程安全问题,应该避免使用多线程;
B:要解决线程安全问题,可以使用同步代码块;
C:使用同步代码块时,必须提供一个锁对象,而且这个锁对象对不同的线程来说,是同一个对象;
D:synchronized修饰非静态方法时,锁对象是调用这个方法的对象;synchronized修饰静态方法时,锁对象是这个方法所在的类;
答案:A
解析:使用多线程是为了程序效率的需求,所以不能通过不使用多线程的方式实现,而是应该使用线程同步技术解决线程安全问题;
编程题
案例1
需求:
实现3个窗口卖票
public class Ticket implements Runnable {
private Integer count = 100; // 共享数据
private String lock = new String();
@Override
public void run() {
while (true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
if (count > 0) {
try {
Thread.sleep((int) (Math.random() * 100));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "在卖票,在卖第" + (count--) + "张票!");
}else{
break;
}
}
}
}
}
public class Test {
public static void main(String[] args) {
Runnable ticket = new Ticket();
// 创建三个窗口
Thread t1 = new Thread(ticket,"窗口1");
Thread t2 = new Thread(ticket,"窗口2");
Thread t3 = new Thread(ticket,"窗口3");
//启动
t1.start();
t2.start();
t3.start();
}
}
案例2
需求:
有100个限量版的水杯,但是只能通过实体店和官网才能进行购买,并且分别统计卖了多少。请用线程进行模拟并设置线程名称用来代表售出途径,再将信息打印出来。比如(实体店卖出第1个,总共剩余n个…)
class Seller implements Runnable {
// 有100个限量版的水杯
private int count = 100;
public void run() {
// 获取当前线程的名字
String name = Thread.currentThread().getName();
// 定义变量统计卖出去的个数
int sellCount = 0;
// 开启循环卖票
while(true) {
synchronized(this) {
if(count > 0) {
// 每卖出一个count-1
count--;
// 线程每卖出一个把卖出的数量+1
sellCount ++;
// 卖出第1个,总共剩余n个.
System.out.println(name +"卖出第"+sellCount+"个,总共剩余"+count+"个");
}else{
break;
}
}
// 为了更好看到竞争资源,睡一小下
/*try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}
}
public class Demo02 {
public static void main(String[] args) {
// 创建一个卖水杯的Runnable接口实现类
Seller seller = new Seller();
// 创建实体店的线程
Thread t1 = new Thread(seller, "实体店");
// 创建官网的线程
Thread t2 = new Thread(seller,"官网");
// 实体店开始卖水杯
t1.start();
// 官网开始卖水杯
t2.start();
}
}
案例3
需求:
使用线程池创建多线程。模拟同学找老师学习Java。
- 创建线程池对象,包含2个线程。从线程池中获取线程对象,然后调用MyRunnable中的run()。
- 在MyRunnable实现类中,首先在控制台打印需求,“我需要一个老师”。模拟需要2秒钟时间老师可以过来指导学生,并在控制台打印老师的姓名。最后,在控制台打印“教我java,教完后,老师回到了办公室”;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class thread implements Runnable {
Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
System.out.println("我需要一个老师!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我来了" + Thread.currentThread().getName());
System.out.println("教我java,教完后,老师回到了办公室");
lock.unlock();
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 1. 创建线程池对象,包含2个线程。从线程池中获取线程对象,然后调用MyRunnable中的run()。
* 2. 在MyRunnable实现类中,
* 首先在控制台打印需求,“我需要一个老师”。
* 模拟需要2秒钟时间老师可以过来指导学生,并在控制台打印老师的姓名。
* 最后,在控制台打印“教我java,教完后,老师回到了办公室”;
*/
public class Demo {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
thread t = new thread();
service.submit(t);
service.submit(t);
service.shutdown();
}
}
案例4
需求:
-
某公司组织年会,会议入场时有两个入口,在入场时每位员工都能获取一张双色球彩票,假设公司有100个员工, 利用多线程模拟年会入场过程,并分别统计每个入口入场的人数,以及每个员工拿到的彩票的号码。线程运行后打印 格式如下:
编号为: 2 的员工 从后门 入场! 拿到的双色球彩票号码是:[17, 24, 29, 30, 31, 32, 07] 编号为: 1 的员工 从后门 入场! 拿到的双色球彩票号码是:[06, 11, 14, 22, 29, 32, 15] //..... 从后门入场的员工总共: 13 位员工 从前门入场的员工总共: 87 位员工
-
题目中用到的产生双色球的工具类已经写好,可以直接使用:
import java.util.Arrays;
import java.util.Random;
public class DoubleColorBallUtil{
// 产生双色球的代码
public static String create() {
String[] red = {"01","02","03","04","05","06","07","08","09","10",
"11","12","13","14","15","16","17","18","19","20","21","22","23",
"24","25","26","27","28","29","30","31","32","33"};
//创建蓝球
String[] blue = "01,02,03,04,05,06,07,08,09,10,11,12,13,14,15,16".split(",");
boolean[] used = new boolean[red.length];
Random r = new Random();
String[] all = new String[7];
for(int i = 0;i<6;i++) {
int idx;
do {
idx = r.nextInt(red.length);//0‐32
} while (used[idx]);//如果使用了继续找下一个
used[idx] = true;//标记使用了
all[i] = red[idx];//取出一个未使用的红球
}
all[all.length-1] = blue[r.nextInt(blue.length)];
Arrays.sort(all);
return Arrays.toString(all);
}
}
public class LuckDraw implements Runnable {
private int number = 100;
@Override
public void run() {
String name = Thread.currentThread().getName();
int count = 0;
while (true){
String s = DoubleColorBallUtil.create();
synchronized (this) {
if (number > 0) {
System.out.println("编号为:" + number + "的员工 从" + name + " 入场! 拿到的双色球彩票号码是:" + s);
number--;
count++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//从后门入场的员工总共: 13 位员工
System.out.println("从" + name + "入场的员工总共:" + count + "位员工");
break;
}
}
}
}
}
public class Test {
public static void main(String[] args) {
LuckDraw l = new LuckDraw();
Thread t1 = new Thread(l,"前门");
Thread t2 = new Thread(l,"后门");
t1.start();
t2.start();
}
}