1、sleep 和 wait 的区别
wait 的功能和 sleep 类似,都是让线程暂停执行任务,但其实是两个完全不同的方法。
来自不同的类
sleep 是 Thread 类提供的方法。
wait 是 Object 类提供的方法。
作用于不同的对象
sleep 是让当前的线程实例对象暂停执行任务。
wait 是让正在访问当前对象的线程休眠,它不是针对线程对象的方法,而是针对线程对象要访问的资源的方法,即调用 A 对象的 wait 方法表示:让当前正在访问 A 对象的线程暂停,同时它有一个前提,即当前线程对象必须拥有 A 对象,所以 wait 方法只能在同步方法或同步块内部调用,否则会抛出 java.lang.IllegalMonitorStateException 异常。
是否释放锁
wait 释放锁,sleep 不释放锁。
wait 的具体使用如下所示。
代码语言:javascript复制public class Test {
public static void main(String[] args) {
A a = new A();
new Thread(()->{
for (int i = 0; i < 10; i ) {
a.test(i);
}
}).start();
}
}
class A{
public synchronized void test(int i){
if(i == 5){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i "-----------");
}
}
上述代码表示当 i=5 的时候,调用了A对象的wait方法,表示让当前访问A对象的线程即main暂停执行,进入阻塞状态,并且永远不会解除阻塞。
那如何让线程解除阻塞呢?两种方法。
1、指定 wait 的时间,调用重载方法 wait(long millis) 即可,millis 毫秒之后会自动解除阻塞,和 sleep 类似的功能。
代码语言:javascript复制class A{
public synchronized void test(int i){
if(i == 5){
try {
this.wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i "-----------");
}
}
2、通过调用 notify 方法唤醒线程。
代码语言:javascript复制public class Test {
public static void main(String[] args) {
A a = new A();
new Thread(()->{
for (int i = 0; i < 10; i ) {
a.test(i);
}
}).start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}
a.test2();
}).start();
}
}
class A{
public synchronized void test(int i){
if(i == 5){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i "-----------");
}
public synchronized void test2(){
this.notify();
}
}
2、synchronized 锁定的是谁?
如果 synchronized 修饰非静态方法,则锁定的是方法的调用者,具体代码如下所示。
代码语言:javascript复制public class Test {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{data.func1();},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{data.func2();},"B").start();
}
}
class Data{
public synchronized void func1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1...");
}
public synchronized void func2(){
System.out.println("2...");
}
}
结果是先输出 1 再输出 2,如果 Data 方法不加 synchronized,则先输出 2 再输出 1,因为有延时,但是加了 synchronized,就跟锁有关系了,synchronized 锁定的是方法调用者 data,所以就算 func1 延迟 3 秒,但是 data 对象被线程 A 锁定了,线程 B 根本无法获取 data,只能等待,线程 A 执行完毕,线程 B 才能获得锁,进而执行业务,我们把代码改一下,如下所示。
代码语言:javascript复制public class Test {
public static void main(String[] args) {
Data data1 = new Data();
Data data2 = new Data();
new Thread(()->{data1.func1();}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{data2.func2();}).start();
}
}
class Data{
public synchronized void func1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1...");
}
public synchronized void func2(){
System.out.println("2...");
}
}
这时候就先输出 2 再输出 1,因为现在是两个对象,线程 A 只是锁定了 data1,但是线程 B 锁定的是 data2,两个完全不冲突,所以不会形成线程同步。
再比如代码改为如下所示,增加第三个非 synchronized 方法。
代码语言:javascript复制public class Test {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{data.func1();}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{data.func3();}).start();
}
}
class Data{
public synchronized void func1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1...");
}
public synchronized void func2(){
System.out.println("2...");
}
public void func3(){
System.out.println("3...");
}
}
结果先输出 3 再输出 1,因为 func3 没有 synchronized,所以它就不需要去争夺锁,就不是同步方法,所以不会去排队。
如果 synchronized 修饰的是静态方法,则锁定的是类,无论多少个对象调用,都会同步,如下所示。
代码语言:javascript复制public class Test {
public static void main(String[] args) {
Data data1 = new Data();
Data data2 = new Data();
new Thread(()->{data1.func1();}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{data2.func2();}).start();
}
}
class Data{
public synchronized static void func1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1...");
}
public synchronized static void func2(){
System.out.println("2...");
}
}
虽然分别用 data1 和 data2 对象调用 func1 和 func2,但是因为 func1 和 func2 都是静态方法,所以锁定的并不是对象,而是 Data 类,因为 Data 类只有一个,所以线程同步。
如果 synchronized 静态方法和 synchronized 实例方法同时存在,静态方法锁的是类,实例方法锁对象。
代码语言:javascript复制public class Test {
public static void main(String[] args) {
Data data1 = new Data();
Data data2 = new Data();
new Thread(()->{data1.func1();}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{data2.func2();}).start();
}
}
class Data{
public synchronized static void func1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1...");
}
public synchronized void func2(){
System.out.println("2...");
}
}
上述代码不会实现同步,各锁各的,互不影响,修改代码如下所示,只保留一个对象,结果不变,一个锁类,一个锁对象。
代码语言:javascript复制public class Test {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{data.func1();}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{data.func2();}).start();
}
}
class Data{
public synchronized static void func1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1...");
}
public synchronized void func2(){
System.out.println("2...");
}
}
如果 synchronized 修饰的是代码块,则锁定的就是传入的对象,能否实现线程同步,就看锁定的对象是否是同一个对象。
代码语言:javascript复制public class Test2 {
public static void main(String[] args) {
Data2 data2 = new Data2();
for(int i=0;i<5;i ){
Integer num = Integer.valueOf(1);
new Thread(()->{
data2.func(num);
}).start();
}
}
}
class Data2{
public void func(Integer num){
synchronized (num){
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
}
上述代码的结果如下所示。
实现了线程同步,因为有排队,那有的同学会问了,明明每次传入的 num 都是不同的对象,为什么还会同步呢?因为虽然是 5 个不同的 num,但是它们是包装类,当 Integer 的值大于 -128 小于等于 127 的时候,会使用包装类常量池,所以 5 个 num 是同一个对象,把 Integer 的值改为 128 ,结果如下所示。
没有同步,因为此时 5 个 num 不是同一个对象,同理,如果使用构造器创建 num,即使值一样也不会同步。
代码语言:javascript复制public class Test2 {
public static void main(String[] args) {
Data2 data2 = new Data2();
for(int i=0;i<5;i ){
Integer num = new Integer(1);
new Thread(()->{
data2.func(num);
}).start();
}
}
}
class Data2{
public void func(Integer num){
synchronized (num){
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
}
使用其他类也是一样,不会同步的,如下所示。
代码语言:javascript复制public class Test2 {
public static void main(String[] args) {
Data2 data2 = new Data2();
for(int i=0;i<5;i ){
A a = new A();
new Thread(()->{
data2.func(a);
}).start();
}
}
}
class Data2{
public void func(A a){
synchronized (a){
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end...");
}
}
}
class A{}
结果如下所示。
代码语言:javascript复制public class Test {
public static void main(String[] args) {
Account account = new Account();
new Thread(()->{
account.count();
},"A").start();
new Thread(()->{
account.count();
},"B").start();
}
}
class Account{
private Integer num = 0;
private Integer id = 0;
public void count(){
synchronized (num){
num ;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() "是第" num "位访客");
}
}
}
上述代码中,如果锁定 num 不能同步,锁定 id 可以同步,原因是什么?
因为 synchronized 必须锁定唯一的元素才可以实现同步。
num 的值每次都在变,所以 num 所指向的引用一直在变,不是唯一的元素,肯定无法实现同步。
id 的值永远不变,所以是唯一的元素,可以实现同步。