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

线程同步问题:

假设有一个数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上站长给出的列子:

发布了58 篇原创文章 · 获赞 20 · 访问量 5215

猜你喜欢

转载自blog.csdn.net/qq_41658124/article/details/103353658