Java基础(线程方法,线程同步,死锁)

NWD 格式转换 STL 全流程:专业方案与迪威模型网在线转换详解 在3D模型处理中,将NWD文件转换为STL格式是常见的需求,特别是在3D打印和计算机辅助制造等领域。NWD文件由Autodesk开发,主要用于建筑、工程和施工行业,包含丰富的几何图形和材质信息,但因其专有格式和大文件大小,限制了其应用范围。相比之下,STL格式广泛应用于3D打印,仅包含表面几何信息。转换NWD到STL的原因包括拓展模型应用场景和便于模型编辑。转换方法包括使用专业软件如Autodesk Navisworks配合Blender或3ds Max,或使用专门的转换工具如CADExchanger。 阅读详情

1 sleep方法

线程类Thread中的sleep方法:

public static native void sleep(long millis) throws InterruptedException;

该静态方法可以让当前执行的线程暂时休眠指定的毫秒数:

public class Test {

    public static void main(String[] args) {


        Thread t1 = new Thread("t1线程"){
            @Override
            public void run() {
                try {
                    //t1线程休眠10毫秒
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
		
        //t1还没有启动,这里肯定是NEW状态
        System.out.println(t1.getState());
		
        //启动t1线程
        t1.start();
		
        //在循环期间查看t1的状态1000次
        //这里t1的状态可能是RUNNABLE,也可能是TIMED_WAITING,也可能是TERMINATED
        for (int i = 0; i < 1000; i++) {
            System.out.println(t1.getState());
        }
		
      
    }

}

//运行结果: 可以多运行几次,每次结果可能不一样
NEW
RUNNABLE
RUNNABLE
RUNNABLE
RUNNABLE
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
.....
RUNNABLE
RUNNABLE
TERMINATED
TERMINATED
TERMINATED
TERMINATED
.....

可以看出,线程执行了sleep方法后,会从RUNNABLE状态进入到TIMED_WAITING状态

这时候线程所处的是一种阻塞状态,是之前介绍过的三种阻塞情况的其中一种

这种阻塞的特点是:阻塞结束后,线程会自动回到RUNNABLE状态

此时的状态图为:

在这里插入图片描述

2 join方法

线程类Thread中的join方法:

public final synchronized void join(long millis)throws InterruptedException{
    //...
}
    
public final void join() throws InterruptedException{
    //...
}

使用join方法,可以让当前线程阻塞,等待另一个指定的线程运行结束后,当前线程才可以继续运行:

例如,使用无参的join方法

public class Test {

    public static void main(String[] args) {


        Thread t1 = new Thread("t1线程"){
            @Override
            public void run() {
                try {
                    //t1线程睡眠1秒钟
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1线程结束");
            }
        };

        Thread t2 = new Thread("t2线程"){
            @Override
            public void run() {
                try {
                    //t2线程调用t1.join方法
                    //t2线程进入阻塞状态
                    //t2线程要等到t1线程运行结束,才能恢复到RUNNABLE状态
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2线程结束");
            }
        };

        t1.start();
        t2.start();

        //让主线程休眠500毫秒,目的是为了给t1和t2点时间,让他们俩个线程进入状态
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(t2.getState());

    }

}

//运行结果:
WAITING
t1线程结束
t2线程结束

t2线程中,调用了t1对象的join方法,那么t2线程就会阻塞,等待t1线程的运行结束,t2线程才能恢复

可以看出,线程执行了join()方法后,会从RUNNABLE状态进入到WAITING状态

但是如果线程中调用的是有参数的join方法,线程所处的状态就不一样了:

例如,使用有参的join方法,其他代码和上面一样

public class Test {

    public static void main(String[] args) {


        Thread t1 = new Thread("t1线程"){
            @Override
            public void run() {
                try {
                    //t1线程睡眠1秒钟
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1线程结束");
            }
        };

        Thread t2 = new Thread("t2线程"){
            @Override
            public void run() {
                try {
                    //t2线程调用t1.join方法
                    //t2线程进入阻塞状态
                    //t2线程要等到t1线程运行结束,才能恢复到RUNNABLE状态
                    //2000表示,当前线程t2最多阻塞2秒钟,2秒钟之内t1线程没有结束,那么t2线程就自动恢复
                    t1.join(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2线程结束");
            }
        };

        t1.start();
        t2.start();

        //让主线程休眠500毫秒,目的是为了给t1和t2点时间,让他们俩个线程进入状态
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(t2.getState());

    }

}

//运行结果:
TIMED_WAITING
t1线程结束
t2线程结束

可以看出,线程执行了join(long million)方法后,会从RUNNABLE状态进入到TIMED_WAITING状态

此时的状态图为:

线程A中,调用了线程B对象的join方法,那么线程A就会进入到阻塞状态,这种阻塞又俩种情况,一种是调用了无参的join方法,那么此时线程A的状态为WAITING(无限期等待),另一种是调用了有参的join方法,那么此时线程A的状态为TIMED_WAITING(有限期等待)

线程A如果调用了sleep方法,那么线程A也会进入阻塞状态,此时线程A的状态为TIMED_WAITING

总结:

  • 如果指定了时间,线程阻塞一定的时间后,会自动恢复到RUNNABLE状态,这种情况下,线程的状态为TIMED_WAITING(有限期等待)
  • 如果没有指定时间,线程会一直阻塞着,直到某个条件满足时,才会自动恢复,这种情况下,线程的状态为WAITING(无限期等待)

在这种情况下,其实还有另一种方式,可以让线程从阻塞状态恢复到RUNNABLE状态,那就是调用线程的interrupt方法

3 interrupt方法

线程类Thread中的interrupt方法:

//Interrupts this thread
public void interrupt(){
    //...
}

根据上面介绍sleep方法和join方法可知,这俩个方法都会抛出InterruptedException类型的异常,说明调用sleep和join使线程进入阻塞状态的情况下,是有可能抛出InterruptedException类型的异常的。

InterruptedException异常类型指的是:线程A中,调用了线程B的interrupt方法,而此时线程B处于阻塞状态,那么此时sleep方法或者join方法就会抛出被打断的异常

public class Test {

    public static void main(String[] args) {


        Thread t1 = new Thread("t1线程"){
            @Override
            public void run() {
                try {
                    //t1线程休眠100秒
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1线程结束");
            }
        };


        t1.start();

        //让主线程休眠500毫秒,目的是为了给t1时间,让它调用sleep方法而进入阻塞状态
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //打断t1由于调用sleep方法而进入的阻塞状态
        t1.interrupt();


    }

}

//运行结果:
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.briup.sync.Test$1.run(Test.java:11)
t1线程结束

可以看出,本来t1线程调用了sleep方法进入了阻塞状态,需要100后才会恢复的,但是我们在主线程中调用了t1线程对象的打断方法interrupt,那么此时 Thread.sleep(100000);这句代码就会抛出被打断的异常,同时t1线程从阻塞状态恢复到RUNNABLE状态,继续执行代码,输出了t1线程结束

此时对于的流程图为:

在这里插入图片描述

interrupt方法的工作原理:

interrupt方法是通过改变线程对象中的一个标识的值(true|false),来达到打断阻塞状态的效果。

一个线程在阻塞状态下,会时刻监测这个标识的值是不是true,如果一旦发现这个值变为true,那么就抛出异常结束阻塞状态,并再把这个值改为false。

从Thread类的源码中可以看到:

interrupt方法中其实是调用了interrupt0这个本地方法,而interrupt0的注释为:Just to set the interrupt flag

public void interrupt() {
    if (this != Thread.currentThread())
        checkAccess();

    synchronized (blockerLock) {
        Interruptible b = blocker;
        if (b != null) {
            interrupt0();           // Just to set the interrupt flag
            b.interrupt(this);
            return;
        }
    }
    interrupt0();
}

private native void interrupt0();

可以看出,interrupt方法只是改变了线程对象中一个标识flag的值

查看线程对象中“打断标识”值的俩个方法:

线程类Thread中的isInterrupted方法:

public boolean isInterrupted() {
    return isInterrupted(false);
}
/**
 * Tests if some Thread has been interrupted.  The interrupted state
 * is reset or not based on the value of ClearInterrupted that is
 * passed.
 */
private native boolean isInterrupted(boolean ClearInterrupted);

注意,这个非静态方法,只是返回这个“打断标识”值,并且不会对这个值进行清除(true->false),因为所传参数ClearInterrupted的值为false

例如,默认情况下,一个线程对象中的“打断标识”值为false

public class Test {

    public static void main(String[] args) {


        Thread t1 = new Thread("t1线程"){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    //判断是否有其他线程调用了自己的interrupt方法
                    //调用类中的非静态方法:isInterrupted
                    System.out.println(this.isInterrupted());
                }
                System.out.println("t1线程结束");
            }
        };


        t1.start();

    }

}

//运行结果:
false
false
false
false
false
false
false
false
false
false
t1线程结束


无论线程是否处于阻塞状态,其他线程都可以调用这个线程的interrupt方法,因为该方法只是改变线程对象中“打断标识”值而已

例如,

public class Test {

    public static void main(String[] args) {


        Thread t1 = new Thread("t1线程"){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    //判断是否有其他线程调用了自己的interrupt方法
                    //调用类中的非静态方法:isInterrupted
                    System.out.println(this.isInterrupted());
                }
                System.out.println("t1线程结束");
            }
        };


        t1.start();

        t1.interrupt();


    }

}

//运行结果:
true
true
true
true
true
true
true
true
true
true
t1线程结束

可以看出,吊用了t1.interrupt();后,t1线程中的“打断标识”值设置为了true,可以通过线程对象中的isInterrupted方法返回这个标识的值,并且不会修改这个值,所以输出显示的一直是ture

线程类Thread中的interrupted方法:

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}
/**
 * Tests if some Thread has been interrupted.  The interrupted state
 * is reset or not based on the value of ClearInterrupted that is
 * passed.
 */
private native boolean isInterrupted(boolean ClearInterrupted);

注意,这个静态方法,返回这个“打断标识”值,并且会对这个值进行清除(true->false),因为所传参数ClearInterrupted的值为true

例如,

public class Test {

    public static void main(String[] args) {


        Thread t1 = new Thread("t1线程"){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    //判断是否有其他线程调用了自己的interrupt方法
                    //调用类中的静态方法:interrupted
                    System.out.println(Thread.interrupted());
                }
                System.out.println("t1线程结束");
            }
        };


        t1.start();

        t1.interrupt();


    }

}

//运行结果:
true
false
false
false
false
false
false
false
false
false
t1线程结束

可以看出,第一次返回true之后,后面在调用方法查看这个“打断标识”值,都是false,因为静态方法 interrupted返回true后,会直接把这个值给清除掉。(true->false)

Thread类中的三个方法:interrupt()、isInterrupted()、interrupted()的结构关系大致如下:

public class Thread{
    
    public void interrupt() {
        //...
        interrupt0(); // Just to set the interrupt flag
    }
    private native void interrupt0();
    
    public boolean isInterrupted() {
        return isInterrupted(false);
    }
    
    public static boolean interrupted() {
        return currentThread().isInterrupted(true);
    }
    
   /**
     * Tests if some Thread has been interrupted.  The interrupted state
     * is reset or not based on the value of ClearInterrupted that is
     * passed.
     */
    private native boolean isInterrupted(boolean ClearInterrupted);
    
    public static native Thread currentThread();  
    
}

根据上面的代码结构,还有前面的一些示例以及解释,这个三个名字很像的方法,就应该容易分清楚了。

4 线程安全

JVM内存中的堆区,是一个共享的区域,是所有线程都可以访问的内存空间。

JVM内存中的栈去,是线程的私有空间,每个线程都有自己的栈区,别的先无法访问到自己栈区的数据。

我们之前编写的代码只有一个main线程,只有它自己去访问堆区中对象的数据,自然不会有什么问题。

但是在多线程环境中,如果有俩个线程并发访问堆区中一个对象中的数据,那么这个数据可能会出现和预期结果不符的情况,例如

public class Test {

    public static void main(String[] args) {

        final MyData myData = new MyData();

        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                for (int i = 0; i < 10; i++) {
                    //先给num赋值
                    myData.num = i;
                    //然后再输出num的值
                    System.out.println(name + ": " + myData.num);
                }

            }
        };

       
        t1.start();
        
    }

}

class MyData{
    int num;
}

//运行结果:
t1: 0
t1: 1
t1: 2
t1: 3
t1: 4
t1: 5
t1: 6
t1: 7
t1: 8
t1: 9

这时候,t1线程自己访问堆区中的myData对象里面的num数据值,程序的结果每次都是和预期的一样

但是,如果再加一个线程t2,同时也访问这个堆区中myData对象中的num属性值,结果可能和预期的不一样:

public class Test {

    public static void main(String[] args) {

        MyData myData = new MyData();

        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                for (int i = 0; i < 10; i++) {
                    //先给num赋值
                    myData.num = i;
                    //然后再输出num的值
                    System.out.println(name + ": " + myData.num);
                }

            }
        };

        Thread t2 = new Thread("t2"){
            @Override
            public void run() {

                for (int i = 100; i < 20000; i++) {
                    //给num赋值
                    myData.num = i;
                }

            }
        };

        t1.start();
        t2.start();



    }

}

class MyData{
    int num;
}
//运行结果:
t1: 0
t1: 11706
t1: 13766
t1: 15459
t1: 17710
t1: 19304
t1: 6
t1: 7
t1: 8
t1: 9

可以看到,每次运行的结果中,t1线程输出的num的值可能和预期都不一样

此时的内存图为:

在这里插入图片描述

注意1,t1和t2并发访问的时候,争夺CPU的时间片,运行完时间片,退出后再次争夺下一个时间片,也就是说t1和t2都是“断断续续”的运行的

注意2,在这期间,可能t1线程有一次拿到时间片运行的时候,给num赋值为1,然后时间片用完退出了,结果下次t2线程拿到了时间片,又将num的值赋成了11750,然后t1线程又拿到了时间片,本来预期的是输出1,但是结果却是输出了11750

核心的原因是,t1线程操作一下变量num,然后时间片用完退出去,t2先过来又操作了变量num,等t1线程再过来的时候,这值已经被t2线程给“偷偷”修改了,那么就出现了和预期不符的情况

总结:

如果有多个线程,它们在一段时间内,并发访问堆区中的同一个变量,并且有写入的操作,那么最终可能会出数据的结果和预期不符的情况,这种情况就是线程安全问题。

我们经常会进行这样的描述:这段代码是线程安全的,那段代码是非线程安全的。 其实就是在说,这段代码在多线程并发访问的环境中,是否会出现上述情况,也就是结果和预期不符的情况。

例如,观察下面代码,是线程安全的还是非线程安全的? t1线程每次输出的结果是否有出现和预期不符的情况?

public class Test {

    public static void main(String[] args) {


        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                //每次循环改变变量i的值
                for (int i = 0; i < 10; i++) {
                    //输出变量i的值
                    System.out.println(name + ": " + i);
                }

            }
        };

        Thread t2 = new Thread("t2"){
            @Override
            public void run() {
				//每次循环改变变量i的值
                for (int i = 100; i < 20000; i++) {

                }

            }
        };

        t1.start();
        t2.start();



    }

}

每次执行结果都是一样的,和预期的相同,因为t1和t2俩个线程根本就没访问同一个共享变量!相当于t1和t2都是各自操作各自的变量i

思考,方法中的局部变量和对象中的成员变量分别在内存中什么地方?哪些变量可能被多个线程共享?

思考,如果t1和t2先不是“争先恐后”的并发访问变量num,而是排好队一个执行完,另一个在执行,是不是就可以解决这个线程安全问题了呢?

5 线程同步

当使用多个线程访问同一个共享变量的时候,并且线程中对变量有写的操作,这时就容易出现线程安全问题。

Java中提供了线程同步的机制,来解决上述的线程安全问题。

Java中实现线程同步的方式,是给需要同步的代码进行synchronized关键字加锁。

例如,改造之前有线程安全问题的代码,给需要同步的代码使用synchronized加锁

public class Test {

    public static void main(String[] args) {

        MyData myData = new MyData();

        Thread t1 = new Thread("t1"){
            @Override
            public void run() {

                String name = Thread.currentThread().getName();
                
                synchronized (myData){
                    for (int i = 0; i < 10; i++) {
                        myData.num = i;
                        System.out.println(name + ": " + myData.num);
                    }
                }

            }
        };

        Thread t2 = new Thread("t2"){
            @Override
            public void run() {

                synchronized (myData){
                    for (int i = 100; i < 20000; i++) {
                        myData.num = i;
                    }
                }

            }
        };

        t1.start();
        t2.start();

    }

}

class MyData{
    int num;
}

现在运行这个程序,每次输出的结果都是和预期的一样:t1线程每次输出的值都是0~9

分析:

线程同步的效果,就是一段加锁的代码,每次只能有一个拿到锁的线程,才有资格去执行,没有拿到的锁的线程,只能等拿到锁的线程把代码执行完,再把锁给释放了,它才能去拿这个锁然后再运行代码。

这样以来,本来这段代码是俩线程并发访问,“争先恐后”的去执行的,现在线程同步之后,这段代码就变成了先又一个拿到的锁的先去执行,执行完了,再由另一个线程拿到锁去执行。

相当于是大家每个线程不要抢,排好队一个一个去执行,那么这时候共享的变量的值,肯定不会出现线程安全问题!

例如,synchronized修饰代码块的使用格式为:

synchronized (锁对象){
    //操作共享变量的代码,这些代码需要线程同步,否则会有线程安全问题
    //...
}

对应这样加锁的代码,如果俩个线程进行并发访问的话:

  • 假设线程t1是第一个这段代码的线程,那么它会率先拿到这把锁,其实就是在这个锁对象中写入自己线程的信息,相当于告诉其他线程,这把锁现在是我的,你们都不能使用。
  • 这时候t1线程拿着锁,就可以进入到加锁的代码块中,去执行代码,执行很短的一个时间片,然后退出,但是锁并不释放,也就意味着,及时下次是t2线程抢到CPU的使用权,它也无法运行代码,因为t2没有拿到锁。
  • 就这样,t1线程开心的拿着锁,抢到CPU的执行权,抢到了就去执行,抢不到也不用担心,因为没有其他线程可以“偷偷”的执行这段代码,因为其他线程拿不到锁。
  • 而对于t2线程来说,即使有一次抢到了CPU执行权,来到了代码面前,要执行的时候才发现,锁被t1线程拿走了,自己无法进入代码块中执行,这时候t2线程就会从运行状态进入阻塞状态,直到t1运行完,把锁释放了,t2线程才会恢复到RUNNABLE状态,抢到CPU执行权,再拿到锁,然后进入代码块中执行

注意,这时候t2线程的阻塞状态,和之前学习的调用sleep或join方法进入的阻塞不同,这种阻塞属于锁阻塞,需要等待另一个线程把锁释放了,t2线程才能恢复。如果t2线程处于这种阻塞,那么调用线程对象的getState方法返回的状态名称为:BLOCKED

例如:

public class Test {

    public static void main(String[] args) {

       Object obj = new Object();

        Thread t1 = new Thread("t1"){
            @Override
            public void run() {

                synchronized (obj){
                    try {
                        Thread.sleep(100000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

            }
        };

        Thread t2 = new Thread("t2"){
            @Override
            public void run() {

                synchronized (obj){

                }

            }
        };

        t1.start();

        //主线程休眠1秒钟,给t1线程点时间,让他先拿到锁,然后去休眠100秒
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t2.start();

        System.out.println("t1线程状态:"+t1.getState());
        System.out.println("t2线程状态:"+t2.getState());


    }

}
//运行结果:
t1线程状态:TIMED_WAITING
t2线程状态:BLOCKED

注意1,t1线程需要拿到锁对象obj,才能运行加锁的代码块

注意2,t2线程也需要拿到锁对象obj,才能运行加锁的代码块

注意3,锁对象obj只有一个,所以t1和t2只能有一个线程先拿到,拿到后执行代码,那么另一个就拿不到了,拿不到就阻塞,此时线程的状态为:BLOCKED

注意4,java中,任意一个对象,只要是对象,就可以用来当做,加锁代码块中的锁对象。然后让多个线程去抢这拿这把锁就可以了,此时就达到了线程同步的效果,因为拿到的锁线程能执行代码,其他拿不到的线程就不执行,并且进入阻塞状态。

此时的线程状态图为:

在这里插入图片描述

可以看出,假设t1线程拿到了锁,t2线程没拿到锁,那么t2线程就会因为锁不可用,进入到锁阻塞状态,直到t1先把加锁的代码执行完,把锁释放了,锁变的可用了,这是t2线程会自动恢复到RUNNABLE状态

注意,t1线程“拿到”锁,只是一种形象的说,就是我们之前说的 引用“指向”对象一样。其实是t1线程把自己的信息写入到了锁对象中,用这种方式告诉其他线程,这个锁对象已经被我 "拿走"了

6 synchronized

通过上面线程同步例子的讲解,我们已经知道了synchronized修饰一个代码块,并指定谁是锁对象的用法,除此之外,还可以使用synchronized直接修饰一个方法,表示这个方法中的所有代码都需要线程同步。

synchronized关键字修饰非静态方法,默认使用this当做锁对象,并且不能自己另外指定

synchronized关键字修饰静态方法,默认使用当前类的Class对象当做锁对象,并且不能自己另外指定

这俩中情况的同步效果是一样的,只是锁对象不同而已

例如,

public class MyData{
    private int[] arr = new int[20];
    //当前数据可以存放的位置,也表示当前存放的元素个数
    private int current;

    //添加数据
    public void add(int num){
        String name = Thread.currentThread().getName();
        arr[current] = num;
        System.out.println(name+"线程本次写入的值为"+num+",写入后取出的值为"+arr[current]);

        current++;
    }

}

注意,MyData类中的add方法,在多线程并发访问的环境中,是有线程安全问题的

例如,

public class Test {

    public static void main(String[] args) {

        MyData myData = new MyData();

        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    myData.add(i);
                    //计算机运行10次运行太快了,让它执行慢一些,好观察效果
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        //t2线程的名字前面加个制表符\t,打印的时候好观察
        Thread t2 = new Thread("\tt2"){
            @Override
            public void run() {
                for (int i = 10; i < 20; i++) {
                    myData.add(i);
                    //计算机运行10次运行太快了,让它执行慢一些,好观察效果
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        t1.start();
        t2.start();


    }

}

//运行结果:
t1线程本次写入的值为0,写入后取出的值为0
	t2线程本次写入的值为10,写入后取出的值为10
	t2线程本次写入的值为11,写入后取出的值为1
t1线程本次写入的值为1,写入后取出的值为1
	t2线程本次写入的值为12,写入后取出的值为2
t1线程本次写入的值为2,写入后取出的值为2
	t2线程本次写入的值为13,写入后取出的值为3
t1线程本次写入的值为3,写入后取出的值为3
	t2线程本次写入的值为14,写入后取出的值为4
t1线程本次写入的值为4,写入后取出的值为4
t1线程本次写入的值为5,写入后取出的值为5
	t2线程本次写入的值为15,写入后取出的值为5
	t2线程本次写入的值为16,写入后取出的值为16
t1线程本次写入的值为6,写入后取出的值为16
t1线程本次写入的值为7,写入后取出的值为17
	t2线程本次写入的值为17,写入后取出的值为17
t1线程本次写入的值为8,写入后取出的值为18
	t2线程本次写入的值为18,写入后取出的值为18
t1线程本次写入的值为9,写入后取出的值为19
	t2线程本次写入的值为19,写入后取出的值为19

可以看出,在某一次add方法执行的时候,会出现写入的数据和当前的数据不一致的情况

此时,我们可以直接在add方法(非静态方法)上,添加修饰符synchronized关键字,表示给这个方法中的所有代码进行线程同步,默认使用的锁对象是this

public synchronized void add(int num){
    String name = Thread.currentThread().getName();
    arr[current] = num;
    System.out.println(name+"线程本次写入的值为"+num+",写入后取出的值为"+arr[current]);

    current++;
}

此时,再运行代码,就不会出现之前那种线程安全问题了

该代码表示,拿到锁对象this的线程,才可以进入到add方法中执行代码,代码执行完,会释放锁,这时锁变的可用了,所有需要这把锁的线程都恢复到RUNABLE状态(它们之前在锁阻塞状态),这些线程一起重新争夺CPU执行权,谁先拿到CPU执行权,就会先过去拿到锁,进入代码去执行

注意,此时t1线程中调用add方法,争夺的锁对象this就是myData对象,t2线程中调用的add方法,争夺的锁对象this也是myData对象,所以t1和t2俩个线程争夺的是同一把锁对象,那么就能达到线程同步的效果!

所以,线程同步的效果的关键点在于,让t1和t2俩个线程去争夺同一把锁对象

思考,根据上面的例子,考虑为什么之前学习的ArrayList中的add方法是非线程安全的,而Vector中的add方法是线程安全的?

7 wait和notify

Object类中有三个方法:wait()、notify()、notifyAll

当一个对象,在线程同步的代码中,充当锁对象的时候,在synchronized同步的代码块中,就可以调用这个锁对象的这三个方法了。

三个核心点:

  • 任何对象中都一定有这三个方法
  • 只有对象作为锁对象的时候,才可以调用
  • 只有在同步的代码块中,才可以调用

其他情况下,调用一个对象的这三个方法,都会报错!

synchronized关键字,虽然可以达到线程同步的效果,但是太“霸道”了,只要一个线程拿到了锁对象,那么这个线程无论是在运行状态,还是时间片用完,回到就绪状态,还是sleep休眠,这个线程都是死死的拿着这个锁对象不释放,只有这个线程把线程同步的代码执行完,才会释放锁对象让别的线程使用。

那么有没有一个方法,可以让拿到的锁的线程,即使代码没执行完,也可以把锁立即给释放了呢?

有的,这个就是wait方法

例如,

public class Test {

    public static void main(String[] args) {

        final Object obj = new Object();

        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                synchronized (obj){
                    for (int i = 0; i < 10; i++) {
                        System.out.println(name+"线程: i = "+i);
                    }
                }
            }
        };

        Thread t2 = new Thread("t2"){
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                synchronized (obj){
                    for (int j = 10; j < 20; j++) {
                        System.out.println(name+"线程: j = "+j);
                    }
                }
            }
        };

        t1.start();
        t2.start();


    }

}

t1和t2俩个线程,争夺同一把锁对象obj,所以程序的运行结果是:要么t1先拿到锁输处09,然后t2再拿到锁输出1019,要么就是t2先拿到锁输入1019,然后t1再拿到锁输出09

现在,我们希望的是t1线程中i=5的时候,先释放锁,让t2拿到锁去运行,在t2线程中,当j=15的时候,释放锁,让t1拿到锁去运行:

在代码中加入条件判断和wait方法的调用

public class Test {

    public static void main(String[] args) {

        final Object obj = new Object();

        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                synchronized (obj){
                    for (int i = 0; i < 10; i++) {
                        System.out.println(name+"线程: i = "+i);
                        if(i==5){
                            try {
                                //obj是所调用,在同步代码块中,可以调用wait方法
                                //让当前拿到锁的线程,立即释放锁
                                obj.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        };

        Thread t2 = new Thread("t2"){
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                synchronized (obj){
                    for (int j = 10; j < 20; j++) {
                        System.out.println(name+"线程: j = "+j);
                        if(j==15){
                            try {
                                //obj是所调用,在同步代码块中,可以调用wait方法
                                //让当前拿到锁的线程,立即释放锁
                                obj.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        };

        t1.start();
        t2.start();


    }

}

//运行结果:
t1线程: i = 0
t1线程: i = 1
t1线程: i = 2
t1线程: i = 3
t1线程: i = 4
t1线程: i = 5
t2线程: j = 10
t2线程: j = 11
t2线程: j = 12
t2线程: j = 13
t2线程: j = 14
t2线程: j = 15

可以看到,t1线程和t2线程都没有运行完,但是代码不运行了,JVM也没停住

这是因为,当前调用锁对象的wait方法后,当前线程释放锁,然后进入到阻塞状态,并且等待其他线程先唤醒自己,如果没有其他线程唤醒自己,那么就一直等着。所以现在的情况是,俩个线程t1和t2都是在处于阻塞状态,等待别人唤醒自己,所以程序不运行了,但是也没结束!

此时,调用t1和t2的getState方法,返回的状态为:WAITING

public class Test {

    public static void main(String[] args) {

        final Object obj = new Object();

        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                synchronized (obj){
                    for (int i = 0; i < 10; i++) {
                        System.out.println(name+"线程: i = "+i);
                        if(i==5){
                            try {
                                obj.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        };

        Thread t2 = new Thread("t2"){
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                synchronized (obj){
                    for (int j = 10; j < 20; j++) {
                        System.out.println(name+"线程: j = "+j);
                        if(j==15){
                            try {
                                obj.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        };

        t1.start();
        t2.start();

        //主线程休眠1秒钟,给t1和t2点时间,等它们调用wait方法
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("t1线程当前的状态为:"+t1.getState());
        System.out.println("t2线程当前的状态为:"+t2.getState());


    }

}

//运行结果:
t1线程: i = 0
t1线程: i = 1
t1线程: i = 2
t1线程: i = 3
t1线程: i = 4
t1线程: i = 5
t2线程: j = 10
t2线程: j = 11
t2线程: j = 12
t2线程: j = 13
t2线程: j = 14
t2线程: j = 15
t1线程当前的状态为:WAITING
t2线程当前的状态为:WAITING

此时线程的状态图为:

在这里插入图片描述

可以看出,此时线程调用了wait方法,释放了锁,变为阻塞状态(WAITING),并进入了等待池,等待其他线程唤醒自己或者打断自己,如果有线程调用了notify方法进行了唤醒,或者interrupt方法进行了打断,那么这个线程就会从等待池进入到锁池,而进入到锁池的线程,会时刻关注锁对象是否可用,一旦可用,这个线程就会立刻自动恢复到RUNNABLE状态。

由图可知,TIMED_WAITING、WAITING、BLOCKED都属于线程阻塞,他们共同的特点是就是线程不执行代码,也不参与CPU的争夺,除此之外,它们还有各自的特点:(重要)

  • 阻塞1,线程运行时,调用sleep或者join方法后,进入这种阻塞,该阻塞状态可以恢复到RUNNABLE状态,条件是线程被打断了、或者指定的时间到了,或者join的线程结束了
  • 阻塞2,线程运行时,发现锁不可用后,进入这种阻塞,该阻塞状态可以恢复到RUNNABLE状态,条件是线程需要争夺的锁对象变为可用了(别的线程把锁释放了)
  • 阻塞3,线程运行时,调用了wait方法后,线程先释放锁后,再进入这种阻塞,该阻塞状态可以恢复到BLOCKED状态(也就是阻塞2的情况),条件是线程被打断了、或者是被别的线程唤醒了(notify方法)

理解上述的状态变化过程后,我们修改代码,加入notify方法的调用:

public class Test {

    public static void main(String[] args) {

        final Object obj = new Object();

        Thread t1 = new Thread("t1"){
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                synchronized (obj){
                    for (int i = 0; i < 10; i++) {
                        System.out.println(name+"线程: i = "+i);
                        if(i==5){
                            try {
                                //在释放锁对象之前,叫醒等待池中等待obj锁对象的线程
                                //意思是告诉对方,我要释放锁了,你准备去抢把
                                obj.notify();
                                obj.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }

                    //最后在执行完所有代码之前,在叫醒一次,防止等待池中还线程在等待obj这个锁对象
                    obj.notify();

                }
            }
        };

        Thread t2 = new Thread("t2"){
            @Override
            public void run() {
                String name = Thread.currentThread().getName();
                synchronized (obj){
                    for (int j = 10; j < 20; j++) {
                        System.out.println(name+"线程: j = "+j);
                        if(j==15){
                            try {
                                //在释放锁对象之前,叫醒等待池中等待obj锁对象的线程
                                //意思是告诉对方,我要释放锁了,你准备去抢把
                                obj.notify();
                                obj.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }

                    //最后在执行完所有代码之前,在叫醒一次,防止等待池中还线程在等待obj这个锁对象
                    obj.notify();

                }
            }
        };

        t1.start();
        t2.start();

        //主线程休眠1秒钟,给t1和t2点时间,等它们调用wait方法
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("t1线程当前的状态为:"+t1.getState());
        System.out.println("t2线程当前的状态为:"+t2.getState());


    }

}
//运行结果:
t1线程: i = 0
t1线程: i = 1
t1线程: i = 2
t1线程: i = 3
t1线程: i = 4
t1线程: i = 5
t2线程: j = 10
t2线程: j = 11
t2线程: j = 12
t2线程: j = 13
t2线程: j = 14
t2线程: j = 15
t1线程: i = 6
t1线程: i = 7
t1线程: i = 8
t1线程: i = 9
t2线程: j = 16
t2线程: j = 17
t2线程: j = 18
t2线程: j = 19
t1线程当前的状态为:TERMINATED
t2线程当前的状态为:TERMINATED


可以看到,此时t1和t2俩个线程都执行完了,打印输出的结果也符合我们的预期

锁对象.notify(),该方法可以在等待池中,随机唤醒一个等待指定锁对象的线程,使得这个线程进入到锁池中,而进入到锁池的线程, 一旦发现锁可用,就可以自动恢复到RUNNABLE状态了

锁对象.notifyAll(),该方法可以在等待池中,唤醒所有等待指定锁对象的线程,使得这个线程进入到锁池中,而进入到锁池的线程, 一旦发现锁可用,就可以自动恢复到RUNNABLE状态了

思考,线程调用无参的wait方法,会释放锁并进入等待池,且此时的状态为WAITING,如果线程调用有参的wait方法,指定一个等待时间,那么线程释放锁后,进入到等待池,此时的状态还是WAITING么?

那就不是的,如果指定了一个等待时间,那状态就是timed_waiting了,这两者的区别就是waiting是一定要通过别的方法唤醒它才能进入就绪状态,而timed_waiting只要时间到了自动变为就绪状态同其他线程争取时间片。

案例:

根据以下代码,补全pos和wit方法完成其功能

要求,银行账号的余额不能是负数

public class Account {
    //账号余额
    private int balance;
    public Account(int balnace) {
        this.balance = balnace;
    }
    //存钱
    public void pos(int money){

    }
    //消费
    public void wit(int money){

    }
}
//男孩,负责挣钱
public class Boy extends Thread{
    private Account account;
    public Boy(Account account, String name) {
        this.account = account;
        setName(name);
    }
    public void run() {
        //一直不停的挣钱
        while(true){
            //随机钱数
            int money = (int)(Math.random()*10000+1);
            //存进账户
            account.pos(money);
            try {
                //休息1秒后,继续挣钱
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}
//女孩,负责花钱
public class Girl extends Thread{
    private Account account;
    public Girl(Account account, String name) {
        this.account = account;
        setName(name);
    }
    public void run() {
        //一直不停的花钱
        while(true){
            //随机钱数(看心情)
            int money = (int)(Math.random()*10000+1);
            //刷卡消费
            account.wit(money);
            try {
                //休息1秒后,继续花钱
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class AccountTest {
    public static void main(String[] args) {
        Account account = new Account(5000);
        Boy boy = new Boy(account, "tom");
        Girl lily1 = new Girl(account, "lily1");
        Girl lily2 = new Girl(account, "lily2");
        Girl lily3 = new Girl(account, "lily3");

        boy.start();
        lily1.start();
        lily2.start();
        lily3.start();
    }
}

8 死锁

在程序中要尽量避免出现死锁情况,一旦发生那么只能手动停止JVM的运行,然后查找并修改产生死锁的问题代码

简单的描述死锁就是:俩个线程t1和t2,t1拿着t2需要等待的锁不释放,而t2又拿着t1需要等待的锁不释放,俩个线程就这样一直僵持下去。

例如,

public class ThreadDeadLock extends Thread{
    private Object obj1;
    private Object obj2;

    public ThreadDeadLock(Object obj1,Object obj2) {
        this.obj1 = obj1;
        this.obj2 = obj2;
    }

    public void run() {
        String name = Thread.currentThread().getName();
        if("Thread-0".equals(name)){
            while(true){
                synchronized (obj1) {
                    synchronized (obj2) {
                        System.out.println(name+" 运行了..");
                    }
                }
            }
        }
        else{
            while(true){
                synchronized (obj2) {
                    synchronized (obj1) {
                        System.out.println(name+" 运行了..");
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        Object obj1 = new Object();
        Object obj2 = new Object();
        Thread t1 = new ThreadDeadLock(obj1,obj2);
        Thread t2 = new ThreadDeadLock(obj1,obj2);
        t1.start();
        t2.start();
    }
}

注意,可以通过jconsole查看到线程死锁的情况

Appstore 3.2(f)违规自救指南:从触发到申诉的完整应对策略 本文详细解析了Appstore 3.2(f)违规的应对策略,从触发原因到申诉流程,帮助开发者有效自救。文章涵盖隐瞒行为、账户关联和信息造假三大违规场景的判定逻辑,并提供申诉信写作的黄金法则和长期合规防御措施,确保开发者能够顺利通过苹果审核。 阅读详情

相关推荐

从 Pandas 到 Polars 十八:数据科学 2025,对未来几年内数据科学领域发展的预测或展望

Rust 在数据科学家中的普及将受到 Polars 的推动,用户会发现从使用 Python 切换到 Rust API 的门槛相对较低——比从 Pandas 切换到 C 的门槛要低得多!在数据科学蓬勃发展的十年里,我所在领域的日常工作主要由像 Python 这样的动态语言或像 R 和 Julia 这样使用即时编译的语言主导。尽管这两个库的功能有很多重叠,但有些人会想要一个像 DuckDB 这样的工具,它拥有更多关系型数据库的陷阱,而有些人则不会。然而,一位受访者正确地指出,基于GPU的库(如cuDF)更快。

sosogod的博客 2466

JAVA--线程(2)

本篇用来测试线程常用的方法与关键字. sleep()与interrupt() static void sleep(long mills):使线程暂时进入"睡眠"状态,持续时间mills毫秒 boolean interrupt():中断线程. public class SleepTest extends Thread{ public static void main(String[...

bujiujie8的博客 406

【ISAR成像】基于Matlab的飞机点目标模型仿真与图像优化

本文详细介绍了如何使用Matlab进行ISAR成像仿真,重点讲解了从构建飞机点目标模型到生成清晰图像的完整流程。通过模拟雷达回波、距离压缩、运动补偿及方位压缩等核心信号处理步骤,并结合相位梯度自聚焦等优化技巧,有效提升了ISAR图像的清晰度与分辨率。文章还分享了雷达参数调优、加窗处理等实战经验,为雷达信号处理与目标识别研究提供了实用指导。

weixin_29071809的博客 388

线程(2)

线程池 概述 线程池就是一个可以复用线程的技术 不使用线程池的问题: 线程池实现的API,参数说明 常见的面试问题 线程池处理Runnable任务 参数七新任务拒绝策略 package com.itxue.d7_threadpool; public class MyRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 5; ...

weixin_60475929的博客 336

Windows系统下AutoDock 4.2.6安装避坑指南(附MGLTools配置技巧)

本文详细介绍了在Windows系统下安装和配置AutoDock 4.2.6的完整流程,特别针对分子对接研究中常见的软件安装问题提供了解决方案。从环境准备、分步安装到常见报错处理,涵盖MGLTools配置技巧、工作目录设置和环境变量优化,帮助科研人员快速搭建稳定的分子对接平台。

weixin_29234239的博客 311

【WRF理论第十四期】 VEGPARM.TBL 文件详解

WRF 读取 VEGPARM.TBL 文件

WW、forever的博客 575

java 多线程Thread类

Thread类之前在博客简单介绍了下,本篇重点介绍几个常用方法: 先看构造方法: Thread() 创建新的线程对象 Thread(String name) 基于指定的名字创建一个线程对象 Thread(Runnable target)基于Runnable接口实现类的实例(可以是匿名内部类)创建一个线程对象 Thread(Runnable t,String name) 根据给定的Runn...

sinat_41132860的博客 332

基于 Simulink 的三相四线制整流器在三相不平衡负载下的中点电位平衡控制实战教程

本文为三相四线制整流器在中点电位不平衡时的Simulink仿真教程。首先分析中点电位漂移的电荷不平衡机理,提出零序电压注入控制策略。详细演示建模步骤:搭建三相四线制主电路,构建中点平衡控制算法(含MATLAB函数代码),实现控制信号叠加与PWM生成。通过对比仿真验证控制效果,并给出零序电压限幅、中性线电感影响等工程建议。该方案能有效抑制三相不平衡负载下的中点电位波动,保证系统稳定运行。

xiaoheshang_123的博客 50

java在sleep时调用interrupt方法

sleep() & interrupt() 线程A正在使用sleep()暂停着: Thread.sleep(100000); 如果要取消他的等待状态,可以在正在执行的线程里(比如这里是B)调用     a.interrupt(); 令线程A放弃睡眠操作,这里a是线程A对应到的Thread实例 执行interrupt()时,并不需要获取Thread实例的锁定.任何线程在任何时刻,都可以

tomorrow_fine的博客 4127

【AI Guide】AI面试攻略只用看这一篇就够了!力争做全网最全的AI面试攻略——大模型(五)Adapter 微调

Adapter 微调(Adapter Fine-tuning)是一种高效的参数高效微调(PEFT)方法。相较于全参数微调(Full Fine-tuning),Adapter 在预训练模型的特定层插入轻量级的可训练模块,而冻结原始模型参数,从而减少计算和存储开销。是缩放因子,控制 Adapter 输出的影响程度。设 Transformer 层的隐藏状态为。为 Adapter 层的可训练参数,为非线性激活函数,如 ReLU。为冻结的预训练模型参数,

AI科研社 182

【AI大模型】DeepSeek + Kimi 高效制作PPT实战详解

AI赋能,使用DeepSeek 高效制作PPT实战详解

congge 1万+

一个死锁问题分析

为什么会死锁呢?一起来看看!!!

码农练功房的博客 548

Vue2 - 详细实现安装西瓜视频 xgplayer 插件详细配置及使用教程,利用西瓜视频播放器播放 mp4、flv、hls 格式视频文件及视频推流(直播或监控摄像头画面),支持移动端H5及PC网站!

vue2,nuxt2,西瓜视频,xgplayer,插件组件,xgplayer-hls,xgplayer-flv,xgplayer-mp4,播放视频点播和直播视频流,自定义播放器样式,截屏,监控视频,vue使用西瓜播放器播放flv、hls、mp4视频,截屏按钮,直播流,报错,黑屏,加载失败,苹果ios不兼容,重连,怎么改样式,很卡,详细教程,监控实时播放推流,倍速播放,在线flv在线推流地址,HLS播放,vue西瓜播放器,vue如何使用西瓜视频插件做直播推流回流,视频点播,flv在线视频流播放,截图功能,利用

🏆 前端领域优质创作者 1万+

【Java】【多线程】同步方法和同步代码块、死锁

转载:https://blog.youkuaiyun.com/u010002184/article/details/72566874?locationNum=11&amp;fps=1一、java的内置锁        每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。    获得内置锁的唯一途径就是进入这个锁的保护的...

love_Aym的博客 759

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

Java同步代码块、线程的生命周期、同步方法、Lock锁、死锁、线程中常用的方法

要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的。

LIJINGPO的博客 588

霍尼韦尔SoftMaster使用说明

霍尼韦尔SoftMaster使用说明

[疯狂Java]线程同步:同步监视器、同步方法、同步锁、死锁

1. 并发访问带来的线程安全问题:     1) 设想当多个线程刚好在同时时间访问一个公共资源时会怎么样?     2) 如果仅仅是读取那个资源那没什么问题,但如果要修改呢?同时修改必然会发生冲突导致数据不一致的错误(最典型的就是同时写一个文件);     3) 在实际问题中最典型的就是银行取钱问题,如果多人刚好同时用同一个账号取钱就会发生错误,而这种错误往往是非常严重的错误;     4

Lirx_Tech的专栏 5588

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

Java-多线程(三)-线程同步、多线程并发案例、线程同步的加锁方法、死锁

Java-多线程-3 学习视频:B站 狂神说Java – https://www.bilibili.com/video/BV1V4411p7EF 学习博客:csdn – https://blog.youkuaiyun.com/qijingwang/article/details/104892717 学习博客:csdn – https://blog.youkuaiyun.com/yalu_123456/article/details/91050036?spm=1001.2014.3001.5501 8、线程同步规则 8.1、线程简

AL ZN的博客 545

java 线程_180_线程_死锁_过多的同步方法可能造成死锁测试_练习

package 线程_180_线程_死锁_练习; /** 过多的同步方法可能造成死锁测试 */ public class SynDemo03 { public static void main(String[] args) { //在外部实例化 Object g = new Object(); Object m = new Object(); //创建线程 Test t1 = new Test(g,m); Test2 t2 = new Test2(g,m); Thread proxy = new Th

weixin_45339692的博客 157

“全栈2019”Java多线程第二十章:同步方法产生死锁的例子

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 “全栈2019”Java多线程第二十章:同步方法产生死锁的例子 下一章 “全栈2019”Java多线程第二十一章:同步代码块产生死锁的例子 学习小组 加入同步学习小组,共同交流与进步。 方式一:关注头条号Gorhaf,私信“Java学习小...

人人都是程序员官方博客 320

java之多线程,线程状态,中断线程,守护线程,线程同步,同步方法,死锁。

文章目录多线程基础进程Java内置多线程:创建新线程线程的状态状态join总结:中断线程第一种方法第二种方法线程间共享变量用关键字volatitle声明:小结守护线程线程同步小结同步方法 文章根据廖雪峰官方网站学习总结的,有需要细看点击链接:链接 多线程基础 进程 在计算机中,一个任务称为一个进程,浏览器是一个进程,播放器是一个进程,等等。 某些进程中还包括很多线程。例如world可以边打字边在后台打印。。 进程和线程的关系就是:一个进程可以包含一个或者多个线程,至少会有一个线程。 操作系统调度的最小任

Xiao爱好的博客 774

线程Thread类(创建线程、线程同步、死锁、线程通讯、线程的停止、守护线程、join 方法)——Java基础

1. Thread 类 进程: 正在执行的程序作为一个进程,进程负责内存空间的划分  单核的CPU在一个时间只能执行一个应用程序,各个应用程序在抢CPU资源 *  线程 Thread :  任何一个java程序,jvm在运行的时候都会创建一个main线程执行main方法中所有的代码 *   一个java 应用程序至少有 2 个线程   jvm 创建的 一个 主线程  是负责 main 方法代码的执...

生活不止眼前的枸杞, 还有蜂蜜和咖啡! 323

Java多线程----线程的同步,锁和死锁,问题以及解决方法(例子说明)

一、线程并发同步概念 线程同步其核心就在于一个“同”。所谓“同”就是协同、协助、配合,“同步”就是协同步调昨,也就是按照预定的先后顺序进行运行,即“你先,我等, 你做完,我再做”。 线程同步,就是当线程发出一个功能调用时,在没有得到结果之前,该调用就不会返回,其他线程也不能调用该方法。 就一般而言,我们在说同步、异步的时候,特指那些需要其他组件来配合或者需要一定时间来完成的任务。在多线程编程...

Fuzz 3609

参透java多线程——从开始到操作方法到同步与死锁到线程池

1、线程和进程 2、多线程 3、多线程操作方法 4、同步与死锁 5、代码练习 6、线程池

拾贰S 189

java_第六章_多线程——程序、进程、线程&线程的创建&Thread类的有关方法&线程的生命周期&线程的同步&线程安全的懒汉式&死锁&线程的通信

*程序:是为完成特定任务、用某种语言编写的一组指令的集合。是一段静态的代码*进程:是程序的一次执行过程,或正在运行的一个程序。是一个动态的过程*线程:进程可以进一步细化为线程,是一个程序内部的一条执行路径,若一个进程同一时间并行执行多个线程,就是支持多线程的,线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小;一个进程中的多个线程共享相同的内存单元/内存地址空间它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源

weixin_47687315的博客 147

java多线程 05 线程同步方法 CopyOnWriteArrayList 死锁 lock锁 synchronized

线程同步 并发:多个线程操作同一个资源 线程同步 – 等待机制 形成条件:队列|锁机制 synchronized 解决线程安全性 同步方法 synchronized 关键字 包括两种用法:synchronized 方法和 synchronized 块 synchronized 方法控制对“对象”的访问,每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会堵塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行 若

shake_shark_pp 381

java学习线程篇之多线程的同步与死锁

java学习线程篇之多线程的同步与死锁1. 线程的同步产生原因2. 线程的同步处理操作3. 线程的死锁情况4. 总结 1. 线程的同步产生原因 实际上所谓的同步就是多个线程访问同一资源时所需要考虑到的情况。 import java.io.PrintStream; class MyThead implements Runnable { //这就是一个多线程的操作类 private int...

TingtingYang 458
上一篇: Java基础(进程与线程,线程的创建)
下一篇: 在方法名称中发现无效的字符串, HTTP 方法名必须是有效的符号.
平平无奇的编码小天才
博客等级 码龄6年 15粉丝 39原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值