4.2.2 synchronized关键字的使用
大多数情况下,并不需要使用Lock来提供高度的锁定控制。每一个对象都有一个内部锁,如果一个方法用synchroized声明,那么对象的锁将保护整个方法,即线程需要获得内部的对象锁后才能调用该方法。、
public synchronized void method()
{
method body
}
//等价于
public void method()
{
this.intrinsicLock.lock();
try{
method body
}
finally{this.intrinsicLick.unlock();}
}
使用synchronized,如果需要使线程等待,可以使用object方法wait()与notifyAll()。需要注意的是,可以对静态方法使用synchronized关键字,当方法调用时,会锁住xxxx.class对象的锁。那么,这个类的同步静态方法其他线程就不可以调用。
同时,synchronized还可以使用同步代码块的信息
4.2.3 Volatile域的使用
如果只需要对一个实例域的存储和读取而设置synchronized关键字,花销太大且如果只使用synchronized会和其他线程同用一个锁。这种情况下,如果只是对一个共享变量除了赋值之外不进行其他操作,使用volatile关键字是最合理的。
4.3 死锁
4.3.1 什么是死锁
有可能会因为每一个线程需要等待其他线程的操作而导致所有线程都阻塞,这种情况称为死锁
代码说明如图:
public class SyncDeadLock {
private static Object locka = new Object();
private static Object lockb = new Object();
public static void main(String[] args) {
new SyncDeadLock().deadLock();
}
private void deadLock() {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (locka) {
try {
System.out.println(Thread.currentThread().getName() + " get locka ing!");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " after sleep 500ms!");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " need lockb!Just waiting!");
synchronized (lockb) {
System.out.println(Thread.currentThread().getName() + " get lockb ing!");
}
}
}
}, "thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockb) {
try {
System.out.println(Thread.currentThread().getName() + " get lockb ing!");
Thread.sleep(500);
System.out.println(Thread.currentThread().getName() + " after sleep 500ms!");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " need locka! Just waiting!");
synchronized (locka) {
System.out.println(Thread.currentThread().getName() + " get locka ing!");
}
}
}
}, "thread2");
thread1.start();
thread2.start();
}
}
两个线程之间因互相需要引用对方持有的锁而导致两个线程都进入阻塞称为死锁。
4.4线程局部变量
线程之间共享局部变量会有各种各样的危险,我们可以使用ThreadLocal辅助类为各个线程提供各自得实例
public static final ThreadLocal<SimpleDateFormat> dateFormat=ThreadLocal.withInitial(()->new SimpleDateFormat("yyyy-MM-dd"));
//并通过如下方法调用
dateFormat.get().format(new Date())
4.5 锁测试与超时
线程调用lock方法来获取另一个线程所持有的锁如果失败则会陷入阻塞,如果不需要陷入阻塞而转去做其他事时,可以使用tryLock方法试图申请一个锁,如果成功返回true,失败可以转去做其他事
if(myLock.tryLock())
{
try{
}finally{
myLock.unLock()
}
}else{
//do anothing
}
4.6阻塞队列
对于许多线程问题,都可以通过使用一个或者多个队列以优雅且安全的方式将其形式化。生产者线程负责向队列插入数据,消费者线程取出他们。使用队列,可以安全的从一个线程向另一个线程传递数据。在银行转账实例中,我们可以使用转账线程将转账指令对象插入到一个队列中,而不是直接访问银行对象。另一个线程从队列中取出指令执行转账,只有该线程可以访问该银行内部对象,因此不需要同步。下面是一个在特定文件夹中寻找特定关键字并输出改行的示例
public class BlockingQueueTest
{
private static final int FILE_QUEUE_SIZE = 10;
private static final int SEARCH_THREADS = 100;
private static final File DUMMY = new File("");
private static BlockingQueue<File> queue = new ArrayBlockingQueue<>(FILE_QUEUE_SIZE);
public static void main(String[] args)
{
try (Scanner in = new Scanner(System.in))
{
System.out.print("Enter base directory (e.g. /opt/jdk1.8.0/src): ");
String directory = in.nextLine();
System.out.print("Enter keyword (e.g. volatile): ");
String keyword = in.nextLine();
Runnable enumerator = () -> {
try
{
enumerate(new File(directory));
queue.put(DUMMY);
}
catch (InterruptedException e)
{
}
};
new Thread(enumerator).start();
for (int i = 1; i <= SEARCH_THREADS; i++) {
Runnable searcher = () -> {
try
{
boolean done = false;
while (!done)//done=false
{
System.out.println(Thread.currentThread().getName()+"要去队列获取数据");
File file = queue.take();//如果队列空,将阻塞
System.out.println(Thread.currentThread().getName()+"获取到了数据,就是这个:"+file);
if (file == DUMMY)//file="",把done设置为true
{
System.out.println(Thread.currentThread().getName()+"获取到了“”数据");
queue.put(file);//线程停止使用,不用线程会阻塞
done = true;//停止
}
else search(file, keyword);
}
}
catch (IOException e)
{
e.printStackTrace();
}
catch (InterruptedException e)
{
}
};
new Thread(searcher).start();
}
}
}
/**
* Recursively enumerates all files in a given directory and its subdirectories.
* @param directory the directory in which to start
*/
public static void enumerate(File directory) throws InterruptedException
{
File[] files = directory.listFiles();
for (File file : files)
{
if (file.isDirectory()) enumerate(file);
else queue.put(file);
}
}
/**
* Searches a file for a given keyword and prints all matching lines.
* @param file the file to search
* @param keyword the keyword to search for
*/
public static void search(File file, String keyword) throws IOException
{
try (Scanner in = new Scanner(file, "UTF-8"))
{
int lineNumber = 0;
while (in.hasNextLine())
{
lineNumber++;
String line = in.nextLine();
if (line.contains(keyword))
System.out.printf("%s:%d:%s%n", file.getPath(), lineNumber, line);
}
}
}
}
其中 put方法添加一个元素,队列满则阻塞,take方法取出一个元素,队列空则阻塞