‘壹’ 线程同步互斥的4种方式
临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的区别
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。
2、互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享
3、信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目
4、事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作
‘贰’ java线程中的同步锁和互斥锁有什么区别
互斥是通过竞争对资源的独占使用,彼此之间不需要知道对方的存在,执行顺序是一个乱序。
同步是协调多个相互关联线程合作完成任务,彼此之间知道对方存在,执行顺序往往是有序的。
‘叁’ JAVA中线程在什么时候需要同步和互斥
何时需要同步
在多个线程同时访问互斥(可交换)数据时,应该同步以保护数据,确保两个线程不会同时修改更改它。
对于非静态字段中可更改的数据,通常使用非静态方法访问
对于静态字段中可更改的数据,通常使用静态方法访问。
1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他异步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。
使用锁定还有一些其他危险,如死锁(当以不一致的顺序获得多个锁定时会发生死锁)。甚至没有这种危险,锁定也仅是相对的粗粒度协调机制,同样非常适合管理简单操作,如增加计数器或更新互斥拥有者。如果有更细粒度的机制来可靠管理对单独变量的并发更新,则会更好一些;在大多数现代处理器都有这种机制。
‘肆’ java多线程开发的同步机制有哪些
Java同步
标签: 分类:
一、关键字:
thread(线程)、thread-safe(线程安全)、intercurrent(并发的)
synchronized(同步的)、asynchronized(异步的)、
volatile(易变的)、atomic(原子的)、share(共享)
二、总结背景:
一次读写共享文件编写,嚯,好家伙,竟然揪出这些零碎而又是一路的知识点。于是乎,Google和翻阅了《Java参考大全》、《Effective Java Second Edition》,特此总结一下供日后工作学习参考。
三、概念:
1、 什么时候必须同步?什么叫同步?如何同步?
要跨线程维护正确的可见性,只要在几个线程之间共享非 final 变量,就必须使用 synchronized(或 volatile)以确保一个线程可以看见另一个线程做的更改。
为了在线程之间进行可靠的通信,也为了互斥访问,同步是必须的。这归因于java语言规范的内存模型,它规定了:一个线程所做的变化何时以及如何变成对其它线程可见。
因为多线程将异步行为引进程序,所以在需要同步时,必须有一种方法强制进行。例如:如果2个线程想要通信并且要共享一个复杂的数据结构,如链表,此时需要
确保它们互不冲突,也就是必须阻止B线程在A线程读数据的过程中向链表里面写数据(A获得了锁,B必须等A释放了该锁)。
为了达到这个目的,java在一个旧的的进程同步模型——监控器(Monitor)的基础上实现了一个巧妙的方案:监控器是一个控制机制,可以认为是一个
很小的、只能容纳一个线程的盒子,一旦一个线程进入监控器,其它的线程必须等待,直到那个线程退出监控为止。通过这种方式,一个监控器可以保证共享资源在
同一时刻只可被一个线程使用。这种方式称之为同步。(一旦一个线程进入一个实例的任何同步方法,别的线程将不能进入该同一实例的其它同步方法,但是该实例
的异步方法仍然能够被调用)。
错误的理解:同步嘛,就是几个线程可以同时进行访问。
同步和多线程关系:没多线程环境就不需要同步;有多线程环境也不一定需要同步。
锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)。
互斥即一次只允许一个线程持有某个特定的锁,因此可使用该特性实现对共享数据的协调访问协议,这样,一次就只有一个线程能够使用该共享数据。
可见性要更加复杂一些,documents它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的另一个线程是可见的 —— 如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题
小结:为了防止多个线程并发对同一数据的修改,所以需要同步,否则会造成数据不一致(就是所谓的:线程安全。如java集合框架中Hashtable和
Vector是线程安全的。我们的大部分程序都不是线程安全的,因为没有进行同步,而且我们没有必要,因为大部分情况根本没有多线程环境)。
2、 什么叫原子的(原子操作)?
Java原子操作是指:不会被打断地的操作。(就是做到互斥 和可见性?!)
那难道原子操作就可以真的达到线程安全同步效果了吗?实际上有一些原子操作不一定是线程安全的。
那么,原子操作在什么情况下不是线程安全的呢?也许是这个原因导致的:java线程允许线程在自己的内存区保存变量的副本。允许线程使用本地的私有拷贝进
行工作而非每次都使用主存的值是为了提高性能(本人愚见:虽然原子操作是线程安全的,可各线程在得到变量(读操作)后,就是各自玩
弄自己的副本了,更新操作(写操作)因未写入主存中,导致其它线程不可见)。
那该如何解决呢?因此需要通过java同步机制。
在java中,32位或者更少位数的赋值是原子的。在一个32位的硬件平台上,除了double和long型的其它原始类型通常都
是使用32位进行表示,而double和long通常使用64位表示。另外,对象引用使用本机指针实现,通常也是32位的。对这些32位的类型的操作是原
子的。
这些原始类型通常使用32位或者64位表示,这又引入了另一个小小的神话:原始类型的大小是由语言保证的。这是不对的。java语言保证的是原始类型的表
数范围而非JVM中的存储大小。因此,int型总是有相同的表数范围。在一个JVM上可能使用32位实现,而在另一个JVM上可能是64位的。在此再次强
调:在所有平台上被保证的是表数范围,32位以及更小的值的操作是原子的。
3、 不要搞混了:同步、异步
举个例子:普通B/S模式(同步)AJAX技术(异步)
同步:提交请求->等待服务器处理->处理完返回 这个期间客户端浏览器不能干任何事
异步:请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕
可见,彼“同步”非此“同步”——我们说的java中的那个共享数据同步(synchronized)
一个同步的对象是指行为(动作),一个是同步的对象是指物质(共享数据)。
4、 Java同步机制有4种实现方式:(部分引用网上资源)
① ThreadLocal ② synchronized( ) ③ wait() 与 notify() ④ volatile
目的:都是为了解决多线程中的对同一变量的访问冲突
ThreadLocal
ThreadLocal 保证不同线程拥有不同实例,相同线程一定拥有相同的实例,即为每一个使用该变量的线程提供一个该变量值的副本,每一个线程都可以独立改变自己的副本,而不是与其它线程的副本冲突。
优势:提供了线程安全的共享对象
与其它同步机制的区别:同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通信;而 ThreadLocal 是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源,这样当然不需要多个线程进行同步了。
volatile
volatile 修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。
优势:这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
缘由:Java
语言规范中指出,为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原
始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。而 volatile
关键字就是提示 VM :对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。
使用技巧:在两个或者更多的线程访问的成员变量上使用 volatile 。当要访问的变量已在 synchronized 代码块中,或者为常量时,不必使用。
线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步,因此存在A和B不一致
的情况。volatile就是用来避免这种情况的。
volatile告诉jvm,它所修饰的变量不保留拷贝,直接访问主内存中的(读操作多时使用较好;线程间需要通信,本条做不到)
Volatile 变量具有 synchronized 的可见性特性,但是不具备原子特性。这就是说线程能够自动发现 volatile
变量的最新值。Volatile
变量可用于提供线程安全,但是只能应用于非常有限的一组用例:多个变量之间或者某个变量的当前值与修改后值
之间没有约束。
您只能在有限的一些情形下使用 volatile 变量替代锁。要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
对变量的写操作不依赖于当前值;该变量没有包含在具有其他变量的不变式中。
sleep() vs wait()
sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,把执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
(如果变量被声明为volatile,在每次访问时都会和主存一致;如果变量在同步方法或者同步块中被访问,当在方法或者块的入口处获得锁以及方法或者块退出时释放锁时变量被同步。)
‘伍’ Java操作系统采用线程同步策略解决交互线程的什么问题
1、线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态。目的是防止出现数据读写错误,实现线程同步的方法有很多,临界区对象就是其中一种。
2、线程同步机制:互斥变量、触发变量、信号量机制等。
‘陆’ java线程同步问题。。
需求讲得不够清楚,尝试看你的代码设计,也看不大明白,不过有些比较明显的问题:
Thread的run方法你为何用synchronized 装饰?synchronized 同步方法的互斥只发生在多个线程调用同个对象的方法时,在这里每个线程的run方法都是由主线程开启,何来多个线程调用run?
另外 在线程内部调用wait();和notifyAll,相当于调用self.wait和self.notifyAll,这两个方法的同步是通过同一个对象来进行互斥的,那么各自线程各自利用自身进行互斥,线程之间并没有任何交集的地方,怎么实现会实现交互?举个简单的例子,线程t1调用wait()进入了自身的等待区,它现在不能再往下跑了,现在等的是别的线程来调用t1.notifyAll来激活,但是线程t2和t3都不知道t1这个东西,怎么来激活呢?甚至可能t2和t3都自己进入了自身对象的等待区而自身难保了.. 这样谁都救不了谁~~
猜想你的思路吧:140个客户,每个客户需要办理的时间分别是确定的,一共有12个窗口可以办理客户,每个窗口处理完之后紧接着处理下一个客户(每个窗口的运作时间都不会影响到其他窗口的运作),直到全部处理完是吧。那么这就是一个生产者&消费者的问题了,这里的生产者就是客户和他的时间,而且还是固定的量,消费者就是窗口,我们抽象为一个个的线程,于是:
抽象一个Routine类表示事务,里面描述了用户名称和用户需要办理的时间:
class Routine{
String name;
long orderTime;
int custNumber; // 描述当前客户的序号
}
抽象一个Routine的Provider类,用于向窗口提供事务,需要考虑同步:
class RoutineProvider{
private List<Routine> routines = new LinkedList<Routine>();
public void init(){
//在这里做初始化,为routines 添加140个实例,count置为140
}
//声明fetchRoutine,用于向窗口线程提供一个Routine,需要注意的是,这里需要同步
public synchronized Routine fetchRoutine(){
if(routines.isEmpty())return null;
return routines.removeFirst();
}
}
此后是窗口线程类:
class WorkThread extends Thread{
private RoutineProvider provider;
private String name;
public WorkThread(String name, RoutineProvider provider){
this.name = name;
this.provider = provider;
}
public void run(){
while(true){
Routine r = provider.fetchRoutine()
if(r == null)break; //事务全部被窗口线程拿走了
System.out.println("窗口[" + name + "]正在处理第" + r.getCustNumber() + "个顾客(" + r.getName() + ")");
try{
sleep(r.getOrderTime())
}catch(InterrupttedException e){}
}
}
最后的处理简单了:
1 新建一个RoutineProvider,为它初始化Routine列表
2 创建一系列的窗口线程(WorkThread),在构造函数中指定窗口名、还有Provider(全局唯一)
3 逐个调用线程的start()方法。
PS:1楼的解法很明显有缺陷:两个synchronized的方法并没有真实的同步,在线程处理上面没有考虑共享数据侵蚀的问题,比如取出一个用户之后,count立马需要-1,这个操作需要作为原子,这里便没有考虑到。
‘柒’ 如何在Java多线程编程中实现程序同与互斥
作为一个完全面向对象的语言,Java提供了类 Java.lang.Thread 来方便多线程编程,这个类提供了大量的方法来方便我们控制自己的各个线程,我们以后的讨论都将围绕这个类进行。
Thread 类最重要的方法是 run() ,它为Thread 类的方法 start() 所调用,提供我们的线程所要执行的代码。为了指定我们自己的代码,只需要覆盖它!
方法一:继承 Thread 类,覆盖方法 run()
我们在创建的 Thread 类的子类中重写 run() ,加入线程所要执行的代码即可。
下面是一个例子:
public class MyThread extends Thread {
int count= 1, number;
public MyThread(int num) {
number = num;
System.out.println("创建线程 " + number);
}
public void run() {
while(true) {
System.out.println("线程 " + number + ":计数 " + count);
if(++count== 6) return;
}
}
public static void main(String args[]) {
for(int i = 0; i < 5; i++) new MyThread(i+1).start();
}
}
这种方法简单明了,符合大家的习惯,但是,它也有一个很大的缺点,那就是如果我们的类已经从一个类继承(如小程序必须继承自 Applet 类),则无法再继承 Thread 类,这时如果我们又不想建立一个新的类.
一种新的方法:不创建 Thread 类的子类,而是直接使用它,那么我们只能将我们的方法作为参数传递给 Thread 类的实例,有点类似回调函数。但是 Java 没有指针,我们只能传递一个包含这个方法的类的实例。那么如何限制这个类必须包含这一方法呢?当然是使用接口!(虽然抽象类也可满足,但是需要继承,而我们之所以要采用这种新方法,不就是为了避免继承带来的限制吗?)
Java 提供了接口 Java.lang.Runnable 来支持这种方法。
方法二:实现 Runnable 接口
Runnable 接口只有一个方法 run(),我们声明自己的类实现 Runnable 接口并提供这一方法,将我们的线程代码写入其中,就完成了这一部分的任务。
但是 Runnable 接口并没有任何对线程的支持,我们还必须创建 Thread 类的实例,这一点通过 Thread 类的构造函数
public Thread(Runnable target);
来实现。
下面是一个例子:
public class MyThread implements Runnable {
int count= 1, number;
public MyThread(int num) {
number = num;
System.out.println("创建线程 " + number);
}
public void run() {
while(true) {
System.out.println("线程 " + number + ":计数 " + count);
if(++count== 6) return;
}
}
public static void main(String args[])
{
for(int i = 0; i < 5; i++) new Thread(new MyThread(i+1)).start();
}
}
‘捌’ java并发编程中,有哪些同步和互斥机制
多线程共享资源,比如一个对象的内存,怎样保证多个线程不会同时访问(读取或写入)这个对象,这就是并发最大的难题,因此产生了 互斥机制(锁)。
using the same monitor lock.
获取锁后,该线程本地存储失效,临界区(就是获得锁后释放锁之前 的代码区)从主存获取数据,并在释放锁后刷入主存。
互斥:
保证临界区代码线程间互斥。
synchronized实现同步的基础:
java中每个对象都可以作为锁
一个任务可以多次获得锁,比如在一个线程中调用一个对象的 synchronized标记的方法,在这个方法中调用第二个synchronized标记的方法,然后在第二个synchronized方法中调用第三个synchronized方法。一个线程每次进入一个synchronized方法中JVM都会跟踪加锁的次数,每次+1,当该这个方法执行完毕,JVM计数-1;当JVM计数为0时,锁完全被释放,其他线程可以访问该变量。
在使用并发时将对象的field设为private 很重要!尤其是使用static变量(evil static variable) 使用 Lock lock =new ReentrantLock()的问题是代码不够优雅,增加代码量;我们一般都是使用synchronized实现互斥机制。但是1.当代码中抛出异常时,显示锁的finally里可以进行资源清理工作。2.ReentrantLock还给我们更细粒度的控制力
‘玖’ Java如何实现线程之间的互斥
临界区(Critical Section):适合一个进程内的多线程访问公共区域或代码段时使用
Java如何实现线程之间的互斥
互斥量 (Mutex):适合不同进程内多线程访问公共区域或代码段时使用,与临界区相似。
事件(Event):通过线程间触发事件实现同步互斥
信号量(Semaphore):与临界区和互斥量不同,可以实现多个线程同时访问公共区域数据,原理与操作系统中PV操作类似,先设置一个访问公共区域的线程最大连接数,每有一个线程访问共享区资源数就减一,直到资源数小于等于零。
‘拾’ java多线程有几种实现方法线程之间如何同步
Java多线程有两种实现方式:一种是继承Thread类,另一种是实现Runable接口,大同小异,推荐后者,因为实现接口的话这个类还可以实现别的接口和继承一个类,灵活性好,若继承Thread类之后,就无法继承其他类了。
至于实现同步,最简单的方法就是使用同步块,synchronized(){语句块}
当多个线程同时访问到同步语句块时,会由一个线程先获得对象锁,获取对象锁的线程执行完毕之后,释放锁,其他线程再次竞争锁,一个一个通过,不存在两个以上线程同时执行同步语句块的情况。