Java:线程间数据共享
并发运行
线程中并发指一个时间段中多个线程都处于已启动但没有运行结束的状态。
多个线程之间默认并发运行,这种运行方式往往会出现交叉的情况。
串行运行
使原本并发运行的多个线程实现串行运行,即多线程间同步执行,需要通过对象锁机制来实现,synchronized就是一个利用锁实现线程同步的关键字。
注:
蓝框:多线程间共享的数据
红框:synchronized对象锁:该对象锁是Java中创建的一个对象,该对象可由任意类创建,只要求所创建的对象在多个线程之间共享即可。如果对象锁为全局成员,为了保证该对象在多个线程间共享,该成员往往被private static final修饰。
注:通过synchronized关键字实现了线程串行运行:一个线程执行完synchronized 代码块后另一个线程才执行,但是哪个线程先执行synchronized 代码块中的代码无法确定。
多线程同步原理
为什么通过synchronized就能实现多线程间串行运行呢?
1.被synchronized括着的部分就是线程执行临界区,每次仅能有一个线程执行该临界区中的代码:当多个线程中的某个线程先拿到对象锁, 则该线程执行临界区内的代码,其他线程只能在临界区外部等待,当此线程执行完临界区中的代码后,在临界区外部等待的其他线程开始再次竞争以获取对象锁,进而执行临界区中的代码,但只能有一条线程“胜利”。
2.临界区中的代码具有互斥性、唯一性和排它性:一个线程只有执行完临界区中的代码另一个线程才能执行。
注:
为什么这行代码60秒左右才会执行?
显示器线程和时间线程共享lockObj对象,显示器线程优先进入启动状态,随后执行相应的run方法,当执行同步代码块时lockObj变量所代表的对象锁归显示器线程所有,进而创建时间线程并使之处于启动状态,此时有一下两种状态:
1、时间线程马上进入执行状态,马上执行该时间线程run方法,可是由于此时lockObj变量所代表的对象锁被显示器线程持有,这时时间线程进入阻塞状态,显示器线程再次执行,然后执行sleep方法,显示器线程在继续持有对象锁的前提下也进入阻塞状态,60秒后显示器线程进入执行状态,随后显示器线程结束,对象锁被释放,进而时间线程开始执行,进而这行代码运行;
2、时间线程并没有马上进入执行状态,显示器线程执行sleep方法,显示器线程在继续持有对象锁的前提下也进入阻塞状态,此时时间线程进入执行状态,执行该时间线程run方法,执行该方法中第一行输出代码,可是由于此时lockObj变量所代表的对象锁被显示器线程持有,所以时间线程并没有执行时间线程run方法内临界区中的代码,这时时间线程也进入阻塞状态,此时显示器和时间两条线程均进去阻塞状态,等待少于60秒的时间后,显示器线程进入运行状态,随后显示器线程结束,对象锁被释放,进而时间线程开始执行,进而这行代码运行;
注:此时临界区中的代码无法实现串行执行,因为此时对象锁在线程1和线程2之间不共享。
注:
此时临界区中的代码依然无法实现串行执行,因为每一个独立线程拥有一个独立的对象锁——new CounterThread()。
要明白这两点:
谁调用该run方法?——CounterThread类对象;
谁执行该run方法?——正在执行的线程
注:此时临界区中的代码无法实现串行执行,因为此时对象锁在线程1和线程2之间不共享。
注:
此时临界区中的代码依然无法实现串行执行,因为每一个独立线程拥有一个独立的对象锁——new CounterThread()。
要明白这两点:
谁调用该run方法?——CounterThread类对象;
谁执行该run方法?——正在执行的线程
注:此时临界区中的代码可以实现串行执行,因为此时接口实现类对象充当了对象锁的功能,该对象锁在两个线程之间共享。
synchronized关键字
synchronized同步关键字有两种使用方式:
1.声明同步方法:
注:若为非静态同步方法,则多线程间共享调用该方法的对象;若为静态同步方法,则多线程之间同享调用该方法的类;
2.同步代码块:
注:this不能在静态方法中使用,此时括号中为”类名.class“:此时多个线程之间共享该类对应的Class类对象(示例代码见代码2)。
死锁
注:
1.如果有两个或两个以上的线程都访问了多个资源,而这些线程占用了一些资源的同时又在等待其它线程占用的资源,也就是说多个线程之间都持有了对方所需的资源,而又相互等待对方释放的资源,在这种情况下就会出现死锁。
2.多个线程互相等待对方释放对象锁,此时就会出现死锁