Java | 对多线程的思考和总结(干货)

思考题

问:A线程正在执行一个对象的同步方法,B线程是否可以执行同一个对象中的非同步方法?

synchronized:

用于同步方法或者代码块,使得多个线程在试图并发执行同一个代码块的时候,串行地执行。以达到线程安全的目的。

允许重入:

在多线程的时候是这样的,但是对于单线程,是允许重入的,每重入一次,计数器加1,当退出代码块时,计数器减1。

/**
 * ClassName: Account
 * Date:      2020/2/25 1:16
 * author:    Oh_MyBug
 * version:   V1.0
 * 两个线程分别访问同个对象的同步方法和非同步方法
 */
public class Account {
    String name;
    double balance;

    public synchronized void set(String name, double balance) {
        System.out.println(Thread.currentThread().getName() + ":start synchronize...");
        this.name = name;
        try {
            Thread.sleep(1000 * 2L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance = balance;
        System.out.println(Thread.currentThread().getName() + ":finish synchronize...");
    }

    public void getBalance(String name) {
        System.out.println(Thread.currentThread().getName() + ":start general method...");
        try {
            Thread.sleep(1000 * 2L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":finish general method...");
        System.out.println(this.balance);
    }

    public static void main(String[] args) {
        Account account = new Account();
        new Thread(()->account.set("zhangsan", 100.0), "Thread-1").start();
        new Thread(()->account.getBalance("zhangsan"), "Thread-2").start();
    }
}


// 输出:
Thread-2:start general method...
Thread-1:start synchronize...
Thread-2:finish general method...
Thread-1:finish synchronize...
100.0

答案:A线程正在执行一个对象的同步方法,B线程可以执行同一个对象中的非同步方法。

/**
 * ClassName: Account
 * Date:      2020/2/25 1:16
 * author:    Oh_MyBug
 * version:   V1.0
 * 两个线程访问两个对象的同步方法
 */
public class Account1 {
    String name;
    double balance;

    public synchronized void set(String name, double balance) {
        System.out.println(Thread.currentThread().getName() + ":start synchronize...");
        this.name = name;
        try {
            Thread.sleep(1000 * 2L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance = balance;
        System.out.println(Thread.currentThread().getName() + ":finish synchronize...");
    }

    public synchronized void getBalance(String name) {
        System.out.println(Thread.currentThread().getName() + ":start synchronized method...");
        try {
            Thread.sleep(1000 * 2L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":finish synchronized method...");
        System.out.println(this.balance);
    }

    public static void main(String[] args) {
        Account1 account = new Account1();
        new Thread(()->account.set("zhangsan", 100.0), "Thread-1").start();
        new Thread(()->account.getBalance("zhangsan"), "Thread-2").start();
    }
}

// 输出:
Thread-1:start synchronize...
Thread-1:finish synchronize...
Thread-2:start synchronized method...
Thread-2:finish synchronized method...
100.0
/**
 * ClassName: Account2
 * Date:      2020/2/25 2:05
 * author:    Oh_MyBug
 * version:   V1.0
 * 两个线程分别访问非同步静态方法和同步非静态方法
 */
public class Account2 {
    String name;
    double balance;
    static String bank = "ABC";

    public synchronized void set(String name, double balance) {
        System.out.println(Thread.currentThread().getName() + ":start synchronize...");
        this.name = name;
        try {
            Thread.sleep(1000 * 2L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance = balance;
        System.out.println(Thread.currentThread().getName() + ":finish synchronize...");
    }

    public static void getBank() {
        System.out.println(Thread.currentThread().getName() + ":start static method...");
        try {
            Thread.sleep(1000 * 2L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":finish static method...");
        System.out.println(bank);
    }

    public static void main(String[] args) {
        Account2 account = new Account2();
        new Thread(()->account.set("zhangsan", 100.0), "Thread-1").start();
        new Thread(()->account.getBank(), "Thread-2").start();
    }
}

// 输出:
Thread-1:start synchronize...
Thread-2:start static method...
Thread-1:finish synchronize...
Thread-2:finish static method...
ABC

总结

  • 非静态方法之间,锁住的是本类的对象,所以,当一个线程在执行这个对象的非静态方法时,就会锁住整个对象,其他线程就不能进行处理这个对象中的其他非静态方法。 静态方法不属于本类对象,属于栈内存的方法区,全局只有一份。 对象处于堆内存,全局中可有多份实例。
  • 当方法是静态方法时,线程拿到的静态方法的锁和非静态方法的锁不是一个。 静态方法的锁是唯一的,非静态方法的锁属于对象的,一个对象可以有多个不同的实例对象。当一个线程进入一个对象的一个synchronized的静态方法后,其它线程可进入此对象的其它方法,不管此方法是静态的还是非静态的。

问:同上,B线程是否可以同时执行同一个对象中的另一个同步方法?

答案:A线程正在执行一个对象的同步方法,B线程不可以执行同一个对象中的另一个同步方法。

问:线程抛出异常会释放锁吗?

/**
 * ClassName: Account
 * Date:      2020/2/25 1:16
 * author:    Oh_MyBug
 * version:   V1.0
 * 两个线程访问两个对象的同步方法
 */
public class Account1 {
    String name;
    double balance;

    public synchronized void set(String name, double balance) throws Exception {
        System.out.println(Thread.currentThread().getName() + ":start synchronize...");
        this.name = name;
        try {
            Thread.sleep(1000 * 2L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance = balance;
        throw new Exception(Thread.currentThread().getName() + ":Exception...");
    }

    public synchronized void getBalance(String name) {
        System.out.println(Thread.currentThread().getName() + ":start synchronize...");
        try {
            Thread.sleep(1000 * 2L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ":finish synchronize...");
        System.out.println(this.balance);
    }

    public static void main(String[] args) throws Exception {
        Account1 account = new Account1();
        new Thread(()-> {
            try {
                account.set("zhangsan", 100.0);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "Thread-1").start();
        new Thread(()->account.getBalance("zhangsan"), "Thread-2").start();
    }
}

// 输出:
Thread-1:start synchronize...
Thread-2:start synchronize...
java.lang.Exception: Thread-1:Exception...
	at Account1.set(Account1.java:21)
	at Account1.lambda$main$0(Account1.java:39)
	at java.base/java.lang.Thread.run(Thread.java:830)
Thread-2:finish synchronize...
100.0

:在程序执行的过程中如果出现异常默认锁会被释放,在并发处理的过程中,有异常的情况需要多加小心,可能会出现数据不一致的情况,多个servlet线程访问同一个资源时,如果第一个线程抛出异常,其他线程就会进入同步代码块,有可能会访问到异常产生时的数据,出现数据不一致的情况。

问:volatile和synchronize区别?

首先需要理解线程安全的两个方面:执行控制内存可见

执行控制的目的是控制代码执行(顺序)及是否可以并发执行。

内存可见控制的是线程执行结果在内存中对其它线程的可见性。根据Java内存模型的实现,线程在具体执行时,会先拷贝主存数据到线程本地(CPU缓存),操作完成后再把结果从线程本地刷到主存。

  • volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
  • volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的
  • volatile仅能实现变量的修改可见性,不能保证原子性;而synchronized则可以保证变量的修改可见性和原子性
  • volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
  • volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化

安利文章
全面理解Java内存模型
volatile和synchronized的区别

问:写一个程序,证明Atomic原子类比synchronize更高效

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * ClassName: TestSyncMethods
 * Date:      2020/2/25 4:08
 * author:    Oh_MyBug
 * version:   V1.0
 */
public class AtomicTest {
    int count = 0;
    AtomicInteger count1 = new AtomicInteger(0);

    synchronized void m() {
        for (int i = 0; i < 10; i++) {
            count++;
        }
    }

    void n() {
        for (int i = 0; i < 10; i++) {
            count1.incrementAndGet();
        }
    }

    public static void main(String a[]) {
        long start = System.currentTimeMillis();
        AtomicTest t = new AtomicTest();

        List<Thread> threads = new ArrayList<>();

        for (int i = 0; i < 50; i++) {
            threads.add(new Thread(t::m, "thread " + i));
        }
        threads.forEach((o) -> o.start());
        threads.forEach((o) -> {
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("increase-->" + t.count);
        System.out.println(System.currentTimeMillis() - start);

        System.out.println("----------------------------------");

        long start1 = System.currentTimeMillis();
        AtomicTest t1 = new AtomicTest();

        List<Thread> thread = new ArrayList<>();

        for (int i = 0; i < 50; i++) {
            thread.add(new Thread(t1::n, "thread " + i));
        }
        thread.forEach((o) -> o.start());
        thread.forEach((o) -> {
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("atomic-->" + t1.count1);
        System.out.println(System.currentTimeMillis() - start1);
    }
}

// 输出:
increase-->500
76
----------------------------------
atomic-->500
24
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * ClassName: TestSyncMethods
 * Date:      2020/2/25 4:08
 * author:    Oh_MyBug
 * version:   V1.0
 */
public class AtomicTest {
    int count = 0;
    AtomicInteger count1 = new AtomicInteger(0);

    synchronized void m() {
        for (int i = 0; i < 10; i++) {
            count++;
        }
    }

    void n() {
        for (int i = 0; i < 10; i++) {
            count1.incrementAndGet();
        }
    }

    public static void main(String a[]) {
        long start = System.currentTimeMillis();
        AtomicTest t = new AtomicTest();

        List<Thread> threads = new ArrayList<>();

        for (int i = 0; i < 1000; i++) {
            threads.add(new Thread(t::m, "thread " + i));
        }
        threads.forEach((o) -> o.start());
        threads.forEach((o) -> {
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("increase-->" + t.count);
        System.out.println(System.currentTimeMillis() - start);

        System.out.println("----------------------------------");


        long start1 = System.currentTimeMillis();
        AtomicTest t1 = new AtomicTest();

        List<Thread> thread = new ArrayList<>();

        for (int i = 0; i < 1000; i++) {
            thread.add(new Thread(t1::n, "thread " + i));
        }
        thread.forEach((o) -> o.start());
        thread.forEach((o) -> {
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("atomic-->" + t1.count1);
        System.out.println(System.currentTimeMillis() - start1);
    }
}

// 输出:
increase-->10000
609
----------------------------------
atomic-->10000
640

总结

  • 在中低程度的竞争下,原子类提供更高的伸缩性;在高强度的竞争下,锁能更好的帮助我们避免竞争。
  • 若资源竞争规模不大,控制粒度较小,使用原子类比使用锁更好,能提高效率与性能

问:Atomic原子类可以保证可见性吗?请写一个程序来证明

import java.util.concurrent.atomic.AtomicInteger;

/**
 * ClassName: AtomicTest1
 * Date:      2020/2/25 4:47
 * author:    Oh_MyBug
 * version:   V1.0
 */
public class AtomicTest1 {
    AtomicInteger atomicInteger = new AtomicInteger(0);
    int interger  = 0;
    public void m(){
        System.out.println(Thread.currentThread().getName());
        for (int i = 0; i < 10000; i ++) {
            atomicInteger.incrementAndGet();
            interger++;
        }
        System.out.println(Thread.currentThread().getName());
    }

    public void n(){
        interger++;
    }

    public void getnum() {
        System.out.println("Atomic:" + atomicInteger.get());
        System.out.println("increase:" + interger);
    }

    public static void main(String[] args) {
        AtomicTest1 atomicTest1 = new AtomicTest1();
        Thread thread1 = new Thread(()->atomicTest1.m(), "Thread-1");
        Thread thread2 = new Thread(()->atomicTest1.m(), "Thread-2");
        thread1.start();
        thread2.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        atomicTest1.getnum();

    }
}

// 输出:
Thread-2
Thread-1
Thread-1
Thread-2
Atomic:20000
increase:18732

:Atomic原子类可以保证可见性

问:写一个程序证明Atomic原子类的多个方法并不构成原子性

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * ClassName: AtomicTest2
 * Date:      2020/2/25 5:05
 * author:    Oh_MyBug
 * version:   V1.0
 */
public class AtomicTest2 {
    AtomicInteger atomicInteger = new AtomicInteger(0);

    public void m(){
        for (int i = 0; i < 10000; i ++) {
            if (atomicInteger.get()<1000)
                atomicInteger.incrementAndGet();
        }
    }

    public int getnum(){
        return atomicInteger.get();
    }

    public static void main(String[] args) {
        AtomicTest2 atomicTest2 = new AtomicTest2();
        List<Thread> threads = new ArrayList<>();

        for (int i = 0; i < 100; i++) {
            threads.add(new Thread(()->atomicTest2.m(), "thread " + i));
        }
        threads.forEach((o) -> o.start());
        threads.forEach((o) -> {
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(atomicTest2.getnum());
    }

}

// 输出:
1001

问:写一个程序模拟死锁

产生条件:

  • 互斥条件

    指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。

  • 请求和保持条件

    指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。

  • 不剥夺条件

    指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。

  • 环路等待条件

    指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

避免死锁:理论上是只要打破四个必要条件之一就能有效预防死锁的发生

  • 加锁顺序(线程按照一定的顺序加锁)
  • 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
  • 破坏“循环等待”条件:将系统中的所有资源统一编号,进程可在任何时刻提出资源申请,但所有申请必须按照资源的编号顺序(升序)提出。这样做就能保证系统不出现死锁。
/**
 * ClassName: DeadLock
 * Date:      2020/2/25 5:42
 * author:    Oh_MyBug
 * version:   V1.0
 */
public class DeadLock {
    public static String resource1 = "resource1";
    public static String resource2 = "resource2";
    public static void main(String[] args) {
        Thread thread1 = new Thread(new BusinessA());
        Thread thread2 = new Thread(new BusinessB());
        thread1.start();
        thread2.start();
    }
    static class BusinessA implements Runnable {
        @Override
        public void run() {
            try{
                System.out.println("BusinessA启动");
                while(true){
                    synchronized(DeadLock.resource1){
                        System.out.println("BusinessA拿到了resource1的锁");
                        Thread.sleep(3000);//获取resource1后先等一会儿,让BusinessB有足够的时间锁住resource2
                        System.out.println("BusinessA想拿resource2的锁。。。。");
                        synchronized(DeadLock.resource2){
                            System.out.println("BusinessA获得到了resource2的锁");
                        }
                    }
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    static class BusinessB implements Runnable {
        @Override
        public void run(){
            try{
                System.out.println("BusinessB启动");
                while(true){
                    synchronized(DeadLock.resource2){
                        System.out.println("BusinessB拿得到了resource2的锁");
                        Thread.sleep(3000);//获取resource2后先等一会儿,让BusinessA有足够的时间锁住resource1
                        System.out.println("BusinessB想拿resource1的锁。。。。");
                        synchronized(DeadLock.resource1){
                            System.out.println("BusinessB获得到了resource1的锁");
                        }
                    }
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }

}
// 输出:
BusinessA启动
BusinessA拿到了resource1的锁
BusinessB启动
BusinessB拿得到了resource2的锁
BusinessA想拿resource2的锁。。。。
BusinessB想拿resource1的锁。。。。
发布了36 篇原创文章 · 获赞 3 · 访问量 6224

猜你喜欢

转载自blog.csdn.net/Oh_MyBug/article/details/104489852