Java多线程简析——Synchronized(同步锁)、Lock以及线程池

来源:互联网 发布:淘宝网牛仔女裤 编辑:程序博客网 时间:2024/06/01 07:25

Java多线程

Java中,可运行的程序都是有一个或多个进程组成。进程则是由多个线程组成的。

最简单的一个进程,会包括mian线程以及GC线程。

为什么要使用同步锁?

  在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。
 
   基本上所有的并发模式在解决线程冲突问题的时候,都是采用序列化访问共享资源的方案。这意味在给定时刻只允许一个任务访问共享资源,通常这是通过在代码前面加上一条锁语句来实现的,锁语句产生了一种互相排斥的效果,这种机制称为互斥量(mutex)。

什么时候使用同步锁呢?

  Brian同步规则:如果你正在写一个变量,它可能接下来将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你必须使用同步,并且,读写线程都必须用相同的监视器锁同步。

  注意:每个访问临界共享资源的方法都必须被同步,否则它们不会正确工作。
 

 如何使用同步锁呢?

synchronized 关键字,它包括两种用法:synchronized 方法和 synchronized 块。 

  • synchronized 方法:

public synchronized void countNum(int n);

特定对象所有synchronized方法共享同一个锁,这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。 

  不光如此,静态方法也可以声明为 synchronized ,以控制其对类的静态成员变量的访问。

public static synchronized void countNum(int n);

synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。

  典型地,若将线程类的方法 run() 声明为synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。

  • synchronized 块:

synchronized(SyncObject.Class) {     //允许访问控制的代码 } 

synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。

  在使用synchronized 块的时候,一定要遵循Brian同步规则,并对每个访问临界共享资源的方法都进行同步。



在图中,红框标识的部分方法,可以认为已过时,不再使用。

(1)wait、notify、notifyAll是线程中通信可以使用的方法。线程中调用了wait方法,则进入阻塞状态,只有等另一个线程调用与wait同一个对象的notify方法。这里有个特殊的地方,调用wait或者notify,前提是需要获取锁,也就是说,需要在同步块中做以上操作。

(2)join方法。该方法主要作用是在该线程中的run方法结束后,才往下执行。如以下代码:

package com.thread.simple;    public class ThreadJoin {              public static void main(String[] args) {            Thread thread= new Thread(new Runnable() {                            @Override              public void run() {                  System.err.println("线程"+Thread.currentThread().getId()+" 打印信息");              }          });          thread.start();                    try {              thread.join();          } catch (InterruptedException e) {              // TODO Auto-generated catch block              e.printStackTrace();          }                    System.err.println("主线程打印信息");                }    }  
该方法显示的信息是:

线程8 打印信息

主线程打印信息

如果去掉其中的join方法,则显示如下:

主线程打印信息
线程8 打印信息

(3)yield方法。这个是线程本身的调度方法,使用时你可以在run方法执行完毕时,调用该方法,告知你已可以出让内存资源。

其他的线程方法,基本都会在日常中用到,如start、run、sleep,这里就不再介绍。





阅读全文
0 0
原创粉丝点击