JAVA多线程学习(二)

一、synchronized关键字

上文的那张图里有个synchronized,现在来讲讲这个关键字。

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
3. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4. 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

修饰一个代码块:一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞

public class MyThread extends Thread {
    
    public MyThread(String name){
        super(name);
    }
    
    @Override
    public void run() {
        synchronized(MyThread.class){
            for (int i = 0; i <5; i++) {
                System.out.println(getName()+":"+i);
            }
            
        }
    }
}

public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1=new MyThread("令狐冲");
        MyThread my2=new MyThread("任盈盈");
        
        my1.start();
        my2.start();
    }
}

令狐冲:0
令狐冲:1
令狐冲:2
令狐冲:3
令狐冲:4
任盈盈:0
任盈盈:1
任盈盈:2
任盈盈:3
任盈盈:4

两个并发线程访问同一个对象中的同步代码快时,同一时刻只能只有一个线程得到执行,另一个线程被阻塞,必须等到当前线程执行完同步代码块的内容后才会释放锁,第二个线程才能执行并锁定该对象。此外,run()方法假如还有其他非同步代码块,两个线程是可以互相争斗执行权的。

public class MyThread extends Thread {
    
    public MyThread(String name){
        super(name);
    }
    
    @Override
    public void run() {
        synchronized(MyThread.class){
            for (int i = 0; i <5; i++) {
                System.out.println(getName()+":"+i);
            }
            
        }
        System.out.println(Thread.currentThread().getName()+":这是非同步代码");
    }
}

令狐冲:0
令狐冲:1
令狐冲:2
令狐冲:3
令狐冲:4
任盈盈:0
令狐冲:这是非同步代码
任盈盈:1
任盈盈:2
任盈盈:3
任盈盈:4
任盈盈:这是非同步代码

 修饰一个方法:Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。

public synchronized void method(){
   // todo
}
public void method(){
   synchronized(this) {
      // todo
   }
}

修饰一个静态方法:静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象

public synchronized static void method() {
   // todo
}

修饰一个类:

class ClassName {
   public void method() {
      synchronized(ClassName.class) {
         // todo
      }
   }
}

总结:

1. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
2. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
3. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

 

二、等待线程和唤醒线程

学习了synchronized关键字,我们回过头来看wait(). 让当前线程进入等待状态,同时,wait()会让当前线程释放它所持有的锁。它一般跟notify()或 notifyAll() 方法配合使用。

public class ThreadWait extends Thread {
    public ThreadWait(String name){
        super(name);
    }
    @Override
    public void run() {
        synchronized(this){
            try {
                //使当前线阻塞 1 s,确保主程序的 t1.wait(); 执行之后再执行 notify()
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":打篮球");
            this.notify();
        }

    }


public class ThreadWaitDemo {
    public static void main(String[] args) {
        ThreadWait t1=new ThreadWait("姚明");
        synchronized(t1){
            try {
                //启动线程
                System.out.println(Thread.currentThread().getName()+"线程启动。。");
                t1.start();
                //主线程等待t1通过notify()唤醒。
                System.out.println(Thread.currentThread().getName()+"线程等待。。");
                t1.wait();//不是使t1线程等待,而是当前执行wait的线程等待
                System.out.println(Thread.currentThread().getName()+"线程继续执行。。");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
测试结果:
main线程启动。。
main线程等待。。
姚明:打篮球
main线程继续执行。。

sleep()与wait()的区别:

1,sleep方法是Thread类的静态方法,wait()是Object超类的成员方法。

2,sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。在调用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备

3,sleep方法需要抛异常,wait方法不需要。

public static void sleep(long millis)
                  throws InterruptedException

参数:millis - 以毫秒为单位的休眠时间。
抛出:InterruptedException - 如果任何线程中断了当前线程。当抛出该异常时,当前线程的中断状态 被清除。

Thread类中sleep方法就已经进行了抛异常处理。

 三、Lock锁

Lock锁是JDK5出现的针对线程的锁定操作和释放操作的接口。synchronized同步代码块,同步方法属于隐式锁;而Lock锁需要通过lock()方式上锁,必须通过unlock()方式进行释放锁,所以称之为显式锁。

//电影院买票
public class ThreadTicket implements Runnable{
    private int ticket=10;
    //创建锁对象
    private Lock lock=new ReentrantLock();
    @Override

public void run() {

while(true){

  lock.lock();//加锁
  try{
    if(ticket>0){
      try {
        Thread.sleep(200);
      } catch (InterruptedException e) {

        e.printStackTrace();
      }
    System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"张票");
    }
  }finally{
    lock.unlock();//释放锁
    }
  }
}

}

public class TicketDemo {
    public static void main(String[] args) {
        ThreadTicket tt=new ThreadTicket();
        Thread t1=new Thread(tt,"窗口一");
        Thread t2=new Thread(tt,"窗口二");
        Thread t3=new Thread(tt,"窗口三");
        
        t1.start();
        t2.start();
        t3.start();
    }
    
}
测试结果:
窗口一正在出售第10张票
窗口一正在出售第9张票
窗口二正在出售第8张票
窗口二正在出售第7张票
窗口二正在出售第6张票
窗口二正在出售第5张票
窗口二正在出售第4张票
窗口二正在出售第3张票
窗口三正在出售第2张票
窗口三正在出售第1张票

 四、死锁问题

线程同步解决了线程的安全问题,但是每次访问资源都会去判断锁,降低了效率,同时还会发生死锁问题。

死锁就是两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象。

public class ThreadDied extends Thread {
    //定义两个锁对象
    private static Object A=new Object();
    private static Object B=new Object();
    
    private boolean flag;
    
    public ThreadDied(boolean flag) {
        super();
        this.flag = flag;
    }

    @Override
    public void run() {
        if(flag){
            synchronized(A){
                System.out.println("农夫A要喝酒才有力气种粮食");
                synchronized(B){
                    System.out.println("农夫A有粮食了");
                }
            }
        }else{
            synchronized(B){
                System.out.println("农夫B有粮食才能酿酒");
                synchronized(A){
                    System.out.println("农夫B酿酒了");
                }
            }
            
        }
    }
}

public class ThreadDiedDemo {
    public static void main(String[] args) {
        ThreadDied t1=new ThreadDied(true);
        ThreadDied t2=new ThreadDied(false);
        
        t1.start();
        t2.start();
    }
}
测试结果:
农夫A要喝酒才有力气种粮食
农夫B有粮食才能酿酒

 

posted @ 2019-03-26 17:53  一棵写代码的柳树  阅读(119)  评论(0)    收藏  举报