Java多线程基础(二)

线程的生命周期

1.源码中定义的生命周期
在这里插入图片描述
源码中的状态定义的界限不是很清楚,那么通常一个线程的完整生命周期需要经历五种状态。
1.新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程处于新建状态
2.就绪:处于新建状态的线程被start()后,将进入线程列表等待cpu的时间片,此时它已经有了运行的条件,只是还没有得到cpu的资源。
3.运行:当就绪的线程被调度并获取cpu执行权时,便进入了运行的状态,run()方法中就定义了线程要完成的操作和功能。
4.阻塞:在某种特殊的情况下,被认为的挂起或是等待程序的输入输出,让出cpu并且临时终止自己的执行,进入阻塞状态
5.死亡:线程完成了它的全部工作、线程被提前强制终止或者线程发送了错误或异常并且未处理异常
在这里插入图片描述

线程的同步

问:为什么需要线程同步或者为什么需要线程安全呢?
场景:利用多线程卖票窗口

class Window2Test implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":买票,号:" + ticket);
                    ticket--;
                } else {
                    break;
                }
        }
    }
}

public class Window2 {
    public static void main(String[] args) {
        Window2Test test = new Window2Test();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        Thread t3 = new Thread(test);
        t1.setName("1");
        t2.setName("2");
        t3.setName("3");
        t1.start();
        t2.start();
        t3.start();
    }
}

执行结果:
在这里插入图片描述
可以看到以上两种情况一个是错票,一个是重票,这些都是不合法的结果
那么造成这样结果的原因是什么呢?
多条语句在操作同一线程的共享数据时,一条线程对多条语句还未执行完,另外一条线程也参与进来,导致了共享数据的错误。
解决方法:
对于多条操作共享数据的语句,当一条线程正在执行时,其他线程不能参与进来,能等待该线程执行完毕,其他线程才能参与

1.方式一:使用同步代码块

 synchronized(同步监视器){
      //需要同步的代码
}

说明:
①同步监视器:俗称:锁,任何一个类的对象,都可以充当同步监视器,要求多个线程必须共用一个同步监视器。
②同步代码:多个线程共同操作的变量,例如卖票时的票数
③同步代码:操作共享数据时的代码
2.方式二:同步方法,在方法上加上synchronized的修饰
3.方式三:Lock() 锁—JDK5.0新增

线程安全的实例

1.在继承Thread类创建线程的方式中使用同步代码块

public class Window1 {
    public static void main(String[] args) {
        WindowTest1 window1 = new WindowTest1();
        WindowTest1 window2 = new WindowTest1();
        WindowTest1 window3 = new WindowTest1();
        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");
        window1.start();
        window2.start();
        window3.start();
    }
}

class WindowTest1 extends Thread{
    private static int ticket =  100; //方式一:使用static 控制同一份买票数量
    private static Object obj = new Object();
    @Override
    public void run() {
        //错误
        //synchronized(this) {
        //正确
//        synchronized(WindowTest1.class){
        //正确
        synchronized(obj){
            while (true) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName() + ":买票,号码:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

说明:
在Window1类中我们声明了三个Thread子类对象,也就是创建了三个新线程。因此需要对Thread子类的run方法做加锁处理。
使用同步代码块的方式,我们需要在synchronized中传入一个对象

synchronized(obj){
    //。。。
}

这个对象可以是任何类的对象,那么有一个要求就是传入的对象对于三个线程来说是同一个,为了实现这个要求,我们可以将Object类声明为static,那么对于三个线程来说就是同一个类对象了,
此时就达到了我们要求的同步代码的效果。
那么,除了传入一个类的对象,还有没有别的方式呢?这里我给出了另外一种方式,就是传入WindowTest1.class,传入一个类,类也是对象所以可以传入当前类本身。

2.在实现Runnable接口方式中使用同步代码块

class Window2Test implements Runnable{
    private int ticket = 100;
    Object obj = new Object();
    @Override
    public void run() {
        while (true){
             synchronized(this){ //this当前对象 // synchronized(obj) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":买票,号:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

public class Window2 {
    public static void main(String[] args) {
        Window2Test test = new Window2Test();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        Thread t3 = new Thread(test);
        t1.setName("1");
        t2.setName("2");
        t3.setName("3");
        t1.start();
        t2.start();
        t3.start();
    }
}

同样这里也要保证同步监听器的要求,传入的对象必须是线程所共用的。
Window2Test 子类并没有在main方法中被实例多次,因此,我们可以在Window2Test中实例一个Object类对象,传入同步代码块的参数中,作为监视器。
另外,还可以传入this,这里this指代的就是当前类的对象,当然也可以传入当前类Window2Test.class

3.在继承Thread类创建线程的方式中使用同步方法

public class Window4 {
    public static void main(String[] args) {
        WindowTest4 window1 = new WindowTest4();
        WindowTest4 window2 = new WindowTest4();
        WindowTest4 window3 = new WindowTest4();
        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");
        window1.start();
        window2.start();
        window3.start();
    }
}

class WindowTest4 extends Thread{
    private static int ticket =  100; //方式一:使用static 控制同一份买票数量
    @Override
    public void run() {
        while (true){
           show();
        }
    }
    //private synchronized void show(){ 错误 当前锁对象是 window1 window2 window3
    private synchronized static void show(){ //此时锁 对象是WindowTest4.class
        if (ticket>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":买票,号码:"+ticket);
            ticket--;
        }
    }
}

这里唯一需要注意的一点就是synchronized修饰的方法必须要声明整静态,因为,这里虽然没有要求我们传入同步监视器,实际上是Java帮我们加上的,属于隐式写法。这时它传入的其实就是类本身。
4.在实现Runnable接口方式中使用同步方法

class Window2Test3 implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
           show();
        }
    }

    public synchronized void show(){
        if (ticket>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":买票,号:"+ticket);
            ticket--;
        }
    }
}

public class Window3 {
    public static void main(String[] args) {
        Window2Test3 window2Test = new Window2Test3();
        Thread thread1 = new Thread(window2Test);
        Thread thread2 = new Thread(window2Test);
        Thread thread3 = new Thread(window2Test);
        thread1.setName("1");
        thread2.setName("2");
        thread3.setName("3");
        thread1.start();
        thread2.start();
        thread3.start();

    }
}

这种方式使用同步方法就简单了,其实它隐式传入的同步监视器就是this了

5.在继承Thread的方式中使用Lock()
我们使用Lock()这种方式时,其实使用的时它的子类ReentrantLock

public class LockTest2 {
    public static void main(String[] args) {
        Window2 window1 = new Window2();
        Window2 window2 = new Window2();
        Window2 window3 = new Window2();
        window1.setName("窗口1");
        window2.setName("窗口2");
        window3.setName("窗口3");
        window1.start();
        window2.start();
        window3.start();
    }
}


class Window2 extends Thread{
    private static int ticket = 100;
    private static ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {

        while (true){
            try {
                //加锁
                lock.lock();
                if (ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":售票:"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }finally {
                //解锁
                lock.unlock();
            }

        }
    }
}

这里代码也比较简单,这种方式在于手动加锁和解锁。

这就是三种线程同步的方式,也可以说是两种,synchronized 和Lock()
那么这两种有什么对比
1.Lock时显示锁(手动开启和关闭锁),synchronized时隐式锁,出了作用域自动释放
2.Lock只有代码块锁,synchronized有代码块和方法锁
3.使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:Lock→同步代码块(已经进入了方法体,分配了相应资源)→同步方法(在方法体之外)

线程死锁问题

死锁:在不同的线程分别占用对方需要的同步资源不放弃,都在等待对方先放弃自己需要的同步资源,就形成了线程的死锁问题。
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

解决方法

1.专门的算法、原则
2.尽量减少同步资源的定义
3.尽量避免嵌套同步
死锁举例

class A {
   public synchronized void foo(B b) {
      System.out.println("当前线程名: " + Thread.currentThread().getName()
            + " 进入了A实例的foo方法"); // ①
      try {
         Thread.sleep(200);
      } catch (InterruptedException ex) {
         ex.printStackTrace();
      }
      System.out.println("当前线程名: " + Thread.currentThread().getName()
            + " 企图调用B实例的last方法"); // ③
      b.last();
   }

   public synchronized void last() {
      System.out.println("进入了A类的last方法内部");
   }
}

class B {
   public synchronized void bar(A a) {
      System.out.println("当前线程名: " + Thread.currentThread().getName()
            + " 进入了B实例的bar方法"); // ②
      try {
         Thread.sleep(200);
      } catch (InterruptedException ex) {
         ex.printStackTrace();
      }
      System.out.println("当前线程名: " + Thread.currentThread().getName()
            + " 企图调用A实例的last方法"); // ④
      a.last();
   }

   public synchronized void last() {
      System.out.println("进入了B类的last方法内部");
   }
}

public class DeadLock implements Runnable {
   A a = new A();
   B b = new B();

   public void init() {
      Thread.currentThread().setName("主线程");
      // 调用a对象的foo方法
      a.foo(b);
      System.out.println("进入了主线程之后");
   }

   public void run() {
      Thread.currentThread().setName("副线程");
      // 调用b对象的bar方法
      b.bar(a);
      System.out.println("进入了副线程之后");
   }

   public static void main(String[] args) {
      DeadLock dl = new DeadLock();
      new Thread(dl).start();
      dl.init();
   }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值