Java 并发变成同步机制

并发编程的演进:

批处理——多进程——多线程


在多线程变成中,由于多个线程共享进程的变量,有可能出现同时访问一个资源的情况,因此需要使用同步机制。


java的内存模型:

Java内存模型规定所有的变量都存在主存当中,每个线程都有自己的工作内存,线程对变量的所有操作都必须在工作内存中进行,而不能直接在主存进行操作。并且每个线程不能访问其他线程的工作内存。


关键字:

原子操作:原子为不可再分操作。只有对象的读取和赋值是原子操作。int i=10 是原子操作。int x = y;不是原子操作

violation:可见关键字

Synchronized:内部隐示锁

lock: ReentrantLock(显示锁) + ReentrantReadWriteLock(读写锁)


violation关键字:

一旦一个共享变量被volatile修饰之后,那么就具备了两层含义。

1)可见性:  保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

    它会强制将对缓存的修改操作立即写入主存,同时会导致其他CPU对应的缓存无效。

通过synchronized和Lock也能够保证可见性,synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中

2) 有序性: 禁止进行指令重排序。

需要注意的是violation不保证原子性。

比如:

public volatile int inc = 0;

inc++;

在多线程中不同依靠violation关键字保证原子操作。

使用volatile关键字的场景:

1) 多变量的写操作不依赖于当前值

2) 该变量没有包含在具有其他变量的不变式中。

使用例子:状态标记量:

volatile boolean inited = false;
//线程1:
context = loadContext();  
inited = true;            
 
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);


synchronized:

把一些不是原子操作组合成原子操作。

(1) synchronized方法

示例代码:

public class InsertData {

    public  synchronized void insert(){
        for(int i=0; i<10;++i){
            System.out.println(Thread.currentThread().getName() + ": 执行,变量i = "+i);
        }
    }

    public synchronized void insert2() {
        System.out.println(Thread.currentThread().getName()+ "start inset2");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+ "end inset2");

    }

    public static  synchronized void insert3(){
        System.out.println(Thread.currentThread().getName() + "insert3");
    }

    public void insert4(){
        System.out.println(Thread.currentThread().getName()+"insert4");
    }
}
测试代码:

public class SynTest {

    @Test
    public void test() throws InterruptedException {
        InsertData insertData = new InsertData();
        new Thread(new Runnable() {
            @Override
            public void run() {
                insertData.insert();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                insertData.insert();
            }
        }).start();


        Thread.sleep(8000);
    }
}
需要注意的是:

1)当一个线程正在访问一个对象的synchronized方法,那么其他 线程不能访问该对象的synchronized方法,这个原因很简单,是因为对当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。

2) 当一个线程正在访问对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。

3) 如果该方法是非static的,则该锁是对象锁。如果方法是static的,则该锁是类锁。对象锁和类锁不互斥。

4) 对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。


(2) synchronized 代码块

synchronized(synObject){

}

当在某个线程中执行这段代码块,该线程会获取对象synObject的锁,从而使其他线程无法同时访问该代码块。

synObject可是是this,代码获取当前对象的锁,也可以是类中的一个属性,也可以是方法的一个object类型的入参,代表获取该对象的锁。


synchronized代码块使用起来比synchronized方法要灵活得多。如果一个方法中只有一部分代码需要同步,如果此时对整个方法用synchronized进行同步,会影响执行效率。而使用synchronized代码块就可以避免这个问题。


synchronized的缺陷:

当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只有两种情况:

1) 获取锁的线程执行完了代码块,然后线程释放对应锁的占有。

2) 线程执行发生异常,此时JVM会让线程自动释放锁。

那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程变只能干巴巴地等待,严重影响程序执行效率。所以就需要有一种机制可以不让等待的线程一直无限地等待下去(比如只等待一定时间或者能够相应中断),通过lock就可以办到。


Lock

1) ReentrantLock:

使用事例:

采用lock,必须主动释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被释放,防止死锁的发生。

  Lock lock =new ReentrantLock();
        lock.lock();
        try{
            //执行业务逻辑代码
        }catch (Exception e){
            
        }finally {
            lock.unlock();
        }

2) ReentrantReadWriteLock: 读写锁

readLock用来获取读锁,writeLock用来获取写锁。

读锁可以被多个线程占有。

一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。

一个线程已经占用了写锁,则其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。

Lock和synchronized的选择:

总结来说,Lock和synchronized有以下几点不同:

  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

          lock是可中断的锁,而synchronized是不可中断锁。lock只能中断等待锁的线程,不能中断正在执行的线程。

  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

  5)Lock可以提高多个线程进行读操作的效率。

  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

相同:

1) synchronized和lock都是可重入的锁。



参考资料:http://www.cnblogs.com/dolphin0520/p/3920373.html

http://www.cnblogs.com/dolphin0520/p/3923737.html

http://www.cnblogs.com/dolphin0520/p/3923167.html



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java并发编程是指在多个线程同时运行时,对共享资源的访问和修改进行协调和管理,以确保程序的正确性和性能。 下面是几个常见的Java并发场景和相应的实现代码: 1. 生产者-消费者模型 生产者-消费者模型是一种常见的并发场景,它涉及到一个或多个生产者线程向一个共享的缓冲区中生产数据,同时一个或多个消费者线程从缓冲区中消费数据。为了协调生产者和消费者线程之间的访问和修改,需要使用锁、条件变量等并发控制机制。 以下是一个简单的生产者-消费者模型的Java实现代码: ```java import java.util.LinkedList; import java.util.Queue; import java.util.Random; public class ProducerConsumerExample { public static void main(String[] args) { Queue<Integer> buffer = new LinkedList<>(); // 缓冲区 int maxSize = 10; // 缓冲区最大容量 Thread producerThread = new Thread(new Producer(buffer, maxSize), "Producer"); Thread consumerThread = new Thread(new Consumer(buffer), "Consumer"); producerThread.start(); consumerThread.start(); } static class Producer implements Runnable { private Queue<Integer> buffer; private int maxSize; public Producer(Queue<Integer> buffer, int maxSize) { this.buffer = buffer; this.maxSize = maxSize; } @Override public void run() { while (true) { synchronized (buffer) { while (buffer.size() == maxSize) { try { buffer.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } Random random = new Random(); int number = random.nextInt(100); buffer.add(number); System.out.println("Produced " + number); buffer.notifyAll(); } } } } static class Consumer implements Runnable { private Queue<Integer> buffer; public Consumer(Queue<Integer> buffer) { this.buffer = buffer; } @Override public void run() { while (true) { synchronized (buffer) { while (buffer.isEmpty()) { try { buffer.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } int number = buffer.poll(); System.out.println("Consumed " + number); buffer.notifyAll(); } } } } } ``` 2. 线程池 线程池是一种管理线程的机制,它通过在应用程序启动时创建一定数量的线程并将它们放入池中,然后在应用程序运行期间重复使用这些线程,以避免因频繁创建和销毁线程而导致的性能问题。Java中提供了Executor和ThreadPoolExecutor两个类来实现线程池。 以下是一个简单的线程池的Java实现代码: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { Runnable worker = new WorkerThread("Task " + i); executor.execute(worker); } executor.shutdown(); while (!executor.isTerminated()) {} System.out.println("All tasks completed"); } static class WorkerThread implements Runnable { private String taskName; public WorkerThread(String taskName) { this.taskName = taskName; } @Override public void run() { System.out.println(Thread.currentThread().getName() + " " + taskName + " is running"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " " + taskName + " is completed"); } } } ``` 3. CountDownLatch CountDownLatch是一种同步工具,它允许一个或多个线程等待一组事件的完成。当CountDownLatch的计数器变为0时,等待线程可以继续执行。Java中提供了CountDownLatch类来实现这种同步机制。 以下是一个简单的CountDownLatch的Java实现代码: ```java import java.util.concurrent.CountDownLatch; public class CountDownLatchExample { public static void main(String[] args) throws InterruptedException { int n = 5; CountDownLatch startSignal = new CountDownLatch(1); CountDownLatch doneSignal = new CountDownLatch(n); for (int i = 0; i < n; i++) { new Thread(new Worker(startSignal, doneSignal)).start(); } System.out.println("Workers are waiting for the start signal"); Thread.sleep(1000); startSignal.countDown(); // 发送开始信号 System.out.println("Workers are started"); doneSignal.await(); // 等待所有任务完成 System.out.println("All tasks are completed"); } static class Worker implements Runnable { private final CountDownLatch startSignal; private final CountDownLatch doneSignal; public Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { this.startSignal = startSignal; this.doneSignal = doneSignal; } @Override public void run() { try { startSignal.await(); // 等待开始信号 System.out.println(Thread.currentThread().getName() + " is working"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { doneSignal.countDown(); // 发送完成信号 } } } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值