线程休眠以及同步

  • 所谓线程休眠就是给线程加入一个定时器,让线程延迟进入可运行状态,延迟时间到达后在通过抢占进程资源来运行线程操作,具体代码如下:
// wait 和 sleep
package chapter09;

public class Java03_Thread {
public static void main(String[] args) throws Exception {
// TODO 线程 - 休眠sleep
// 休眠1秒钟 - 循环执行打印语句
while (true) {
Thread.sleep(1000);
System.out.println("main线程执行完毕");
}
}
}
  • 下面是常用的同步操作结合线程休眠操作(synchronized+wait+notify/notifyAll)

下面是一个业务场景,我们去银行办理业务通常是需要拿号排队的,银行9点开门,那么此时我们想要第一个办理业务就需要提前于九点去到银行办理业务,此时业务流程就是: 我先到银行拿号->银行开门开始叫号->我办理业务

  • synchronizedJava中的关键字,用于实现线程同步。它可以应用于方法或代码块,确保在同一时间只有一个线程可以访问被synchronized修饰的代码。
  • synchronized的主要作用是解决多线程并发访问共享资源时可能出现的数据不一致或冲突的问题。当多个线程同时访问共享资源时,可能会导致数据竞争和不确定的结果。通过使用synchronized关键字,我们可以确保在同一时间只有一个线程可以执行被synchronized修饰的代码,从而避免数据竞争和保证数据的一致性。
// 这里就是两个线程同时访问num资源
package Chapter08;

public class Process_25 {
public static void main(String[] args) throws Exception {

// TODO 线程 - 同步
// synchronized - 同步关键字
// 多个线程访问同步方法时,只能一个一个访问,同步操作
// new Hashtable<>();
// synchronized关键字还可以修饰代码块,称之为同步代码块
/*
synchronized( 用于同步得对象 ) {
处理逻辑
}

*/
// 创建叫号器
Num num = new Num();

// 创建用户
User user = new User(num);
user.start();

// 创建银行
Bank bank = new Bank(num);
bank.start();

}
}

// 叫号器
class Num {

}

// 银行类
class Bank extends Thread {
private Num num;
public Bank( Num num ) {
this.num = num;
}
public void run() {
synchronized (num) {
try {
// 线程休眠 2秒
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("9:00, 开发,开始叫号");

// 唤醒被.wait的线程 - 银行正式开门接收办理业务
num.notifyAll();
}
}
}

// 用户类
class User extends Thread {
// public synchronized void test() {
//
// }
private Num num;
public User( Num num ) {
this.num = num;
}
// 复写线程逻辑的执行方法
public void run() {

// 同步代码块 - 到银行拿号
synchronized ( num ) {
System.out.println("我是号码1,银行还没开门,我需要等一会");
try {
// 等待银行开门(线程休眠)
num.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

// 线程被唤醒(notify) - 线程继续执行开始办理业务
System.out.println("叫到我的号了,该我办业务了。");
}
}
}

/*
* 输出结果:
* 1. 我是号码1,银行还没开门,我需要等一会
* 2. 延迟 2 秒
* 3. 9:00, 开发,开始叫号
* 4. 叫到我得号了,该我办业务了。
* */

wait(等待)和sleep(休眠)的区别

  • 两者都是用于阻塞线程的进行的
// TODO 线程 - 阻塞
// wait & sleep
// 1. 名字
// wait : 等待
// sleep : 休眠
// 2.从属关系
// wait : Object, 成员方法(只要是Object对象都有)
// sleep : Thread, 静态方法(只有 Thread类才能使用)
// 3. 使用方式
// wait : 只能使用在同步代码中
// sleep : 可以在任意地方法使用
// 4. 阻塞时间
// wait : 超时时间(会发生错误)
// sleep : 休眠时间(不会发生错误)
// 5. 同步处理
// wait : 如果执行wait方法,那么其他线程有机会执行当前的同步操作。(因此,在同步操作的使用需要结合synchronized来使用)
// sleep : 如果执行sleep方法,那么其他线程没有机会执行当前的同步操作。

线程的安全问题

  • 所谓线程安全问题,站在开发者的角度来看那就是多个线程之间操作同一个数据,导致数据冲突了
package Chapter08;

public class Process_safe {
public static void main(String[] args) throws Exception {

// TODO 线程安全
// 所谓的线程安全问题,其实就是多个线程在并发执行时,修改了共享内存中共享对象的属性,导致的数据冲突问题

// 只有一个对象 - user
User7 user = new User7();

// 线程1 - 操作user(修改同一个对象)
Thread t1 = new Thread(()->{
user.name = "zhangsan";
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(user.name);
});

// 线程1 - 操作user(修改同一个对象)
Thread t2 = new Thread(()->{
user.name = "lisi";
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(user.name);
});
t1.start();
t2.start();

System.out.println("main线程执行完毕");
}
}

class User7 {
public String name;
}

// 代码的运行结果是输出两个 lisi,说明了两个线程操作的是同一个对象,user的name属性由 zhangsan -> lisi

解决方法:

  • 将异步并发转换成同步操作就可以很好的解决线程安全问题,同步操作可以使得代码再执行的过程中确保在同一时间只有一个线程可以访问共享资源。这样可以避免多个线程同时修改共享数据导致的数据不一致问题。