文章目录
- (1)练习题1
- (2)练习题2
- (3)练习题3
现在咱们线程一共说了这么几件事情,如下:
具体文章见专栏。
接下来看几个练习题吧。
(1)练习题1
🌋题目描述
【新年倒计时】
模拟新年倒计时,每隔1秒输出一个数字,依次输出10,9,8…1,最后输出:新年快乐!
🍰分析
题目中没有说要造分线程,那我们可以直接放到主线程里面,也是可以的。
直接写一个for循环遍历即可,如下:
public class HappyNewYear {
public static void main(String[] args) {
for (int i = 10; i >=1 ; i--) {
System.out.println(i);
}
}
}
然后sleep
,让它一秒钟输出一下。如下:
记得处理一下异常:
这里只能用try-catch处理,因为sleep抛出的方法是编译时异常,而且父类没有抛出,不能使用throws的方式。
🌱代码
package yuyi03;
/**
* ClassName: HappyNewYear
* Package: yuyi03
* Description:
* 模拟新年倒计时,每隔1秒输出一个数字,依次输出10,9,8......1,最后输出:新年快乐!
* @Author 雨翼轻尘
* @Create 2024/1/30 0030 13:03
*/
public class HappyNewYear {
public static void main(String[] args) {
for (int i = 10; i >=0 ; i--) {
try {
Thread.sleep(1000); //睡1s
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i>0){
System.out.println(i);
}else {
System.out.println("HappyNewYear!");
}
}
}
}
🍺输出结果
(2)练习题2
🌋题目描述
关于Thread.sleep()
方法的一个面试题:
如下的代码中sleep()
执行后,到底是哪个线程进入阻塞状态了呢?
🌱代码
package yuyi03;
/**
* ClassName: ThreadTest
* Package: yuyi03
* Description:
* 如下的代码中sleep()执行后,到底是哪个线程进入阻塞状态了呢?
* @Author 雨翼轻尘
* @Create 2024/1/30 0030 13:16
*/
public class ThreadTest {
public static void main(String[] args) {
// 创建线程对象
MyThread t = new MyThread();
t.setName("线程1");
t.start();
// 调用sleep方法
try {
t.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 5秒之后这里才会执行。
System.out.println("hello World!");
}
}
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
🍺输出结果(部分)
🍰分析
main方法中造了一个MyThread的对象,并且起了一个名字,调用了start方法。
紧接着有一个sleep方法,这个sleep方法是让主线程睡了还是让分线程睡了?
其实是主线程。
虽然t是一个线程,但是这里不能算是一个线程去调用sleep。只能理解为是一个对象去调用sleep。
t.sleep()
代码的执行,是在主线程里面调用的。
静态方法,用类和对象调用都没有区别,都只会影响主线程。
(3)练习题3
🌋题目描述
银行有一个账户。
有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额。
问题:该程序是否有安全问题,如果有,如何解决?
【提示】
1,明确哪些代码是多线程运行代码,须写入run()方法。
2,明确什么是共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。
【拓展问题】可否实现两个储户交替存钱的操作
🍰分析
“同一个账户”就是共享数据。
如果有多线程,那就一定会有线程安全问题吗?
不一定。比如一个线程操作一个账户的钱,另一个线程操作另一个账户的钱,这就不会有线程安全问题。
但若是多个线程操作一个共享数据,就会有线程安全问题了。
本题目我们用继承Thread
的方式来写。
比如现在有一个客户,我们让它继承于Thread类。如下:
public class AccountTest {
}
class Customer extends Thread{
}
既然大家需要共用一个账户,那么这个账户如何体现共享?
用静态吗?
其实不用静态也可以!
比如现在声明一个Account类,就是账户类,里面有余额,如下:
class Account{ //账户
private double balance; //余额
}
然后在客户Customer类里面声明一个Account属性,如下:
class Account{ //账户
private double balance; //余额
}
class Customer extends Thread{
Account account;
}
这里就不将它写成静态的了,那能不能实现共享呢?
这就取决于它的构造器如何去使用了。
通过构造器给当前属性实例化一下,如下:
class Account{ //账户
private double balance; //余额
}
class Customer extends Thread{
Account account;
//构造器
public Customer(Account acct){
this.account=acct;
}
}
怎么保证造两个Customer
,是同一个account
呢?
只new
一个对象,然后将值传进去即可。
比如在main方法中,造一个账户acct,然后new一个Customer的时候将acct传进去。
public class AccountTest {
public static void main(String[] args) {
Account acct=new Account();
new Customer(acct);
}
}
这样就可以创建两个Customer
,如下:
public class AccountTest {
public static void main(String[] args) {
Account acct=new Account();
Customer customer1=new Customer(acct);
Customer customer2=new Customer(acct);
}
}
现在这两个线程就共享同一个账户acct。
以后就可以使用这种思路来让线程共享资源啦,如下:
public class AccountTest {
public static void main(String[] args) {
Account acct=new Account();
Customer customer1=new Customer(acct);
Customer customer2=new Customer(acct);
}
}
class Account{ //账户
private double balance; //余额
}
class Customer extends Thread{
Account account;
//构造器
public Customer(Account acct){
this.account=acct;
}
}
同一个对象的实例变量是共享的。
在Thread类里面有一个可以起名字的构造器,这边就使用它来给线程起个名字吧。
来个重载构造器:
class Customer extends Thread{
Account account;
//构造器
public Customer(Account acct,String name){
super(name);
this.account=acct;
}
}
现在就可以用这个构造器了,比如:
public class AccountTest {
public static void main(String[] args) {
Account acct=new Account();
Customer customer1=new Customer(acct,"小旺");
Customer customer2=new Customer(acct,"小岁");
}
}
这样的话,就通过构造器的方式将线程的名字赋值好了。
现在两个储户需要存钱,需要在run
方法里面做这个事情。
存三次,就循环三次:
class Customer extends Thread{
//...
@Override
public void run() {
for (int i = 0; i < 3; i++) {
}
}
}
然后就是操作账户的余额,让余额balance三次增加。加钱的事情可以定义在Account类里面。如下:
class Account{ //账户
private double balance; //余额
public void deposit(double amt){
if(amt>0){
balance+=amt;
}
System.out.println(Thread.currentThread().getName()+"存钱1000元,余额为:"+balance);
}
}
然后在刚才的for循环里面可以调用一下deposit
方法。
class Customer extends Thread{
//...
@Override
public void run() {
for (int i = 0; i < 3; i++) {
account.deposit(1000);
}
}
}
当我们调用run方法的时候,就会进入for循环,然后调用account的deposit方法,首先往里面存了1000块钱。
然后各自线程去调用start
方法,他们会各自调用run方法,去存钱。如下:
public class AccountTest {
public static void main(String[] args) {
Account acct=new Account();
Customer customer1=new Customer(acct,"小旺");
Customer customer2=new Customer(acct,"小岁");
customer1.start();
customer2.start();
}
}
🌱代码
package yuyi03;
/**
* ClassName: AccountTest
* Package: yuyi03
* Description:
*
* @Author 雨翼轻尘
* @Create 2024/1/30 0030 14:56
*/
public class AccountTest {
public static void main(String[] args) {
Account acct=new Account();
Customer customer1=new Customer(acct,"小旺");
Customer customer2=new Customer(acct,"小岁");
customer1.start();
customer2.start();
}
}
class Account{ //账户
private double balance; //余额
public void deposit(double amt){
if(amt>0){
balance+=amt;
}
System.out.println(Thread.currentThread().getName()+"存钱1000元,余额为:"+balance);
}
}
class Customer extends Thread{
Account account;
//构造器
public Customer(Account acct){
this.account=acct;
}
public Customer(Account acct,String name){
super(name);
this.account=acct;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
account.deposit(1000);
}
}
}
🍺输出结果
现在看着没有线程安全问题,接下来演示一下线程安全问题。
共享数据就是“钱数”balance。
在这里加一个sleep
:
现在的代码:
public class AccountTest {
public static void main(String[] args) {
Account acct=new Account();
Customer customer1=new Customer(acct,"小旺");
Customer customer2=new Customer(acct,"小岁");
customer1.start();
customer2.start();
}
}
class Account{ //账户
private double balance; //余额
public void deposit(double amt){
if(amt>0){
balance+=amt;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"存钱1000元,余额为:"+balance);
}
}
class Customer extends Thread{
Account account;
//构造器
public Customer(Account acct){
this.account=acct;
}
public Customer(Account acct,String name){
super(name);
this.account=acct;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
account.deposit(1000);
}
}
}
输出结果:
可以看到出现了问题。
这就是线程安全问题,怎么解决呢?
balance
是共享数据,直接将操作写在deposit
方法里面了,那么可以给这个方法直接加上synchronized
吗?
能不能使用,取决于这个this
是不是唯一的。
大家不要记“继承的方式this不唯一”,需要具体问题具体分析。
这个方法的调用者是Account
类的对象,而Account类的对象只创建了一个,如下:
所以现在就是acct。既然是唯一的,那么线程就是安全的。
🌱代码
package yuyi03;
/**
* ClassName: AccountTest
* Package: yuyi03
* Description:
*
* @Author 雨翼轻尘
* @Create 2024/1/30 0030 14:56
*/
public class AccountTest {
public static void main(String[] args) {
Account acct=new Account();
Customer customer1=new Customer(acct,"小旺");
Customer customer2=new Customer(acct,"小岁");
customer1.start();
customer2.start();
}
}
class Account{ //账户
private double balance; //余额
public synchronized void deposit(double amt){ //this:是唯一的,即为actt
if(amt>0){
balance+=amt;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"存钱1000元,余额为:"+balance);
}
}
class Customer extends Thread{
Account account;
//构造器
public Customer(Account acct){
this.account=acct;
}
public Customer(Account acct,String name){
super(name);
this.account=acct;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
account.deposit(1000);
}
}
}
🍺输出结果
这就是之前说的,同步方法可以使用在“继承Thread类的子类”中的场景了,只要对象是同一个,那就可以直接使用非静态同步方法了。
🎲现在的结果显示小旺存完之后,小岁才存钱,要想体现交互可以让他睡一下,如下:
🌱代码
package yuyi03;
/**
* ClassName: AccountTest
* Package: yuyi03
* Description:
*
* @Author 雨翼轻尘
* @Create 2024/1/30 0030 14:56
*/
public class AccountTest {
public static void main(String[] args) {
Account acct=new Account();
Customer customer1=new Customer(acct,"小旺");
Customer customer2=new Customer(acct,"小岁");
customer1.start();
customer2.start();
}
}
class Account{ //账户
private double balance; //余额
public synchronized void deposit(double amt){ //this:是唯一的,即为actt
if(amt>0){
balance+=amt;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"存钱1000元,余额为:"+balance);
}
}
class Customer extends Thread{
Account account;
//构造器
public Customer(Account acct){
this.account=acct;
}
public Customer(Account acct,String name){
super(name);
this.account=acct;
}
@Override
public void run() {
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.deposit(1000);
}
}
}
🍺输出结果