• 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁


    什么是同步

    • 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条线程访问,一条线程在执行一个循环的过程中被中断,下一个线程则出现错误
    • 因此,线程任务中可能引起错误的地方应当被一次执行完毕

    同步代码块

    • 用同步代码块改写上面的代码
    package testpack;
    
    public class Test1  { 
        public static void main(String[] args){ 
        	System.out.println("现在是主线程: "+Thread.currentThread()); 
        	System.out.println("下面新建两个线程");
        	A a=new A(500); 
        	new Thread(a,"线程A").start(); 
        	new Thread(a,"线程B").start();
        	new Thread(a,"线程C").start();
        }
    }
    class A implements Runnable{
    	private int tickets;
    	A (int tick){
    		tickets=tick;
    	}
    	private Object obj=new Object();              //同步监视器
    	public void run() {
    		synchronized(obj){                        //同步代码块
    			for (;tickets>0;tickets--) {
    				System.out.println("当前线程:"+Thread.currentThread()+" 卖出第 "+tickets+" 张票。");
    				try{
    					Thread.sleep(1);              //让当前线程暂停1毫秒,其他线程也不能执行该同步代码块
    				}catch(InterruptedException ex){
    					ex.printStackTrace();
    				}
    				if (tickets==1){
    					System.out.println("票已卖完,当前线程是: "+Thread.currentThread());
    				}
    			}
    		}
    		
    	}
    }
    
    • 同步监视器,就是一个普通的对象,就像一把锁,只有获得了同步监视器的线程才能执行同步代码块。
    • 同步代码块执行一次完毕后,将会释放锁,接下来是这条线程拿到同步锁,还是其他其他线程,则不一定,根据线程调度而定,但是在同步代码块执行过程中,不会被中断,一个同步任务会被一次执行完毕

    同步方法

    • 在同步代码块中,synchonized关键字在run()方法内部,修饰的是一段代码,也可以用来修饰run()方法,也就是同步方法
    • synchronized不只可以修饰run()方法,还可以修饰其他方法,只要是需要一次同步完成的任务,然后再在run()方法中被调用
    • 同步方法中有一个隐式的同步监视器,就是this,也就是调用run()方法(或同步方法)的这个对象
    • 还是上面的实例,用同步方法改写
    package testpack;
    
    public class Test1  { 
        public static void main(String[] args){ 
        	System.out.println("现在是主线程: "+Thread.currentThread()); 
        	System.out.println("下面新建两个线程");
        	A a=new A(500);
        	new Thread(a,"线程A").start();
        	new Thread(a,"线程B").start();
        	new Thread(a,"线程C").start();
        }
    }
    class A implements Runnable{
    	private int tickets;
    	A (int tick){
    		tickets=tick;
    	}
    	public synchronized void run() {                                    //同步方法
    			for (;tickets>0;tickets--) {
    				System.out.println("当前线程:"+Thread.currentThread()+" 卖出第 "+tickets+" 张票。");
    				try{
    					Thread.sleep(1);
    				}catch(InterruptedException ex){
    					ex.printStackTrace();
    				}
    				if (tickets==1){
    					System.out.println("票已卖完,当前线程是: "+Thread.currentThread());
    				}
    			}
    		
    	}
    }
    

    释放同步监视器

    • 当前线程的同步任务(同步方法、同步代码块)执行完毕
    • 在同步任务中,遇到break、return,终止了同步任务
    • 在同步任务中,出现Error、Exception等,导致同步任务结束
    • 在同步任务中,执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器
    • 不会释放同步监视器的情况:
      • 同步任务中,调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行
      • 同步任务中,其他线程调用了该线程的suspend()方法将该线程挂起

    同步锁

    • 除了可以用new Object()和this作同步监视器往外,还可以定义专门的同步锁,且功能更加强
    • Lock接口
      • ReentrantLock实现类
    • ReadWriteLock
      • ReentrantReadWriteLock实现类
      • ReentrantReadWriteLock.ReadLock
      • ReentrantReadWriteLock.WriteLock
    • StampedLock
    • 示例:用ReentrantLock改写上面的代码
    package testpack;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Test1  { 
        public static void main(String[] args){ 
        	System.out.println("现在是主线程: "+Thread.currentThread()); 
        	System.out.println("下面新建两个线程");
        	A a=new A(50);
        	new Thread(a,"线程A").start();
        	new Thread(a,"线程B").start();
        	new Thread(a,"线程C").start();
        }
    }
    class A implements Runnable{
    	private final ReentrantLock lock=new ReentrantLock();    //定义一个同步锁
    	private int tickets;
    	A (int tick){
    		tickets=tick;
    	}
    	public void run() {
    			lock.lock();                                     //加锁
    			for (;tickets>0;tickets--) {
    				System.out.println("当前线程:"+Thread.currentThread()+" 卖出第 "+tickets+" 张票。");
    				if (tickets==1){
    					System.out.println("票已卖完,当前线程是: "+Thread.currentThread());
    				}
    			}
    			lock.unlock();                                    //释放锁
    	}
    }
    

    死锁

    • 两个线程各拿一把锁,下一步运行都需要对方手里那把锁,但都拿不到,则造成死锁,程序不能继续执行
    package testpack;
    public class Test1  { 
        public static void main(String[] args){ 
        	DeadLock dl=new DeadLock();
        	new Thread(dl).start();
        	dl.init();
        }
    }
    class DeadLock implements Runnable {
    	A a=new A();
    	B b=new B();
    	public void init(){
    		a.a1(b);
    		System.out.println("进入主线程");
    	}
    	public void run(){
    		b.b1(a);
    		System.out.println("进入子线程");
    	}
    }
    class A {
    	public synchronized void a1(B b){
    		System.out.println("当前线程是:"+Thread.currentThread().getName()+" ,正在执行a1()");
    		try{
    			Thread.sleep(10);
    		}catch(InterruptedException ex){
    			ex.printStackTrace();
    		}
    		System.out.println("当前线程是:"+Thread.currentThread().getName()+" ,准备调用b2()");
    		b.b2();   //b2方法是同步方法,调用该方法要对调用的对象b加锁
    	}
    	public synchronized void a2(){
    		System.out.println("这是a2()方法");
    	}
    }
    class B{
    	
    	public synchronized void b1(A a){
    		System.out.println("当前线程是:"+Thread.currentThread().getName()+" ,正在执行b1()");
    		try{
    			Thread.sleep(10);
    		}catch(InterruptedException ex){
    			ex.printStackTrace();
    		}
    		System.out.println("当前线程是:"+Thread.currentThread().getName()+" ,准备调用a2()");
    		a.a2();   //a2方法是同步方法,调用该方法要对调用的对象a加锁
    	}
    	
    	public synchronized void b2(){
    		System.out.println("这是b2()方法");
    	}
    }
    
    • 上面在调用a.a2()和b.b2()方法时,分别要对a对象和b对象加锁,但这时,a、b对象的锁都在对方手里,造成两个线程阻塞

    其他

    • 可变类的线程安全是以降低程序的运行效率为代价的
    • 不要对线程安全类的所有方法都进行同步,只对那些改变共享资源的方法进行同步
    • 如果一个类有单线程和多线程运行环境,那么应该提供两种版本,就是StringBuilder(单线程)和StringBuffer(多线程)一样
  • 相关阅读:
    FFmpeg 结构体学习(八):FFMPEG中重要结构体之间的关系
    FFmpeg 结构体学习(七): AVIOContext 分析
    FFmpeg 结构体学习(六): AVCodecContext 分析
    FFmpeg 结构体学习(五): AVCodec 分析
    FFmpeg 结构体学习(四): AVFrame 分析
    FFmpeg 结构体学习(三): AVPacket 分析
    Android 通过onTouchEvent判断是否为双击事件
    FFmpeg 结构体学习(二): AVStream 分析
    Java 使用 int 数据计算百分比
    Android 关于解决MediaButton学习到的media控制流程
  • 原文地址:https://www.cnblogs.com/sonng/p/6134444.html
Copyright © 2020-2023  润新知