欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java学习笔记 多线程(二)线程同步、死锁、交互

程序员文章站 2022-05-22 11:18:11
...

线程同步问题:

假设有一个数x=1000,有两个线程,一个给x加一操作、另一个给x减一操作,

那么在执行n次之后,讲道理x应该还是等于1000的,但是因为线程同步的问题,最后会出现x!=1000的情况。

 

线程同步问题产生的原因:

1. 假设增加线程先进入,得到的x是1000
2. 进行增加运算
3. 正在做增加运算的时候,还没有来得及修改hp的值,减少线程来了
4. 减少线程得到的hp的值也是1000
5. 减少线程进行减少运算
6. 增加线程运算结束,得到值1001,并把这个值赋予x
7. 减少线程也运算结束,得到值999,并把这个值赋予x

这样x的值最后就是999

这种情况的x的值999就是一个错误的值,在业务上称之为脏数据

同步问题解决的思路:

假如先进行增加操作,增加线程获得x的值并进行增加操作,在完成整个增加操作之前,如果有

减少线程试图对x进行操作,但是不被允许那么增加操作完成之后,减少操作才能访问x的值,这

样就能避免上面的脏数据问题。

synchronized同步对象概念:

只要被我synchronized修饰的对象someObject,那有其他线程试图来占有someObject的时候,就会

等待,等我释放占有之后,其他线程才能有资格操作someObject。

someObject又叫做同步对象,任何对象都能作为同步对象,为了实现同步操作,必须使用同一个对象,

所以这里的someObject是一个(Object)类型的,啥都能使用它。

final Object someObject = new Object();

释放同步对象的方式:synchronized代码块运行结束,或者有异常抛出。

Thread t1 = new Thread(){
            public void run(){
                try {
                    System.out.println( now()+" t1 线程已经运行");
                    System.out.println( now()+this.getName()+ " 试图占有对象:someObject");
                    synchronized (someObject) {
                          
                        System.out.println( now()+this.getName()+ " 占有对象:someObject");
                        Thread.sleep(5000);
                        System.out.println( now()+this.getName()+ " 释放对象:someObject");
                    }
                    System.out.println(now()+" t1 线程结束");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        };

如果所有线程都访问的同一个对象的话,就可以用该对象来代替someObject。因为所有对象都可以作为同步对象。

 

也可以在方法前直接加上synchronized,那么在外部访问此方法的时候,就不需要格外使用synchronized了,

同样也达到了同步的效果。

public void hurt(){
        //使用this作为同步对象
        synchronized (this) {
            hp=hp-1;   
        }
    }

线程安全的类:

HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式:

区别1、HashMap可以存放null,Hashtable不能存放null。

区别2、HashMap不是线程安全的类,Hashtable是线程安全的类。

Stringbuffer是线程安全的,StringBuilder不是线程安全的。

(线程安全的stringbuffer会更慢一点点,因为不需要同步,大家都抢着来,就很快)

ArrayList是非线程安全的,Vector不是线程安全的。

除此之外,Collections工具类里面有把上述非线程安全的类转换为线程安全的,比如以ArrayList为例:

package multiplethread;
 
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
public class TestThread {
    
    public static void main(String[] args) {
        List<Integer> list1 = new ArrayList<>();
        List<Integer> list2 = Collections.synchronizedList(list1);
    }
        
}

线程死锁:

死锁是在上面讲的线程占有的基础上的,

假设有a,b,c三个对象,t1,t2,t3三个线程,

当t1占有a时,接着想去占有b

同时当t2占有b时,接着想去占有c

同时当t3占有c时,接着想去占有a

但是呢,他们都是想去占有的状态,而自身占有的并没有解除,那这三线程就等,疯狂的等。

这就是线程死锁。

package Multiplethread;
import charactor.Hero;
public class Deadlock {
	/*
	 * 3个同步对象a, b, c
		3个线程 t1,t2,t3	
		故意设计场景,使这3个线程彼此死锁
	 */
	public static void main(String[] args) {
		final Hero a=new Hero();
		final Hero b=new Hero();
		final Hero c=new Hero();
		a.name="a";
		b.name="b";
		c.name="c";
		
		Thread t1 = new Thread() {
			public void run() {
				synchronized (a) {
					System.out.println("我是线程t1,我已经占有了a");
					try {
						Thread.sleep(1000);
					}catch(InterruptedException e)
					{
						e.printStackTrace();
					}
					System.out.println("我想要占有b");
					System.out.println("t1等待中");
					synchronized (b) {
						System.out.println("占有成功!");
					}
					
				}
			}
		};
		t1.start();
		
		Thread t2 = new Thread() {
			public void run() {
				synchronized (b) {
					System.out.println("我是线程t2,我已经占有了b");
					try {
						Thread.sleep(1000);
					}catch(InterruptedException e)
					{
						e.printStackTrace();
					}
					System.out.println("我想要占有c");
					System.out.println("t2等待中");
					synchronized (c) {
						System.out.println("占有成功!");
					}
					
				}
			}
		};
		t2.start();
		Thread t3 = new Thread() {
			public void run() {
				synchronized (c) {
					System.out.println("我是线程t3,我已经占有了c");
					try {
						Thread.sleep(1000);
					}catch(InterruptedException e)
					{
						e.printStackTrace();
					}
					System.out.println("我想要占有a");
					System.out.println("t3等待中");
					synchronized (a) {
						System.out.println("占有成功!");
					}
					
				}
			}
		};
		t3.start();
	}
	
}

线程交互:

在上述的过程中,线程和线程之间都是自己干自己的,要么一起争活干,要么就等别人做完才能干。

那么线程交互作用就是,让线程之间能实现配合一起工作。

两个方法:

this.wate()让占用this的线程等待,并临时释放占用,别的线程能可以先来处理this。调用wait一定

要在synchronized块里面,否则会出错。

this.notify() 通知那些在等待this的线程可以重新开始工作了,不用等待了。

还有notifyAll() 意思是所有等待的可以开始了。

比如How2j上站长给出的列子:

java学习笔记 多线程(二)线程同步、死锁、交互