首页 > 其他 > 详细

synchronized的使用

时间:2021-02-21 23:56:10      阅读:42      评论:0      收藏:0      [点我收藏+]

解法一:wait/notify和synchronized的组合

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Semaphore;
 
/**
 * 实现一个容器,提供add,size方法
 * 两个线程,线程一添加十个元素到容器中,线程二监控元素的个数,当个数到5时,线程二给出提示并结束
 *
 * @author zab
 * @date 2019-10-20 21:54
 */
public class ThreadCommunicationTest1 {
 
    private volatile MyContainer1 myContainer1 = new MyContainer1();
 
    public void f1() {
        synchronized (this) {
 
            for (int i = 1; i <= 10; i++) {
                myContainer1.add("test");
                System.out.println("add" + i);
                if(i == 5){
                    this.notify();//唤醒其他等待的锁
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
 
        }
    }
 
    public void f2() {
        synchronized (this) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("size等于5啦!!!");
            this.notify();
        }
    }
 
 
    public static void main(String[] args) {
        ThreadCommunicationTest1 t = new ThreadCommunicationTest1();
        new Thread(t::f2).start();
        new Thread(t::f1).start();
    }
}
 
class MyContainer1 {
    List<String> list = new LinkedList<>();
 
    public void add(String s) {
        list.add(s);
    }
 
    public int size() {
        return list.size();
    }
 
}

 

多次运行结果如下:

 技术分享图片

 wait/notify的解法比较常规,大概逻辑是,f2方法启动的线程先运行,由于f2方法一来就加锁等待,释放锁,f1方法的线程获得锁,循环输出1、2、3、4、5,当输出5时,叫醒f2,同时自己wait,释放锁,f2得以执行,输出"size等于5啦!!!",输出完毕过后叫醒正在wait的f1,f1得以执行下面的输出。

方法二:lock、condition组合

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
/**
 * 实现一个容器,提供add,size方法
 * 两个线程,线程一添加十个元素到容器中,线程二监控元素的个数,当个数到5时,线程二给出提示并结束
 *
 * @author zab
 * @date 2019-10-20 21:54
 */
public class ThreadCommunicationTest2 {
 
    private volatile MyContainer2 myContainer2 = new MyContainer2();
    private Lock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
 
 
    public void f1() {
 
        lock.lock();
        for (int i = 1; i <= 10; i++) {
            System.out.println("add" + i);
            if (i == 5) {
                c2.signal();
                try {
                    c1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        lock.unlock();
 
    }
 
    public void f2() {
        lock.lock();
        try {
            c2.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("size等于5啦!!!");
        c1.signal();
        lock.unlock();
    }
 
 
    public static void main(String[] args) {
        ThreadCommunicationTest2 t = new ThreadCommunicationTest2();
        new Thread(t::f2).start();
        new Thread(t::f1).start();
    }
}
 
class MyContainer2 {
    List<String> list = new LinkedList<>();
 
    public void add(String s) {
        list.add(s);
    }
 
    public int size() {
        return list.size();
    }
 
}

 

 

synchronize和Lock锁的区别

为什么java已经通过synchronized关键字实现同步访问了,还需要提供Lock?

synchronized的缺陷
前面博客有提到过释放对象的锁有两种情况:

程序执行完同步代码块会释放代码块。
程序在执行同步代码块是出现异常,JVM会自动释放锁去处理异常。
如果获取锁的线程需要等待I/O或者调用了sleep()方法被阻塞了,但仍持有锁,其他线程只能干巴巴的等着,这样就会很影响程序效率。 
因此就需要一种机制,可以不让等待的线程已知等待下去,比如值等待一段时间或响应中断,Lock锁就可以办到。

再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。

但是采用synchronized关键字来实现同步的话,就会导致一个问题:如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。 

因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。 
另外,Lock可以知道线程有没有得到锁,而synchronized不能。

总结区别
总结来说,Lock与synchronized有以下区别:

Lock是一个接口,而synchronized是关键字。
synchronized会自动释放锁,而Lock必须手动释放锁。
Lock可以让等待锁的线程响应中断,而synchronized不会,线程会一直等待下去。
通过Lock可以知道线程有没有拿到锁,而synchronized不能。
Lock能提高多个线程读操作的效率。
synchronized能锁住类、方法和代码块,而Lock是块范围内的

 

为什么java已经通过synchronized关键字实现同步访问了,还需要提供Lock?

synchronized的缺陷
前面博客有提到过释放对象的锁有两种情况:

程序执行完同步代码块会释放代码块。
程序在执行同步代码块是出现异常,JVM会自动释放锁去处理异常。
如果获取锁的线程需要等待I/O或者调用了sleep()方法被阻塞了,但仍持有锁,其他线程只能干巴巴的等着,这样就会很影响程序效率。 
因此就需要一种机制,可以不让等待的线程已知等待下去,比如值等待一段时间或响应中断,Lock锁就可以办到。

再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。

但是采用synchronized关键字来实现同步的话,就会导致一个问题:如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。 

因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。 
另外,Lock可以知道线程有没有得到锁,而synchronized不能。

总结区别
总结来说,Lock与synchronized有以下区别:

Lock是一个接口,而synchronized是关键字。
synchronized会自动释放锁,而Lock必须手动释放锁。
Lock可以让等待锁的线程响应中断,而synchronized不会,线程会一直等待下去。
通过Lock可以知道线程有没有拿到锁,而synchronized不能。
Lock能提高多个线程读操作的效率。
synchronized能锁住类、方法和代码块,而Lock是块范围内的

 

Java多线程中Lock的使用

Jdk1.5以后,在java.util.concurrent.locks包下,有一组实现线程同步的接口和类,说到线程的同步,可能大家都会想到synchronized关键字,

这是java内置的关键字,用来处理线程同步的,但这个关键字有很多的缺陷,使用起来也不是很方便和直观,所以就出现了Lock,下面,我们

就来对比着讲解Lock。

通常我们在使用synchronized关键字的时候会遇到下面这些问题:

(1)不可控性,无法做到随心的加锁和释放锁。

(2)效率比较低下,比如我们现在并发的读两个文件,读与读之间是互不影响的,但如果给这个读的对象使用synchronized来实现同步的话,

那么只要有一个线程进入了,那么其他的线程都要等待。

(3)无法知道线程是否获取到了锁。

而上面synchronized的这些问题,Lock都可以很好的解决,并且jdk1.5以后,还提供了各种锁,例如读写锁,但有一点需要注意,使用synchronized

关键时,无须手动释放锁,但使用Lock必须手动释放锁。下面我们就来学习一下Lock锁。

Lock是一个上层的接口,其原型如下,总共提供了6个方法:

public interface Lock {
  // 用来获取锁,如果锁已经被其他线程获取,则一直等待,直到获取到锁
   void lock();
  // 该方法获取锁时,可以响应中断,比如现在有两个线程,一个已经获取到了锁,另一个线程调用这个方法正在等待锁,但是此刻又不想让这个线程一直在这死等,可以通过
    调用线程的Thread.interrupted()方法,来中断线程的等待过程
  void lockInterruptibly() throws InterruptedException;
  // tryLock方法会返回bool值,该方法会尝试着获取锁,如果获取到锁,就返回true,如果没有获取到锁,就返回false,但是该方法会立刻返回,而不会一直等待
   boolean tryLock();
  // 这个方法和上面的tryLock差不多是一样的,只是会尝试指定的时间,如果在指定的时间内拿到了锁,则会返回true,如果在指定的时间内没有拿到锁,则会返回false
   boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  // 释放锁
   void unlock();
  // 实现线程通信,相当于wait和notify,后面会单独讲解
   Condition newCondition();
}

 

那么这几个方法该如何使用了?前面我们说到,使用Lock是需要手动释放锁的,但是如果程序中抛出了异常,那么就无法做到释放锁,有可能引起死锁,

所以我们在使用Lock的时候,有一种固定的格式,如下:

Lock l = ...;
l.lock();
try {
  // access the resource protected by this lock
} finally {// 必须使用try,最后在finally里面释放锁
  l.unlock();
}

下面我们来看一个简单的例子,代码如下:

/**
 * 描述:Lock使用
 */
public class LockDemo {
    // new一个锁对象,注意此处必须声明成类对象,保持只有一把锁,ReentrantLock是Lock的唯一实现类
   Lock lock = new ReentrantLock();
   public void readFile(String fileMessage){
      lock.lock();// 上锁
      try{
         System.out.println(Thread.currentThread().getName()+"得到了锁,正在读取文件……");
         for(int i=0; i<fileMessage.length(); i++){
            System.out.print(fileMessage.charAt(i));
         }
         System.out.println();
         System.out.println("文件读取完毕!");
      }finally{
         System.out.println(Thread.currentThread().getName()+"释放了锁!");
         lock.unlock();
      }
   }
   
   public void demo(final String fileMessage){
      // 创建若干个线程
      ExecutorService service = Executors.newCachedThreadPool();
      // 提交20个任务
      for(int i=0; i<20; i++){
         service.execute(new Runnable() {
            @Override
            public void run() {
               readFile(fileMessage);
               try {
                  Thread.sleep(20);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
         });
      }
    // 释放线程池中的线程
      service.shutdown();
   }
}

 

 

 

 

synchronized的使用

原文:https://www.cnblogs.com/lixiaochong/p/14426498.html

(0)
(0)
   
举报
评论 一句话评论(0
分享档案
最新文章
教程昨日排行
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!