Java多线程基础二

 本篇文章转载自:Java多线程基础-使用多线程(二) - 简书

|-目录
|  同步锁
  -|同步锁使用范围
  -|对象锁与静态锁
  -|死锁
|  volatile实现’内存共享’

-synchronized同步锁

1.同步锁使用范围
  同步锁使用场景:多个线程对同一个对象中的实例变量进行并发访问。
  方法体中声明的局部变量不需要同步处理。

public class ThreadPrivateNumDemo {
    public static void main(String[] args) {
        final PrintPrivateNum privateNum = new PrintPrivateNum();
        Thread thread_1 = new Thread("thread_1") {
            public void run() {
                privateNum.printNum(Thread.currentThread().getName());
            };
        };
        Thread thread_2 = new Thread("thread_2") {
            @Override
            public void run() {
                privateNum.printNum(Thread.currentThread().getName());
            }
        };
        thread_1.start();
        thread_2.start();
    }
}

class PrintPrivateNum {
    public void printNum(String name) {
        int num = 0; // 局部变量不需要同步锁
        if ("thread_1".equals(name)) {
            num += 300;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        if ("thread_2".equals(name)) {
            num -= 100;
        }
        System.out.println(Thread.currentThread().getName() + ",Num:" + num);
    }
}

 图1-1 同步锁[安全的局部变量]


  从【图 1-1】可以验证安全的局部变量这句话,两个线程同时操作num变量,结果都是正常的。

class PrintPrivateNum {
    private int num = 0; // 全局变量需要同步

图1-2 同步锁[实例变量]

将num局部变量改为全局变量,可以看出结果与想要的不太一样,我们想要的结果或许是thread_2,Num:300 thread_1,Num:200thread_1,Num:-100 thread_2,Num:200,不会是【图1-2】中的结果,【图1-2】结果产生的原因是:thread_2修改Num=300后sleep,此时thread_1也同时进来修改了Num= 300 -100,故打印时Num已经变成了200。防止上述情况出现一般在方法声明加上synchronized关键字。

public synchronized void printNum(String name)

图1-3 同步锁[实例变量]

 使用了synchronized 关键字则结果始终是【图1-3】,原理说明:synchronized 关键字在方法声明中使用时,是起到锁的这样一个作用,作用:每次有且只有一个线程执行该方法的方法体。

2.对象锁与静态锁
  使用对象锁分为:synchronized(this)锁,synchronized(非this对象)锁。synchronized(this)锁与synchronized关键字在方法声明是一样的作用,优点都是解决多线程同步问题。synchronized(非this对象,比如成员变量锁),对比与synchronized(this)的优点:提高多个方法同步的效率问题

public class ThreadSynchronizedDemo {
    public static void main(String[] args) throws InterruptedException {
        final ThreadSynchronizedObject object = new ThreadSynchronizedObject();
        Thread thread_1 = new Thread("thread_1") {
            public void run() {
                try{
                    object.threadMethodA();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        };
        Thread  thread_2 = new Thread("thread_2") {
            public void run() {
                try{
                    object.threadMethodB();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        };
        thread_1.start();
        thread_2.start();
        Thread.sleep(3000);
        long start_time = (ThreadSynchronizedTimeUtils.mMethodAIntoTime - ThreadSynchronizedTimeUtils.mMethodBIntoTime) < 0 ? ThreadSynchronizedTimeUtils.mMethodAIntoTime
                : ThreadSynchronizedTimeUtils.mMethodBIntoTime;
        long end_time = (ThreadSynchronizedTimeUtils.mMethodAOutTime - ThreadSynchronizedTimeUtils.mMethodBOutTime) > 0 ? ThreadSynchronizedTimeUtils.mMethodAOutTime
                : ThreadSynchronizedTimeUtils.mMethodBOutTime;
        System.out.println("start_time:"+start_time);
        System.out.println("end_time:"+end_time);
        System.out.println("总耗时:" + (end_time - start_time));
    }
}

class ThreadSynchronizedObject {

    public synchronized void threadMethodA() throws InterruptedException {
        ThreadSynchronizedTimeUtils.setMethodAIntoTime();
        System.out.println(Thread.currentThread().getName() + ",进入threadMethodA");
        Thread.sleep(1000); ///<模拟方法请求耗时
        System.out.println(Thread.currentThread().getName() + ",退出threadMethodA");
        ThreadSynchronizedTimeUtils.setMethodAOutTime();
    }

    public void threadMethodB() throws InterruptedException {
        synchronized (this) {
            ThreadSynchronizedTimeUtils.setMethodBIntoTime();
            System.out.println(Thread.currentThread().getName() + ",进入threadMethodB");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + ",退出threadMethodB");
            ThreadSynchronizedTimeUtils.setMethodBOutTime();
        }
    }
}

class ThreadSynchronizedTimeUtils {

    public static long mMethodAIntoTime;
    public static long mMethodAOutTime;
    public static long mMethodBIntoTime;
    public static long mMethodBOutTime;

    public static void setMethodAIntoTime() {
        mMethodAIntoTime = System.currentTimeMillis();
    }

    public static void setMethodAOutTime() {
        mMethodAOutTime = System.currentTimeMillis();
    }

    public static void setMethodBIntoTime() {
        mMethodBIntoTime = System.currentTimeMillis();
    }

    public static void setMethodBOutTime() {
        mMethodBOutTime = System.currentTimeMillis();
    }
}

 图1-4 对象锁

从上面代码以及结果可以得出两个结论:
  1)synchronized关键字与synchronized(this)是同一把锁(this对象)因为两个线程方法进入与退出始终是成对出现。
  2)synchronized(this)锁使多线程同步执行方法体中的内容。

class ThreadSynchronizedObject {
    private Object object  = new Object();
    
    public synchronized void threadMethodA() throws InterruptedException {
        ThreadSynchronizedTimeUtils.setMethodAIntoTime();
        System.out.println(Thread.currentThread().getName() + ",进入threadMethodA");
        Thread.sleep(1000); ///<模拟方法请求耗时
        System.out.println(Thread.currentThread().getName() + ",退出threadMethodA");
        ThreadSynchronizedTimeUtils.setMethodAOutTime();
    }
    
    public void threadMethodB() throws InterruptedException {
        synchronized (object) {
            ThreadSynchronizedTimeUtils.setMethodBIntoTime();
            System.out.println(Thread.currentThread().getName() + ",进入threadMethodB");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + ",退出threadMethodB");
            ThreadSynchronizedTimeUtils.setMethodBOutTime();
        }
    }
}

图1-5 非对象锁.jpg 

ThreadSynchronizedObjectthreadMethodB改为synchronized (非this)锁,效率如【图1-5】提升了一倍,故synchronized(非this)锁适用于各个实例方法都需要同步操作时

静态锁:应用在static静态方法上,锁为当前*.java文件的Class类。

public class ThreadSynchronizedStaticDemo {
    public static void main(String[] args) {
        Thread thread_1 = new Thread("thread_1"){
            public void run() {
                synchronizedStaticService.methodA();
            };
        };
        Thread thread_2 = new Thread("thread_2") {
            public void run() {
                synchronizedStaticService.methodB();
            };
        };
        thread_1.start();
        thread_2.start();
    }
}

class synchronizedStaticService {
    public static synchronized void methodA() {
        try{
            System.out.println("" + Thread.currentThread().getName() + ",开始methodA()!");
            Thread.sleep(1000);
            System.out.println("" + Thread.currentThread().getName() +  ",退出methodA()!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    
    public static void methodB() {
        synchronized(synchronizedStaticService.class) {
            try{
                System.out.println("" + Thread.currentThread().getName() + ",开始methodB()!");
                Thread.sleep(1000);
                System.out.println("" + Thread.currentThread().getName() + ",退出methodB()!");
            } catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

图1-6 静态锁

 从【图1-6】看出methodA与methodB两个方法都是按照顺序执行完成,可见静态方法中synchronized关键字与synchronized(当前类.class)代码块作用一样

3.死锁
  出现死锁的情形:两个或多个线程处于永久等待状态,每个线程都等待其他线程释放所持有的资源(锁)。

public class ThreadDealDemo {
    public static void main(String[] args) {
        final DealService dealService = new DealService();
        Thread thread_1 = new Thread("thread_1") {
            public void run() {
                dealService.methodA();
            };
        };
        Thread thread_2 = new Thread("thread_2") {
            public void run() {
                dealService.methodB();
            };
        };
        thread_1.start();
        thread_2.start();
    }
}

class DealService {
    private Object lock1 = new Object();
    private Object lock2 = new Object();
    
    public void methodA() {
        System.out.println("" + Thread.currentThread().getName() + ",等待获取lock1");
        synchronized (lock1) {
            try {
                System.out.println("" + Thread.currentThread().getName() + ",持有lock1");
                Thread.sleep(2000);
                System.out.println("" + Thread.currentThread().getName() + ",等待获取lock2");
                synchronized (lock2) {
                    System.out.println("" + Thread.currentThread().getName() + ",持有lock2");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    public void methodB() {
        System.out.println("" + Thread.currentThread().getName() + ",等待获取lock2");
        synchronized (lock2) {
            try {
                System.out.println("" + Thread.currentThread().getName() + ",持有lock2");
                Thread.sleep(2000);
                System.out.println("" + Thread.currentThread().getName() + ",等待获取lock1");
                synchronized (lock1) {
                    System.out.println("" + Thread.currentThread().getName() + ",持有lock1");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

图1-7 死锁

从【图1-7】结果来看,进程一直处于运行状态,thread_1等待获取thread_2所持有的lock_2,thread_2等待获取thread_1所持有的lock_1,这样进程不借助外力的情况下,处于永久等待状态。

图1-8 死锁

图1-9 死锁

图1-10 死锁

可以使用Java JDK自带工具jps检测进程中死锁问题。
步骤:
1)jps查询根据结果找出进程号;eg:上述工程进程名为ThreadDealDemo进程号为46580.
2)jstrck -l 进程号获取当前进程堆栈信息,找到deallock信息。可以如【1-10】有相应的死锁堆栈信息,方便找到死锁对应点。

-volatile实现’内存共享‘

  volatile作用:使变量在多个线程可见;
  volatile实现‘共享内存’的解释:使用volatile让原本‘私有堆栈’中的操作,变成‘公共堆栈’中操作,这样内存在每个线程中都可见了。

图1-11 线程数据操作

public class ThreadVolatileDemo {
    public static void main(String[] args) throws InterruptedException {
        ThreadVolatileRunnable runnable = new ThreadVolatileRunnable();
        Thread thread = new Thread(runnable, "thread_1");
        thread.start();
        Thread.sleep(1000);
        runnable.setPrint(false); //这个操作和thread.start()不是同一个线程,所以有两个工作内存
    }
}

class ThreadVolatileRunnable implements Runnable {
    private boolean isPrint = true;
    
    public void setPrint(boolean flag) {
        this.isPrint = flag;
        if(!flag)
            System.out.println("" + Thread.currentThread().getName() + ",尝试让线程退出!");
    }
    
    public void run() {
        int num =0;
        while (isPrint) {
            num++;
        }
        System.out.println("" + Thread.currentThread().getName() + ",停止运行!num:" + num);
    }
}

图1-11 volatile

private volatile boolean isPrint = true;

图1-12 volatile

当sleep 1s后尝试停止线程,可从【图1-11】看出程序开关一直显示红色[运行状态],停止不了。这也就是第一章Java多线程基础-使用多线程 - 简书提到的,停止不了的死循环线程现象。
将isPrint变量增加volatile关键字后结果如【图1-12】程序正常退出。
  这里要提示一个技术点,如果将打印语句移到while循环里,同样的操作线程也能停止。关键点在println方法中有Synchronized结构体,synchronized作用于结构体时,作用:1)同步,2)让成员变量变为多线程可见;
故当我们又想让变量变为可见,又要同步则synchronized满足需求。参考下图

public void run() {
        int num =0;
        while (isPrint) {
            synchronized (this){ //能够同步工作内存
                num++;
            }
        }
        System.out.println("" + Thread.currentThread().getName() + ",停止运行!num:" + num);
    }

-总结

  这里主要介绍了synchronized对象锁,静态锁使用方式场景,volatile实现内存共享功能。

猜你喜欢

转载自blog.csdn.net/qq_35572013/article/details/127964022
今日推荐