java--多线程03

发布时间:2024年01月24日

?synchronized关键字

? synchronized有两种使用方式

? - 在方法上修饰,此时该方法变为一个同步方法
? - 同步块,可以更准确的锁定需要排队的代码片段

?同步方法

? ? ? ? 当一个方法使用synchronized修饰后,这个方法称为"同步方法",即:多个线程不能同时 在方法内部执行.只能有先后顺序的一个一个进行. 将并发操作同一临界资源的过程改为同步执行就可以有效的解决并发安全问题.


package thread;

/**
?* 多线程并发安全问题
?* 当多个线程并发操作同一临界资源,由于线程切换的时机不确定,导致操作顺序出现
?* 混乱,严重时可能导致系统瘫痪。
?* 临界资源:同时只能被单一线程访问操作过程的资源。
?*/
public class SyncDemo {
? ? public static void main(String[] args) {
? ? ? ? Table table = new Table();
? ? ? ? Thread t1 = new Thread(){
? ? ? ? ? ? public void run(){
? ? ? ? ? ? ? ? while(true){
? ? ? ? ? ? ? ? ? ? int bean = table.getBean();
? ? ? ? ? ? ? ? ? ? Thread.yield();
? ? ? ? ? ? ? ? ? ? System.out.println(getName()+":"+bean);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? Thread t2 = new Thread(){
? ? ? ? ? ? public void run(){
? ? ? ? ? ? ? ? while(true){
? ? ? ? ? ? ? ? ? ? int bean = table.getBean();
? ? ? ? ? ? ? ? ? ? /*
? ? ? ? ? ? ? ? ? ? ? ? static void yield()
? ? ? ? ? ? ? ? ? ? ? ? 线程提供的这个静态方法作用是让执行该方法的线程
? ? ? ? ? ? ? ? ? ? ? ? 主动放弃本次时间片。
? ? ? ? ? ? ? ? ? ? ? ? 这里使用它的目的是模拟执行到这里CPU没有时间了,发生
? ? ? ? ? ? ? ? ? ? ? ? 线程切换,来看并发安全问题的产生。
? ? ? ? ? ? ? ? ? ? ?*/
? ? ? ? ? ? ? ? ? ? Thread.yield();
? ? ? ? ? ? ? ? ? ? System.out.println(getName()+":"+bean);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? t1.start();
? ? ? ? t2.start();
? ? }
}

class Table{
? ? private int beans = 20;//桌子上有20个豆子

? ? /**
? ? ?* 当一个方法使用synchronized修饰后,这个方法称为同步方法,多个线程不能
? ? ?* 同时执行该方法。
? ? ?* 将多个线程并发操作临界资源的过程改为同步操作就可以有效的解决多线程并发
? ? ?* 安全问题。
? ? ?* 相当于让多个线程从原来的抢着操作改为排队操作。
? ? ?*/
? ? public synchronized int getBean(){
? ? ? ? if(beans==0){
? ? ? ? ? ? throw new RuntimeException("没有豆子了!");
? ? ? ? }
? ? ? ? Thread.yield();
? ? ? ? return beans--;
? ? }
}

同步块

? ? 有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率.同步块可以更准确的控制需要多个线程排队执行的代码片段.

语法:


synchronized(同步监视器对象){
? ?需要多线程同步执行的代码片段
}

? ? ?同步监视器对象即上锁的对象,要想保证同步块中的代码被多个线程同步运行,则要求多个线程看到的同步监视器对象是同一个.

##### 在静态方法上使用synchronized

当在静态方法上使用synchronized后,该方法是一个同步方法.由于静态方法所属类,所以一定具有同步效果.

静态方法使用的同步监视器对象为当前类的类对象(Class的实例).

注:类对象会在后期反射知识点介绍.

```java
package thread;

/**
?* 静态方法上如果使用synchronized,则该方法一定具有同步效果。
?*/
public class SyncDemo3 {
? ? public static void main(String[] args) {
? ? ? ? Thread t1 = new Thread(){
? ? ? ? ? ? public void run(){
? ? ? ? ? ? ? ? Boo.dosome();
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? Thread t2 = new Thread(){
? ? ? ? ? ? public void run(){
? ? ? ? ? ? ? ? Boo.dosome();
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? t1.start();
? ? ? ? t2.start();
? ? }
}
class Boo{
? ? /**
? ? ?* synchronized在静态方法上使用是,指定的同步监视器对象为当前类的类对象。
? ? ?* 即:Class实例。
? ? ?* 在JVM中,每个被加载的类都有且只有一个Class的实例与之对应,后面讲反射
? ? ?* 知识点的时候会介绍类对象。
? ? ?*/
? ? public synchronized static void dosome(){
? ? ? ? ? ? Thread t = Thread.currentThread();
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? System.out.println(t.getName() + ":正在执行dosome方法...");
? ? ? ? ? ? ? ? Thread.sleep(5000);
? ? ? ? ? ? ? ? System.out.println(t.getName() + ":执行dosome方法完毕!");
? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? e.printStackTra


package thread;

/**
?* 有效的缩小同步范围可以在保证并发安全的前提下尽可能提高并发效率。
?*
?* 同步块
?* 语法:
?* synchronized(同步监视器对象){
?* ? ? 需要多个线程同步执行的代码片段
?* }
?* 同步块可以更准确的锁定需要多个线程同步执行的代码片段来有效缩小排队范围。
?*/
public class SyncDemo2 {
? ? public static void main(String[] args) {
? ? ? ? Shop shop = new Shop();
? ? ? ? Thread t1 = new Thread(){
? ? ? ? ? ? public void run(){
? ? ? ? ? ? ? ? shop.buy();
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? Thread t2 = new Thread(){
? ? ? ? ? ? public void run(){
? ? ? ? ? ? ? ? shop.buy();
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? t1.start();
? ? ? ? t2.start();
? ? }
}

class Shop{
? ? public void buy(){
? ? ? ? /*
? ? ? ? ? ? 在方法上使用synchronized,那么同步监视器对象就是this。
? ? ? ? ?*/
// ? ?public synchronized void buy(){
? ? ? ? Thread t = Thread.currentThread();//获取运行该方法的线程
? ? ? ? try {
? ? ? ? ? ? System.out.println(t.getName()+":正在挑衣服...");
? ? ? ? ? ? Thread.sleep(5000);
? ? ? ? ? ? /*
? ? ? ? ? ? ? ? 使用同步块需要指定同步监视器对象,即:上锁的对象
? ? ? ? ? ? ? ? 这个对象可以是java中任何引用类型的实例,只要保证多个需要排队
? ? ? ? ? ? ? ? 执行该同步块中代码的线程看到的该对象是"同一个"即可
? ? ? ? ? ? ?*/
? ? ? ? ? ? synchronized (this) {
// ? ? ? ? ? ?synchronized (new Object()) {//没有效果!
? ? ? ? ? ? ? ? System.out.println(t.getName() + ":正在试衣服...");
? ? ? ? ? ? ? ? Thread.sleep(5000);
? ? ? ? ? ? }

? ? ? ? ? ? System.out.println(t.getName()+":结账离开");
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }

? ? }
}
ce();
? ? ? ? ? ? }
? ? ? ? }
? ? }
}

? ? ?静态方法中使用同步块时,指定的锁对象通常也是当前类的类对象


class Boo{
? ? public static void dosome(){
? ? ? ? /*
? ? ? ? ? ? 静态方法中使用同步块时,指定同步监视器对象通常还是用当前类的类对象
? ? ? ? ? ? 获取方式为:类名.class
? ? ? ? ?*/
? ? ? ? synchronized (Boo.class) {
? ? ? ? ? ? Thread t = Thread.currentThread();
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? System.out.println(t.getName() + ":正在执行dosome方法...");
? ? ? ? ? ? ? ? Thread.sleep(5000);
? ? ? ? ? ? ? ? System.out.println(t.getName() + ":执行dosome方法完毕!");
? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? }
? ? }
}

互斥锁

? ? 当多个线程执行不同的代码片段,但是这些代码片段之间不能同时运行时就要设置为互斥的.

使用synchronized锁定多个代码片段,并且指定的同步监视器是同一个时,这些代码片段之间就是互斥的.


package thread;

/**
?* 互斥锁
?* 当使用synchronized锁定多个不同的代码片段,并且指定的同步监视器对象相同时,
?* 这些代码片段之间就是互斥的,即:多个线程不能同时访问这些方法。
?*/
public class SyncDemo4 {
? ? public static void main(String[] args) {
? ? ? ? Foo foo = new Foo();
? ? ? ? Thread t1 = new Thread(){
? ? ? ? ? ? public void run(){
? ? ? ? ? ? ? ? foo.methodA();
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? Thread t2 = new Thread(){
? ? ? ? ? ? public void run(){
? ? ? ? ? ? ? ? foo.methodB();
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? t1.start();
? ? ? ? t2.start();
? ? }
}
class Foo{
? ? public synchronized void methodA(){
? ? ? ? Thread t = Thread.currentThread();
? ? ? ? try {
? ? ? ? ? ? System.out.println(t.getName()+":正在执行A方法...");
? ? ? ? ? ? Thread.sleep(5000);
? ? ? ? ? ? System.out.println(t.getName()+":执行A方法完毕!");
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
? ? public synchronized void methodB(){
? ? ? ? Thread t = Thread.currentThread();
? ? ? ? try {
? ? ? ? ? ? System.out.println(t.getName()+":正在执行B方法...");
? ? ? ? ? ? Thread.sleep(5000);
? ? ? ? ? ? System.out.println(t.getName()+":执行B方法完毕!");
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}

死锁

? ?死锁的产生:

? ? 两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。这个现象就是死锁。


package thread;

/**
?* 死锁
?* 死锁的产生:
?* 两个线程各自持有一个锁对象的同时等待对方先释放锁对象,此时会出现僵持状态。
?* 这个现象就是死锁。
?*/
public class DeadLockDemo {
? ? //定义两个锁对象,"筷子"和"勺"
? ? public static Object chopsticks = new Object();
? ? public static Object spoon = new Object();

? ? public static void main(String[] args) {
? ? ? ? Thread np = new Thread(){
? ? ? ? ? ? public void run(){
? ? ? ? ? ? ? ? System.out.println("北方人开始吃饭.");
? ? ? ? ? ? ? ? System.out.println("北方人去拿筷子...");
? ? ? ? ? ? ? ? synchronized (chopsticks){
? ? ? ? ? ? ? ? ? ? System.out.println("北方人拿起了筷子开始吃饭...");
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? Thread.sleep(5000);
? ? ? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? System.out.println("北方人吃完了饭,去拿勺...");
? ? ? ? ? ? ? ? ? ? synchronized (spoon){
? ? ? ? ? ? ? ? ? ? ? ? System.out.println("北方人拿起了勺子开始喝汤...");
? ? ? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? ? ? Thread.sleep(5000);
? ? ? ? ? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? System.out.println("北方人喝完了汤");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? System.out.println("北方人放下了勺");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? System.out.println("北方人放下了筷子,吃饭完毕!");
? ? ? ? ? ? }
? ? ? ? };


? ? ? ? Thread sp = new Thread(){
? ? ? ? ? ? public void run(){
? ? ? ? ? ? ? ? System.out.println("南方人开始吃饭.");
? ? ? ? ? ? ? ? System.out.println("南方人去拿勺...");
? ? ? ? ? ? ? ? synchronized (spoon){
? ? ? ? ? ? ? ? ? ? System.out.println("南方人拿起了勺开始喝汤...");
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? Thread.sleep(5000);
? ? ? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? System.out.println("南方人喝完了汤,去拿筷子...");
? ? ? ? ? ? ? ? ? ? synchronized (chopsticks){
? ? ? ? ? ? ? ? ? ? ? ? System.out.println("南方人拿起了筷子开始吃饭...");
? ? ? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? ? ? Thread.sleep(5000);
? ? ? ? ? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? ? ? System.out.println("南方人吃完了饭");
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? System.out.println("南方人放下了筷子");
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? System.out.println("南方人放下了勺,吃饭完毕!");
? ? ? ? ? ? }
? ? ? ? };

? ? ? ? np.start();
? ? ? ? sp.start();

? ? }
}

总结:? ??

? ? ? 多线程编程时,为了保证数据的一致性和避免线程间的竞态条件(race condition)问题,可以使用synchronized关键字来实现同步。

一.synchronized关键字: synchronized关键字用于修饰方法或代码块,保证在同一时刻只有一个线程能够访问被synchronized修饰的代码。

  1. 修饰方法: 用synchronized修饰方法时,该方法成为同步方法,表示一次只有一个线程能够执行该方法。

    public synchronized void doSomething() {
       // 代码
    }
    

  2. 修饰代码块: 用synchronized修饰代码块时,只对该代码块进行同步,而不是整个方法。

    public void doSomething() {
       synchronized (this) {
          // 代码
       }
    }
    

二、同步方法和同步块的区别:

  1. 同步方法是对整个方法进行同步,只要一个线程进入该方法,其他线程就无法访问该方法。
  2. 同步块只对被synchronized修饰的代码块进行同步,其他非同步代码块仍然可以被其他线程访问。

三、互斥锁(Mutex Lock): 互斥锁是一种同步机制,它用于确保同一时刻只有一个线程能够访问共享资源,从而避免多线程间的数据竞争问题。

在Java中,synchronized关键字实现的同步块和同步方法就是一种互斥锁的实现,确保在同一时刻只有一个线程能够访问被同步的代码。

四、死锁(Deadlock): 死锁是指两个或多个线程互相持有对方需要的资源,导致它们都无法继续执行的情况。

死锁发生的四个条件:

  1. 互斥条件:资源只能被一个线程占用。
  2. 请求与保持条件:一个线程获得了资源后,还继续请求其他资源。
  3. 不剥夺条件:资源只能被持有线程主动释放,不能被其他线程强制剥夺。
  4. 循环等待条件:多个线程之间形成了等待资源的循环链。

避免死锁的方法:

  1. 避免使用过多的同步块,尽量减少同步的范围。
  2. 按照固定的顺序来申请资源,避免循环等待的情况。
  3. 使用定时锁(tryLock)避免长时间等待资源。
  4. 使用资源分配图(Resource Allocation Graph)进行死锁检测和恢复。

?

文章来源:https://blog.csdn.net/Flying_Fish_roe/article/details/135757817
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。