您的位置:首页 > 编程语言 > Java开发

java 7 并发 初级 学习记录(2)

2014-06-24 00:00 351 查看
1.资源竞争

并发的难点集中在如何对一个资源进行操作并且保证不出错,如果不采取任何措施,线程可不会乖乖按照你的想法进行执行,他们会产生一些非常奇葩的问题。

关键字:synchronized,同步关键字,可以修饰整个方法,也可以在方法内部使用同步块。它的作用,就是让多个线程在同时访问某个方法的时候,进行单一处理。每次只允许一个线程进入方法,进入后,对方法加锁,其他线程全部在方法外暂时堵塞,直到进入方法中的线程执行完毕之后,解锁,其他线程才允许进入。很好理解!

那如果有多个方法被修饰了synchronized呢?可以把多个synchronized修饰的方法想成一个同步组方法,他们就是一个整体,这一个组方法都只允许每次只有一个线程在执行。

那如果static方法被修饰了synchronized呢?很明显,那所有的线程对这个static方法都会被同步,而不单单只是对某一个对象了。如果有一个static同步方法和一个普通同步方法呢?要注意的是,他们不会被归为一个同步组中,即一个线程可以进入static同步方法中,而在同一时刻,另一个线程可以进入普通同步方法中。

2.同步代码块

synchronized修饰的方法,线程会被阻塞在方法外面,这样会造成一定程度上的性能损耗。其实很多时候,需要同步的部分只是方法中的某个部分,甚至就几行代码。所以我们可以把同步的规模进行缩小,缩小到只包含这一部分的代码。可以使用synchronized代码块来实现,synchronized代码块会接收一个参数,这个参数通常是某个对象,或者是this自身,其实他的含义就是需要获取谁的对象锁。通常在一个对象中使用synchronized代码块,参数一般设置成this,那么这把锁就是当前对象的锁,不过也有另外一种可能,比如两个同步方法,我需要有多个线程能分别同时进入。上面已经有说明了同一个对象中,synchronized方法组只能有一个线程进入,那怎么还能同时进入两个方法呢?synchronized代码块可以解决,秘诀就是这个参数的设置。要知道一个本质,同步都是根据对象的锁的进行的,不同的锁当然是可以给多个线程去执行的,所以synchronized(obj){}中的obj除了this,当然还可以自定义一个Object类的实例来充当,即在类中定义一个Object类的属性,把他设置为同步代码块的参数,OK,这样就不会和持有this锁的部分同步了。

以上全是理论,下面做点小例子测试。

public class JavaCurrent {

public static void main(String[] args) {
Target
7fe0
target = new Target();
for (int i = 0; i < 2; i++) {
Thread t = new Thread(new Task(target));
t.start();
}
Thread thread1 = new Thread(new Task1(target));
Thread thread2 = new Thread(new Task2(target));
thread1.start();
thread2.start();
}
}

----------------------------------------------------------------------------------------
/**
* 竞争目标类
* @author dlsm-syq
*
*/
public class Target {

private Object obj = new Object();//新建加锁对象

public synchronized static void methodOfStatic(){
System.out.println(Thread.currentThread().getName()+"->进入methodOfStatic静态方法中,处理一些事物,需要3秒时间");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " methodOfStatic 处理完成,期间没有其他线程打扰");
}

public void methodOfNormalBlock(){
synchronized (this) {
System.out.println(Thread.currentThread().getName() + "-->进入methodOfNormalBlock方法代码块中,处理事物需要3秒钟");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " methodOfNormalBlock 处理完成,期间没有其他线程打扰");
}
}

public void methodOfNormalBlockObj(){
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "-->进入methodOfNormalBlockObj方法代码块中,处理事物需要3秒钟");
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " methodOfNormalBlockObj 处理完成,期间没有其他线程打扰");
}
}
}

-----------------------------------------------------------------------------------------
public class Task implements Runnable{

private Target target;

public Task(Target target) {
this.target = target;
}

@Override
public void run() {
Target.methodOfStatic();
}
}

public class Task1 implements Runnable{

private Target target;

public Task1(Target target) {
this.target = target;
}

@Override
public void run() {
target.methodOfNormalBlock();
}
}

public class Task2 implements Runnable{

private Target target;

public Task2(Target target) {
this.target = target;
}

@Override
public void run() {
target.methodOfNormalBlockObj();
}
}

----------------------------------------------执行结果-------------------------------------
Thread-0->进入methodOfStatic静态方法中,处理一些事物,需要3秒时间
Thread-3-->进入methodOfNormalBlockObj方法代码块中,处理事物需要3秒钟
Thread-2-->进入methodOfNormalBlock方法代码块中,处理事物需要3秒钟
Thread-0 methodOfStatic 处理完成,期间没有其他线程打扰
Thread-2 methodOfNormalBlock 处理完成,期间没有其他线程打扰
Thread-1->进入methodOfStatic静态方法中,处理一些事物,需要3秒时间
Thread-3 methodOfNormalBlockObj 处理完成,期间没有其他线程打扰
Thread-1 methodOfStatic 处理完成,期间没有其他线程打扰


貌似没有问题

3.wait(),notify(),notifyall()

先说说对象锁。一个线程都是在进入synchronized方法或者代码块中才能获取该对象的锁,在执行完毕之前,都会一直持有锁。所以这里一个非常重要的注意点就是线程必须持有锁,也就是说必须在synchronized方法或者代码块内部情况下,才能继续话题。

wait(),notify(),notifyall() 三个方法都不是线程相关类中的方法,而是原始类Object中的方法,原因很简单了,就是每个对象都有控制线程的权利。三个方法都必须在synchronized方法或者代码块中使用,不然必定抛出java.lang.IllegalMonitorStateException异常。要注意是调用他们三个的时候,调用notify的对象必须要和synchronized的锁对象保持一致,不然也是IllegalMonitorStateException异常。也许有点混乱,这里举个例子说明:

public class JavaCurrent {

public static void main(String[] args) {
B b = new B();
A a = new A(b);

Thread t1 = new Thread(new ATask(a));
Thread t2 = new Thread(new BTask(b));

t2.start();
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.start();
}

}

class ATask implements Runnable{

private A a;

public ATask(A a) {
this.a = a;
}

@Override
public void run() {
a.amethod();
}

}

class BTask implements Runnable{

private B b;

public BTask(B b) {
this.b = b;
}

@Override
public void run() {
for (int i = 0; i < 10; i++) {
b.bmethod();
}
}

}

------------------------------------------------------------------------------
public class A {

private B b;

public A(B b) {
this.b = b;
}

public synchronized void amethod(){
System.out.println(Thread.currentThread().getName()+" in amethod doing something");
b.notifyAll();
}

}

---------------------------------------------------------------------------------
public class B {

public synchronized void bmethod(){
System.out.println(Thread.currentThread().getName() +" in bmethod doing somethind");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

运行结果:
Thread-1 in bmethod doing somethind
Thread-0 in amethod doing something
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.lang.Object.notifyAll(Native Method)
at syq.think.current7.p70.A.amethod(A.java:13)
at syq.think.current7.p70.ATask.run(JavaCurrent.java:60)
at java.lang.Thread.run(Unknown Source)


例子中,A类中持有一个B类的对象引用,然后在synchronized方法中调用b.notifyall()方法,结果出现异常。也就是说没有获取b的对象锁,但却是在a的synchronized方法中。所以调用notifyall的对象所处的同步块要在该对象的synchronized块中。

wait()和sleep()的区别,sleep()可以在任何地方使用,wait()必须在同步块中使用,wait()会使当前线程释放对象锁,然后进入一个waitting room的线程等候区,等待notify。notify()会挑waitting room中的一条线程进行唤醒,notifyall()唤醒waitting room中的所有线程,然后线程都去竞争对象锁,建议使用notifyall方法。

接下来看看具体用法的例子:

public class JavaCurrent {

public static void main(String[] args) {
//开8个线程去跑WaitTask
Target target = new Target();
for (int i = 0; i < 8; i++) {
Thread t = new Thread(new WaitTask(target));
t.start();
}

try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//开1个线程去跑NotifyTask
Thread thread = new Thread(new NotifyTask(target));
thread.start();
}
}

----------------------------------------------------------------------------------
public class Target {

private int allowIn = 3;//允许4个线程经过setwait()

private int count = 0;

public void setWait(){
System.out.println(Thread.currentThread().getName()+ " 进入setWait");
synchronized (this) {
System.out.println(Thread.currentThread().getName()+ " 进入setWait中的同步块中");
if(count > allowIn){
try {
System.out.println(Thread.currentThread().getName()+ " 发现已经超出最大数值了,只能等待了!");
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
TimeUnit.SECONDS.sleep(1);//休眠1秒
System.out.println(Thread.currentThread().getName()+ " 处理setWait完毕,退出方法,释放锁");
count ++ ;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public void setNotify(){
System.out.println(Thread.currentThread().getName()+ " 进入setNotify");
synchronized (this) {
System.out.println(Thread.currentThread().getName()+ " 进入setNotify中的同步块中");
if(count > allowIn){
System.out.println(Thread.currentThread().getName()+ " 发现已经超出最大数值了,数值清理!");
count = 0;
notifyAll();
}else{
try {
TimeUnit.SECONDS.sleep(2);//休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ " 处理setNotify完毕,退出方法,释放锁");
}
}
}
}

------------------------------------------------------------------------------------
public class NotifyTask implements Runnable{

private Target target;

public NotifyTask(Target target) {
this.target = target;
}

@Override
public void run() {
while(true){
target.setNotify();
}
}

}

public class WaitTask implements Runnable{

private Target target;

public WaitTask(Target target) {
this.target = target;
}

@Override
public void run() {
target.setWait();
}

}

执行结果:
Thread-0 进入setWait
Thread-2 进入setWait
Thread-1 进入setWait
Thread-0 进入setWait中的同步块中
Thread-3 进入setWait
Thread-5 进入setWait
Thread-7 进入setWait
Thread-4 进入setWait
Thread-6 进入setWait
Thread-0 处理setWait完毕,退出方法,释放锁
Thread-6 进入setWait中的同步块中
Thread-6 处理setWait完毕,退出方法,释放锁
Thread-4 进入setWait中的同步块中
Thread-8 进入setNotify
Thread-4 处理setWait完毕,退出方法,释放锁
Thread-2 进入setWait中的同步块中
Thread-2 处理setWait完毕,退出方法,释放锁
Thread-7 进入setWait中的同步块中
Thread-7 发现已经超出最大数值了,只能等待了!
Thread-5 进入setWait中的同步块中
Thread-5 发现已经超出最大数值了,只能等待了!
Thread-3 进入setWait中的同步块中
Thread-3 发现已经超出最大数值了,只能等待了!
Thread-1 进入setWait中的同步块中
Thread-1 发现已经超出最大数值了,只能等待了!
Thread-8 进入setNotify中的同步块中
Thread-8 发现已经超出最大数值了,数值清理!
Thread-8 进入setNotify
Thread-1 处理setWait完毕,退出方法,释放锁
Thread-3 处理setWait完毕,退出方法,释放锁
Thread-5 处理setWait完毕,退出方法,释放锁
Thread-7 处理setWait完毕,退出方法,释放锁
Thread-8 进入setNotify中的同步块中
Thread-8 发现已经超出最大数值了,数值清理!
Thread-8 进入setNotify
Thread-8 进入setNotify中的同步块中
Thread-8 处理setNotify完毕,退出方法,释放锁
Thread-8 进入setNotify
Thread-8 进入setNotify中的同步块中


可以看到同时开了8个线程跑waitTask,当thread0进入同步块之后,其他线程都在外面等待了,知道它处理完毕释放锁了,thread6进入了同步块中进行相同的处理。但thread7进入时,因为前面已经有thread0,6,4,2这四个线程通过了,所以thread7只能wait等待了,同理后面的进入的线程全部都要去wait等待。最后当thread8进入setNotify并执行完毕之后,重置了count属性,并且notifyall所有wait线程,后续的线程又可以继续活动了。注意:被唤醒之后的线程会重新竞争对象锁,然后从wait()处继续向下执行。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: