Java中德线程


当一个线程在指向操作共享数据的多条代码过程中,其他线程参与了运算,
 
就会导致线程安全问题。
 -----------------------------
解决思路:
 
就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,
 
其他线程不可以参与运算。
 
必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算。
 


在java中,用同步代码块就可以解决这个问题。
线程同步
 
Java多线程支持引入同步监视器来解决多个线程同时操作一个文件的问题,使用同步监视器的通用方法就是同步代码块。
 
同步代码块的格式:
 
synchronized(对象){
 
需要被同步的代码;
 
}

synchronized后括号中的obj就是同步监视器,线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。任何时候只能有一条线程可以获得对同步监视器的锁定,当同步代码块执行结束后,该线程自然释放对该同步监视器的锁定。
 
虽然Java程序允许使用任何对象来作为同步监视器,同步监视器的目的是为了阻止多条线程同时对一个共享资源进行并发访问。因此通常推荐使用可能被并发访问的共享资源充当同步监视器。

同步方法
 
与同步代码块对应的,Java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字修饰某个方法。则该方法称为同步方法。对于同步方法而言,无须显示指定同步监视器,同步方法的同步监视器是this,也就是该对象本身。
 
格式:public  synchronized  void  methodName(){
 
同步代码块
 
}
 
 


同步的好处和弊端。
 
同步的好处:解决了线程的安全问题。
 
同步的弊端:相对降低了程序的效率,因为同步外的线程都会判断同步锁。

同步的前提:
 
同步中必须有多个线程,并使用同一个锁。
同步函数的锁是this.

同步锁(Lock)
 
Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock实现允许更灵活的结构,可以具有差别很大的属性,并且可以支持多个相关的Condition对象。
 
Lock是控制多个线程对共享资源进行访问的工具,通常,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。不过,某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁)。在实现线程安全的控制中,通常使用ReentrantLock(可重入锁)。使用该Lock对象可以显示的加锁和释放锁。
 
Lock对象的代码格式:
 
public class LockTest {  
  
    // 定义锁对象  
    private final ReentrantLock lock = new ReentrantLock();  
  
    public void method() {  
        // 加锁  
        lock.lock();  
        try {  
            // 需要保证线程安全的代码  
        } catch (Exception e) {  
            // TODO: handle exception  
        } finally {  
            lock.unlock();  
        }  
    }  
}  

同步函数和同步代码块的区别:
 
同步函数的锁是固定的this。
 
同步代码块的锁是任意的对象。
 
*********建议使用同步代码块***********。
 


静态的同步函数使用的锁是:该函数所属的字节码文件对象。
 
可以用getClass获取,也可以用 当前类名.class表示。
 
例如:Class clazz1 = t.getClass();
 
Class clazz2 = Ticket2.class;

/**
 * 饿汉式
 * @author TnTSuper
 *
 */
/*class Single{
private Single(){};
private static final Single instance=new Single();
public static Single getInstance(){
return instance;
}
}*/

/**
 * 懒汉式
 */
class Single{
private Single(){};
private static Single s=null;
/* //同步方法比较低效率
public static synchronized Single getInstance(){
if(s==null){
s=new Single();
}
return s;
}*/
/**高效的同步方法的饿汉式
1.静态的同步函数使用的锁是:该函数所属的字节码文件对象。
2.懒汉式可以延迟加载
3.多线程访问会出现安全问题,需采用同步代码块!
*/
public static Single getInstance(){
if(s==null){
synchronized (Single.class) {
if(s==null){
s=new Single();
}
}
}
return s;
}
}

死锁:常见情景之一:同步的嵌套。同步中还有同步。(面试中可能会出现)
 
当两个线程相互等待对方释放同步监视器时就会发生死锁。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。
 



 
/**
 * 死锁的一个例子!
 * @author TnTSuper
 *
 */
class Test111 implements Runnable{
private boolean flag;
public Test111(boolean flag){
this.flag=flag;
}
public void run(){
if(flag){
synchronized (MyLock.myLock_A) {
System.out.println("if....myLock_A");
synchronized (MyLock.myLock_B) {
System.out.println("if....myLock_B");
}
}
}else {
synchronized (MyLock.myLock_B) {
System.out.println("else....myLock_B");
synchronized (MyLock.myLock_A) {
System.out.println("else....myLock_A");
}
}
}
}
}

class MyLock{
static Object myLock_B=new Object();
static Object myLock_A=new Object();
}

public class DeadLockTest {
public static void main(String[] args){
Thread t1=new Thread(new Test111(true));
Thread t2=new Thread(new Test111(false));
t1.start();
t2.start();
}
}

 
线程间通讯:
 
多个线程在处理同一资源,但是任务却不同。
 
线程通信
 
线程协调运行
 
为了让线程协调运行,借助Object类提供的wait()、notify()和notifAll()三个方法。这三个方法不属于Thread类,而属于Object类。但这三个方法必须由监视器对象来调用。
 
(1)对于使用synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用这三个方法。
 
(2)对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用这三个方法。
 
Object类提供的三个方法解释:
 
(1)wait():导致当前线程等待,直到其他线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。
 
(2)notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。
 
(3)notifyAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可能执行被唤醒的线程。
 
多生产者多消费者问题。 
 
if判断标记只有一次,会导致不该运行的线程运行了,会出现数据错误的情况。
 
while判断标记,解决了线程获取执行权后,是否要运行。
 
notify:只能唤醒一个线程,如果唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。
 
notifyAll 解决了本方线程一定会唤醒对方线程的问题。

public class ProducerConsumerDemo {

/**
 * 对于多个生产者和消费者。 为什么要定义while判断标记。 原因:让被唤醒的线程再一次判断标记。
 * 
 * 为什么定义notifyAll, 因为需要唤醒对方线程。 因为只用notify,容易出现只唤醒本方线程的情况。 导致程序中的所有线程都等待。
 */

public static void main(String[] args) {
Resource r = new Resource();
new Thread(new Producer(r)).start();
new Thread(new Producer(r)).start();
new Thread(new Consumer(r)).start();
new Thread(new Consumer(r)).start();
}
}

class Resource {
private String name;
private int count = 1;
private boolean flag = false;

public synchronized void set(String name) {

while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println(Thread.currentThread().getName() + "生产者" + this.name);
this.name = name + "--" + count++;
flag = true;

this.notifyAll();
}

public synchronized void Out() {
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+ "--------消费者--------" + this.name);
flag = false;

this.notifyAll();
}

}

class Producer implements Runnable {
Resource resource;

public Producer(Resource resource) {
this.resource = resource;
}

public void run() {
while (true) {
resource.set("+商品+");
}
}
}

class Consumer implements Runnable {
Resource resource;

public Consumer(Resource resource) {
this.resource = resource;
}

public void run() {
while (true) {
resource.Out();
}
}
}

DK1.5解决办法:
  k1.5以后将同步和锁封装成了对象。
 
并将操作锁的隐式方式定义到了该对象中,将隐式的动作变成了显示动作。
 


Lock接口:出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成了显示锁操作。
 
同时更为灵活,可以一个锁上加上多组监视器。
 
lock():获取锁
 
unlock():释放锁,通常需要定义在finally代码块中。
 


Condition接口:出现替代了object中的wait  notify  notifyAll方法。
 
将这些监视器方法单独进行了封装,变成Condition监视器对象。
 
可以和任意的锁进行组合。
 
await();
 
signal();
 
signalAll();
 


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * JDK1.5 中提供了多线程升级解决方案。 将同步Synchronized替换成现实Lock操作。
 * 将Object中的wait,notifynotifyAll,替换了Condition对象。 该对象可以Lock锁 进行获取。
 * 该示例中,实现了本方只唤醒对方操作。
 * 
 * Lock:替代了Synchronized lock unlock newCondition()
 * 
 * Condition:替代了Object wait notify notifyAll await(); signal(); signalAll();
 */

public class ProducerConsumerDemo2 {

/**
 * 对于多个生产者和消费者。 为什么要定义while判断标记。 原因:让被唤醒的线程再一次判断标记。
 * 
 * 为什么定义notifyAll, 因为需要唤醒对方线程。 因为只用notify,容易出现只唤醒本方线程的情况。 导致程序中的所有线程都等待。
 */

public static void main(String[] args) {
Resource2 r = new Resource2();
new Thread(new Producer2(r)).start();
new Thread(new Producer2(r)).start();
new Thread(new Consumer2(r)).start();
new Thread(new Consumer2(r)).start();
}
}

class Resource2 {
private String name;
private int count = 1;
private boolean flag = false;

private Lock lock = new ReentrantLock();
Condition conn_pro = lock.newCondition();
Condition conn_cons = lock.newCondition();

public void set(String name) throws InterruptedException {
lock.lock();
try {
while (flag) {
try {
conn_pro.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println(Thread.currentThread().getName() + "生产者"
+ this.name);
this.name = name + "--" + count++;
flag = true;

conn_cons.signal();
} finally {
lock.unlock();
}

}

public synchronized void Out() throws InterruptedException {
lock.lock();
try {
while (!flag) {
try {
conn_cons.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()
+ "--------消费者--------" + this.name);
flag = false;

conn_pro.signal();
} finally {
lock.unlock();
}
}

}

class Producer2 implements Runnable {
Resource2 resource;

public Producer2(Resource2 resource) {
this.resource = resource;
}

public void run() {
while (true) {
try {
resource.set("+商品+");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

class Consumer2 implements Runnable {
Resource2 resource;

public Consumer2(Resource2 resource) {
this.resource = resource;
}

public void run() {
while (true) {
try {
resource.Out();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}


 

-------------------------------------------------------------------------------
 

多线程中的一点小细节:
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值