同步问题:每一个线程对象轮番抢占共享资源带来的问题
首先看一段代码:
class MyThread implements Runnable{
private int ticket=10;
@Override
public void run() {
while (this.ticket>0){
try{
//模拟网络延迟
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
",还有"+this.ticket--+"张票");
}
}
}
public class Test2 {
public static void main(String[] args) throws InterruptedException {
MyThread mt=new MyThread();
new Thread(mt,"黄牛A").start();
new Thread(mt,"黄牛B").start();
new Thread(mt,"黄牛C").start();
}
}
这时会发现运行到最后时会出现如下结果,当只剩下一张票的时候,却被三个人拿去同时买:
......
黄牛B,还有1张票
黄牛C,还有0张票
黄牛A,还有-1张票
上面出现现象就可以称之为不同步操作,其唯一好处就是处理速度快(多个线程并发执行)
synchronize处理同步有两种模式:同步代码块,同步方法
同步代码块:(推荐使用,锁粒度较细)要使用同步代码块必须设置一个锁定的对象,一般可以锁当前对象this
同一时刻只能有一个线程进入代码块,方法内仍然是多线程
synchronized(this){
//需要同步代码块
}
class MyThread implements Runnable{
private int ticket=1000;
@Override
public void run() {
for(int i=0;i<1000;i++){
synchronized (this){
if(this.ticket>0){
System.out.println(Thread.currentThread().getName()+
",还有"+this.ticket--+"张票");
}
}
}
}
}
public class Test2 {
public static void main(String[] args) throws InterruptedException {
MyThread mt=new MyThread();
new Thread(mt,"黄牛A").start();
new Thread(mt,"黄牛B").start();
new Thread(mt,"黄牛C").start();
}
}
会发现执行结果中没有负数:
......
黄牛B,还有3张票
黄牛B,还有2张票
黄牛B,还有1张票
同步方法:在方法上添加synchronize关键字,表示此方法只有一个线程能进入。隐式锁对象,this
同一时刻只有一个线程能进入此方法
范例:
class MyThread implements Runnable{
private int ticket=1000;
@Override
public void run() {
for(int i=0;i<1000;i++){
if(this.ticket>0){
this.sala();
}
}
}
public synchronized void sala(){
if(this.ticket>0){
try {
//模拟网络延迟
Thread.sleep(20);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+
",还有"+this.ticket--+"张票");
}
}
}
public class Test2 {
public static void main(String[] args) throws InterruptedException {
MyThread mt=new MyThread();
new Thread(mt,"黄牛A").start();
new Thread(mt,"黄牛B").start();
new Thread(mt,"黄牛C").start();
}
}
会发现执行结果中没有负数:
......
黄牛C,还有3张票
黄牛A,还有2张票
黄牛A,还有1张票
同步的缺点:虽然可以保证数据的完整性(线程安全操作),但是其执行的速度回很慢 StringBuilder 和 StringBuffer
①实际上synchronized(this)
非static的synchronized方法,只能防止多个线程同时执行同一个对象的同步代码块。
synchronized叫对象锁(默认只能锁一个对象),锁的是对象本身即this。
范例:观察synchronized的额外说明
class Sync{
public synchronized void test(){ //默认锁的是当前对象
System.out.println("test方法开始,当前线程"+Thread.currentThread().getName());
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("test方法结束,当前线程为"+Thread.currentThread().getName());
}
}
class MyThread extends Thread{
//使用static只实例化一个Sync对象,这样三个线程使用同一块锁
//static Sync sync=new Sync();
@Override
public void run() {
Sync sync=new Sync();
sync.test();
}
}
public class Test2 {
public static void main(String[] args) {
for(int i=0;i<3;i++){
Thread thread=new MyThread();
thread.start();
}
}
}
观察结果,三个线程同时开始,同时结束:
test方法开始,当前线程Thread-0
test方法开始,当前线程Thread-1
test方法开始,当前线程Thread-2
test方法结束,当前线程为Thread-0
test方法结束,当前线程为Thread-1
test方法结束,当前线程为Thread-2
若要锁住这段代码还有三中思路:
范例: 锁同一个对象
class Sync{
public synchronized void test(){
System.out.println("test方法开始,当前线程"+Thread.currentThread().getName());
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("test方法结束,当前线程为"+Thread.currentThread().getName());
}
}
class MyThread extends Thread{
private Sync sync;
public MyThread(Sync sync){
this.sync=sync;
}
@Override
public void run() {
this.sync.test();
}
}
public class Test2 {
public static void main(String[] args) {
//实例化一个Sync对象
Sync sync=new Sync();
for(int i=0;i<3;i++){
Thread thread=new MyThread(sync);
thread.start();
}
}
}
对象锁与全局锁:
synchronized默认对象锁,锁的是当前对象而非代码块
全局锁锁的是真正代码段,与对象无关!
实现全局锁的两种方式:
1.在同步代码段锁Class对象
2.使用static synchronized方法
范例:使用全局锁,锁的是类而不是this对象
class Sync{
public void test(){
//使用全局锁,锁的是以下代码段,与对象无关
synchronized (Sync.class){
System.out.println("test方法开始,当前线程"+Thread.currentThread().getName());
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("test方法结束,当前线程为"+Thread.currentThread().getName());
}
}
}
class MyThread extends Thread{
@Override
public void run() {
Sync sync = new Sync() ;
sync.test();
}
}
public class Test2 {
public static void main(String[] args) {
for(int i=0;i<3;i++){
Thread thread=new MyThread();
thread.start();
}
}
}
static synchronized方法,static方法可以直接类名加方法名调用,方法中无法使用this,所以它锁的不是this,而是类 的Class对象,所以,static synchronized方法也相当于全局锁,相当于锁住了代码段。
范例:使用static synchronized方法
class Sync{
//此时锁的为一下代码块,与对象无关
public static synchronized void test(){ //默认锁的是当前对象
System.out.println("test方法开始,当前线程"+Thread.currentThread().getName());
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("test方法结束,当前线程为"+Thread.currentThread().getName());
}
}
class MyThread extends Thread{
@Override
public void run() {
Sync sync=new Sync();
sync.test();
}
}
public class Test2 {
public static void main(String[] args) {
for(int i=0;i<3;i++){
Thread thread=new MyThread();
thread.start();
}
}
}
class MyThread implements Runnable{
private ThreadTest test;
public MyThread(ThreadTest test){
super();
this.test=test;
}
@Override
public void run() {
if(Thread.currentThread().getName().equals("Thread-1")){
this.test.testA();
}else if(Thread.currentThread().getName().equals("Thread-2")){
this.test.testB();
}
}
}
class ThreadTest{
public synchronized void testA(){
System.out.println(Thread.currentThread().getName()+"tsetA方法");
while(true){}
}
public synchronized void testB(){
System.out.println(Thread.currentThread().getName()+"tsetB方法");
}
}
public class Test2 {
public static void main(String[] args) throws InterruptedException {
ThreadTest test=new ThreadTest();
MyThread mythread=new MyThread(test);
Thread thread1=new Thread(mythread);
Thread thread2=new Thread(mythread);
thread1.start();
Thread.sleep(1000);
thread2.start();
}
}
线程2是否能进入到testB中吗?
锁的是当前对象,两个同步方法锁的是同一个对象
同一个对象,当一个类中有两个同步方法时,有一个线程已经进了一个同步方法时,另外一个线程绝对不能进入另外的同步方法
如果有一个同步方法一个普通方法,另外一个线程可以进到普通方法
同步代码块:执行同步代码块后首先要执行monitorenter指令,退出时执行monitorexit指令。
使用内建锁(synchronized)进行同步,关键在于获取指定锁对象monitor对象,当线程获取monitor后才能继续向下执行,否则就只能等待。这个获取过程是互斥的,即同一时刻只有一个线程能够获取到对象monitor。
通常一个monitorenter指令会包含若干个monitorexit指令。原因在于JVM需要确保在正常执行路径以及异常执行路径上都能正确的解锁,任何情况下都能够解锁。
同步方法:当使用所有synchronized标记方法时,编译后字节码中方法的访问标记多了一个 ACC_SYNCHRONIZED。该标记表示,进入该方法时,JVM需要进行monitorenter操作,退出该方法时,无论是否正常返回,JVM均需要进行monitorexit操作。
当执行monitorenter时,如果目标锁对象的monitor计数器为0,表示此对象没有被任何其他对象所持有。此时JVM会将该锁对象的持有线程设置为当前线程,并且有计数器+1;如果目标锁对象计数器不为0,判断锁对象的持有线程是否是当前线程,如果是再次将计数器+1(锁的可重入性),如果锁对象的持有线程不是当前线程,当前线程需要等待,直至持有线程释放锁。
当执行monitorexit时,JVM会将锁对象的计数器-1,当计数器减为0时,代表该锁已经被释放。
JDK1.5提供的Lock锁(了解即可 )(任然是对象锁)
范例:使用ReentrantLock进行同步处理,在需要上锁的时候Lock,在finally中unLock
class MyThread implements Runnable{
private int ticket=100;
private Lock ticketLock=new ReentrantLock();
@Override
public void run() {
for(int i=0;i<100;i++){
try{
ticketLock.lock();
if(this.ticket>0) {
System.out.println(Thread.currentThread().getName() +
"还剩下:" + this.ticket-- + "票");
}
}finally{
//手工释放锁
ticketLock.unlock();
}
}
}
}
public class Test2 {
public static void main(String[] args) throws InterruptedException {
MyThread mt=new MyThread();
Thread thread1=new Thread(mt,"黄牛1");
Thread thread2=new Thread(mt,"黄牛2");
Thread thread3=new Thread(mt,"黄牛3");
thread1.setPriority(Thread.MIN_PRIORITY);
thread2.setPriority(Thread.MAX_PRIORITY);
thread3.setPriority(Thread.MAX_PRIORITY);
thread1.start();
thread2.start();
thread3.start();
}
}