Java thread(4)

这一块主要是讨论关于进程同步的相关问题,主要是考虑一下的关键字:锁对象、条件对象 -> synchronized wait() notify()。

1、关于锁对象与条件对象:

所对象的定义在java中的java.util.concurrent.locks中Lock接口,具体可以有多种实现。对于lock()方法而言,通常的使用方法如下:

比如自己写的某个类已经实现了Lock接口,并且生成了一个实例myLock。之后用Lock接口的lock()方法来控制临界区的代码:

myLock.lock()//相当于获取了这个锁,如果锁同时被另一个线程拥有,则会发生阻塞
try
{
      //临界区的代码
}

finally
{
    myLock.unlock()//放弃对这个锁的拥有权
}

这个用法感觉上就像是之前操作系统中的阻塞原语的用法一样,用P、V互斥信号量,把临界区的代码包在里面。

条件对象,这个条件对象的使用就像是之前PV操作中的同步原语一样,condition对象的声明方法也在java.util.concurrent.locks 接口中。使用newCondition()可以返回一个condition实例。

调用conditioninstance.await()方法之后,则当前线程会被阻塞,并且放弃了当前线程对锁的拥有权。

conditioninstance.signalAll()这个方法的调用会激活因为这一条件而等待的所有的线程,这些线程会从等待集中移除,变成再次可运行的。

通常与Lock结合起来使用:

myLock.Lock()

try

{

While(某一个条件不满足的时候)

conditioninstance.await();

//某一个条件符合之后 就会往下实行

…

//相关的操作执行完成后 唤醒等待的线程 这些被唤醒的线程通过竞争实现访问

conditioninstance.await();

}

finally

{

myLock.unlock();

}

2、synchronized 关键字以及 notify wait 的使用:

基本上来说,这个与上面的lock以及条件对象,所实现的功能是相同的。

比如说一个用synchronized关键字修饰的方法:

public synchronized void method()
{
    Method body
}

这样的效果与使用 lock类似,相当于拥有了调用这个方法的一个对象的内部锁

public void method()
{
this.intrinsicLock.lock();
try
{
    Method body
}
finally
{
   this.intrinsicLock.unlock();
}
}

2.1、对于synchornized关键字的说明:

由于在多线程的环境中,可能会有两个或者更多的线程试图同时访问某一个资源,为了保证对于资源修改的有效性,以及修改不会引起冲突,必须采用一定的机制来控制线程对于某个资源的访问的权限。可以通过对临界代码加锁的方式来进行限制,即是前面提到的,采用锁对象的方式来进行限制。

每一个对象内置的都有一个锁或者是被称作monitor。被synchronized关键字修饰的方法叫做同步方法。当某个类的实例去访问这个实例的同步方法的时候,则被synchronized关键字修饰的方法相当于被对象的内部锁给包住了,通过synchronized关键字给这个方法上了锁,这个锁是这个对象本身的内置的锁,此时其他的任何一个线程都无法通过这个对象来直接访问这个方法,必须要等这个方法执行完后,将这个对象的内部的锁的拥有权释放,之后其他线程才能再通过这个对象来获执行synchronized方法。这样比起直接生成锁对象,再在最后解锁的方式要精简一些。

注意synchronized关键字要放在方法返回类型之前。

具体例子的代码:

package com.javase.thread;

//这个主要是演示一下 synchornized方法

public class threadTest3 {

public static void main(String []args){

synExample synexmple=new synExample();

Thread t1=new theThread(synexmple);

Thread t2=new theThread(synexmple);

t1.start();

t2.start();

}

}

class synExample{

public synchronized void excute() throws InterruptedException

{

for(int i=0;i<10;i++)

{

System.out.println("the number is "+ i);

}

return;

}

}

class theThread extends Thread{

//注意这里要通过 synExample对象来调用excute方法

private synExample synexample;

public theThread(synExample syn)

{

this.synexample=syn;

}

public void run()

{

try {

this.synexample.excute();

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

return;

}

}

这个加上了 synchronized 关键字之后,两个线程就会交替地执行 synExample 实例的excute方法,先出0-9,再出0-9。要是不加synrchonized关键字的话,就实现不了互斥访问excute关键字的效果,可能就是乱序输出。

特别注意一下,当Synchronized关键字修饰的是一个static方法的时候,此时通过synchronized关键字激活的是Class类对象的锁,并不是通过Class类生成的实例的锁。这样的话,任何相关线程通过这个class类对象来访问任何static synchornized方法(访问同一个类的static synchornized方法),都是可以实现互斥访问的。

2.2、关于synchornized块:

通过synchornized关键字来修饰一个方法,这样实现的一个互斥访问可能是粗粒度的,比如一个方法中只有某一部分需要同步,这样的话就需要用到synchrornized块来控制,表示对于块中的代码而言,某时刻只要有一个线程在执行就好。采用synchronized块的时候通常要设置一个私有的Object对象,这个Object对象没有什么实际的意义,只是用来提供一个可以使用的内部锁。

参考下面的例子:

package com.javase.synchronizedblock;

public class blockTest {

//这个主要是测试一下 关于synchornized块的信息

public static void main(String[]args){

//两个线程要访问同一个example类对象

Example example=new Example();

Thread t1=new threadExample1(example);

Thread t2=new threadExample1(example);

t1.start();

t2.start();

}

}

class Example{

private Object obj=new Object();

public void excute(){

int i;

for(i=1;i<=10;i++)

{System.out.println("the number is "+i);}

synchronized(obj){

for(i=11;i<=20;i++){

try {

Thread.sleep(100);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

System.out.println("the number of synchronized block in excute is "+i);

}

}

}

}

class threadExample1 extends Thread{

private Example example;

//构造函数

public threadExample1(Example example){

this.example=example;

}

public void run()

{

example.excute();

}

}

执行结果:
/*
the number is 1
the number is 2
the number is 1
the number is 3
the number is 2
the number is 4
the number is 3
the number is 5
the number is 4
the number is 6
the number is 5
the number is 7
the number is 6
the number is 8
the number is 7
the number is 9
the number is 8
the number is 10
the number is 9
the number is 10
the number of synchronized block in excute is 11
the number of synchronized block in excute is 12
the number of synchronized block in excute is 13
the number of synchronized block in excute is 14
the number of synchronized block in excute is 15
the number of synchronized block in excute is 16
the number of synchronized block in excute is 17
the number of synchronized block in excute is 18
the number of synchronized block in excute is 19
the number of synchronized block in excute is 20
the number of synchronized block in excute is 11
the number of synchronized block in excute is 12
the number of synchronized block in excute is 13
the number of synchronized block in excute is 14
the number of synchronized block in excute is 15
the number of synchronized block in excute is 16
the number of synchronized block in excute is 17
the number of synchronized block in excute is 18
the number of synchronized block in excute is 19
the number of synchronized block in excute is 20
*/

可以看出来,生成两个线程实例来访问通样一个Example对象,这个Example对象输出的1-10的部分没有加锁,11-20的部分被放在了synchronized块中,结果很显然,synchronized块中的部分实现了互斥访问,而外面的部分没有互斥访问,输出的顺序是混乱的。

注意synchornized块的调用方式:

直接 synchronized(obj) { 临界代码块 } 这样就ok 了。

2.3、关于wait与notify方法

wait与notify都是Object类中的方法,重要性显而易见,它们所实现的功能与之前的条件对象的功能是一致的。无论是wait与notify全需要在synchronized所修饰的方法或者方法块中进行调用,wait()方法与notify()方法总是成对出现的。

调用一个对象的wait方法的时候,该线程释放了对该对象的内部锁的拥有权,直到其他线程调用同一个对象的notify方法,原先的进程的wait状态就解除了等待,有重新竞争获得锁的资格了。注意wait方法与sleep方法的区别,虽然都是线程的等待状态,但是在sleep的时候,线程不会释放掉对锁的拥有权。

notify方法可以唤醒多个线程中的一个,这种唤醒是武断的随机选择的,每次只能唤醒一个,而notifyall方法是解除所有的在该对象上调用wait方法的线程的等待状态,使得所有的被唤醒的进程进入争夺锁的状态中,但最后只有一个线程可以重新获得锁, 一般就用notifyall因为notify有时候使用不当可能会发生死锁。

基本的线程的状态之间的转化关系可以参考下面的这个图:

一个利用notify 与 wait实现的银行账户存取钱的例子:

package com.javase.thread.waitnotify;

public class bankTest {

public static void main(String[]args){

//生成一个账户对象

bankAccount account=new bankAccount();

int i;

//启动三个取钱的线程

for(i=1;i<3;i++)

{

Thread e=new threadExtract(account,i);

e.start();

}

//启动三个存钱的线程

for(i=2;i<5;i++)

{

Thread s=new threadSave(account,i); 

s.start();

}

}

}

class bankAccount{

private int money=0;

//取钱

public synchronized void extract(int number) throws InterruptedException

{

//要是账户钱不够的话 就不能取 要一直等待 存钱之后 才能再取

while(money<=number)

{wait();}

//当符合取钱的条件时

this.money-=number;

System.out.println("取钱:"+number+" 当前账户的余额:"+money);

}

//存钱

public synchronized void save(int number) throws InterruptedException

{

//加入默认条件下 存钱没有上限 不管余额多少 都能存入

this.money+=number;

System.out.println("存钱:"+number+" 当前账户的余额:"+money);

//存入之后 唤醒 取钱的进程

notifyAll();

}

}

class threadSave extends Thread{

private bankAccount account;

private int money;

public threadSave(bankAccount accon,int savemoney)

{

account=accon;

money=savemoney;

}

public void run(){

try {

this.account.save(money);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

class threadExtract extends Thread{

private bankAccount account;

private int money;

public threadExtract(bankAccount accon,int extractmoney)

{

account=accon;

money=extractmoney;

}

public void run(){

try {

this.account.extract(money);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

}

/*

运行结果:
存钱:2 当前账户的余额:2
取钱:1 当前账户的余额:1
存钱:4 当前账户的余额:5
存钱:3 当前账户的余额:8
取钱:2 当前账户的余额:6
*/

这个例子主要就是在一个bank类里分别设置了存钱和取钱的方法,很重要的一点是在wait条件判断的那一部分要使用while而不能使用if 因为在线程很多的情况下,采用if往往会出问题,特别是在多个线程的时候,因为在这个线程wait的时候,其他线程可能已经对环境做了一些改变,这个时候还需要对条件进行重新检验。

虽然这个程序里线程的启动顺序是先取钱再存钱,但是并没有出现负数的情况,实际上是取钱进程在等待存钱进程执行完成后,才进行取钱操作。第一个打印出来的肯定是存钱,但是后面几个线程执行顺序就不确定了,每次运行的结果可能都不一样,但是可以保证的是,取钱的数目要小于账户的余额。

Java thread(4)

时间: 2024-10-22 13:35:55

Java thread(4)的相关文章

java基础知识回顾之java Thread类学习(八)--java多线程通信等待唤醒机制经典应用(生产者消费者)

 *java多线程--等待唤醒机制:经典的体现"生产者和消费者模型 *对于此模型,应该明确以下几点: *1.生产者仅仅在仓库未满的时候生产,仓库满了则停止生产. *2.消费者仅仅在有产品的时候才能消费,仓空则等待. *3.当消费者发现仓储没有产品可消费的时候,会唤醒等待生产者生产. *4.生产者在生产出可以消费的产品的时候,应该通知等待的消费者去消费. 下面先介绍个简单的生产者消费者例子:本例只适用于两个线程,一个线程生产,一个线程负责消费. 生产一个资源,就得消费一个资源. 代码如下: pub

java基础知识回顾之java Thread类学习(七)--java多线程通信等待唤醒机制(wait和notify,notifyAll)

1.wait和notify,notifyAll: wait和notify,notifyAll是Object类方法,因为等待和唤醒必须是同一个锁,不可以对不同锁中的线程进行唤醒,而锁可以是任意对象,所以可以被任意对象调用的方法,定义在Object基类中. wait()方法:对此对象调用wait方法导致本线程放弃对象锁,让线程处于冻结状态,进入等待线程的线程池当中.wait是指已经进入同步锁的线程,让自己暂时让出同步锁,以便使其他正在等待此锁的线程可以进入同步锁并运行,只有其它线程调用notify方

java基础知识回顾之java Thread类学习(六)--java多线程同步函数用的锁

1.验证同步函数使用的锁----普通方法使用的锁 思路:创建两个线程,同时操作同一个资源,还是用卖票的例子来验证.创建好两个线程t1,t2,t1线程走同步代码块操作tickets,t2,线程走同步函数封装的代码操作tickets,同步代码块中的锁我们可以指定.假设我们事先不知道同步函数用的是什么锁:如果在同步代码块中指定的某个锁(测试)和同步函数用的锁相同,就不会出现线程安全问题,如果锁不相同,就会发生线程安全问题. 看下面的代码:t1线程用的同步锁是obj,t2线程在操作同步函数的资源,假设不

java基础知识回顾之java Thread类学习(五)--java多线程安全问题(锁)同步的前提

这里举个例子讲解,同步synchronized在什么地方加,以及同步的前提: * 1.必须要有两个以上的线程,才需要同步. * 2.必须是多个线程使用同一个锁. * 3.必须保证同步中只能有一个线程在运行,锁加在哪一块代码 那么我们要思考的地方有:1.知道我们写的哪些是多线程代码 2.明确共享数据 3.明确多线程运行的代码中哪些语句是操作共享数据的.. 4.要确保使用同一个锁. 下面的代码:需求:两个存户分别往银行存钱,每次村100块,分三次存完. class bank{ private int

Java Thread.interrupt方法

部分内容引用CSDNdr8737010 比如你在main线程中,开启了一个新的线程new Thread 首先,每个线程内部都有一个boolean型变量表示线程的中断状态,true代表线程处于中断状态,false表示未处于中断状态. 而interrupt()方法的作用只是用来改变线程的中断状态(把线程的中断状态改为true,即被中断). A线程调用wait,sleep,join方法,这时B线程调用了A的interrupt方法而抛出的InterruptedException是wait,sleep,j

性能分析之-- JAVA Thread Dump 分析综述

性能分析之-- JAVA Thread Dump 分析综述 一.Thread Dump介绍 1.1什么是Thread Dump? Thread Dump是非常有用的诊断Java应用问题的工具.每一个Java虚拟机都有及时生成所有线程在某一点状态的thread-dump的能力,虽然各个 Java虚拟机打印的thread dump略有不同,但是大多都提供了当前活动线程的快照,及JVM中所有Java线程的堆栈跟踪信息,堆栈信息一般包含完整的类名及所执行的方法,如果可能的话还有源代码的行数. 1.2 T

Java thread中对异常的处理策略

转载:http://shmilyaw-hotmail-com.iteye.com/blog/1881302 前言 想讨论这个话题有一段时间了.记得几年前的时候去面试,有人就问过我一个类似的问题.就是java thread中对于异常的处理情况.由于java thread本身牵涉到并发.锁等相关的问题已经够复杂了.再加上异常处理这些东西,使得它更加特殊. 概括起来,不外乎是三个主要的问题.1. 在java启动的线程里可以抛出异常吗? 2. 在启动的线程里可以捕捉异常吗? 3. 如果可以捕捉异常,对于

三个实例演示 Java Thread Dump 日志分析

jstack Dump 日志文件中的线程状态 dump 文件里,值得关注的线程状态有: 死锁,Deadlock(重点关注)  执行中,Runnable 等待资源,Waiting on condition(重点关注) 等待获取监视器,Waiting on monitor entry(重点关注) 暂停,Suspended 对象等待中,Object.wait() 或 TIMED_WAITING 阻塞,Blocked(重点关注)   停止,Parked 下面我们先从第一个例子开始分析,然后再列出不同线程

Java Thread 相关的函数

构造方法摘要 Thread()          分配新的 Thread 对象. Thread(Runnable target)          分配新的 Thread 对象. Thread(Runnable target, String name)          分配新的 Thread 对象. Thread(String name)          分配新的 Thread 对象. Thread(ThreadGroup group, Runnable target)          分

java基础知识回顾之java Thread类学习(四)--java多线程安全问题(锁)

上一节售票系统中我们发现,打印出了错票,0,-1,出现了多线程安全问题.我们分析为什么会发生多线程安全问题? 看下面线程的主要代码: @Override public void run() { // TODO Auto-generated method stub while(true){ if(ticket > 0){//当线程0被调起的时候,当执行到这条判断语句的时候,线程1被调起抢了CPU资源,线程0进入冻结状态. try { Thread.sleep(100);//中断当前活跃的线程,或者