Java多线程3 线程的同步

/*
Java多线程: 线程的生命周期与线程安全

一,线程的分类
Java中的线程分为两类:一种是守护线程,一种是用户线程。 它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
Java垃圾回收和异常处理就是典型的守护线程,main方法时用户线程,若JVM中都是守护线程,当前JVM将退出。

二,线程的状态
JDK中用Thread.State类(Thread中的内部枚举类)定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,
在它的一个完整的生命周期中通常要经历如下的五种状态:
1新建(NEW):当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
2就绪(RUNNABLE):处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
3运行(RUNNABLE):当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
4阻塞(BLOCKED WAITING TIMED_WAITING):在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
5死亡(TERMINATED):线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

三,线程状态转换涉及到的方法
1,使用new 构造器()新建一个Thread类或者其子类的实例对象,
2,新建状态到就绪状态:start()方法。
3,就绪状态到运行状态:cpu分配资源。
4,运行状态回到就绪状态:失去cpu资源或主动使用yield()方法。
5,运行状态到死亡状态:1run()方法执行完毕,2程序出现error或未处理的Exception,3主动调用stop()方法。
6,运行状态到阻塞状态:1,其他线程调用join()参与此线程,2调用sleep()方法,3调用wait()方法,4等待同步锁时,5调用suspend()方法
7,阻塞状态到就绪状态:2,调用join()方法的线程执行结束,2sleep()到时,3notify()或notifyAll(),4获得同步锁,5resume()方法

四,线程安全问题
出现原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行,即使当前线程出现阻塞,共享数据也不能被改变

Java对于多线程的安全问题提供了解决方式:同步机制,使用synchronized关键字声明方法或代码块

  1. 同步代码块:
    synchronized (同步监视器){
    // 需要被同步的代码;
    操作共享数据的代码既是需要被同步的代码,
    多个线程共同操作的数据就是共享数据。
    同步监视器就是锁,任何类的对象都可以是锁,但是要求多线程共用一把锁。
    实现Runnable接口的类中可以使用this来当锁,但继承Thread类的子类中
    慎重使用this来当锁,因为锁不唯一。
    }
  2. synchronized还可以放在方法声明中,表示整个方法为同步方法。
    如果操作共享数据的语句都在一个方法体中,可以选择将此方法声明为synchronized。
    public synchronized void show (String name){
    // 需要被同步的代码;
    }

五 同步锁机制:
在《Thinking in Java》中说:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。
防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,
使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。
5.1 synchronized的锁是什么?
任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
同步方法的锁:没有显示指定,默认静态方法(类名.class)、非静态方法(this),
同步代码块:自己指定,很多时候也是指定为this或类名.class

5.2 注意:
必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就无法保证共享资源的安全
一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)

5.3 释放锁的操作
 当前线程的同步方法、同步代码块执行结束。
 当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行。
 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

5.4 不会释放锁的操作
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器)。
注意:应尽量避免使用suspend()和resume()来控制线程

5.5同步的范围
1、如何找问题,即代码是否存在线程安全
(1)明确哪些代码是多线程运行的代码
(2)明确多个线程是否有共享数据
(3)明确多线程运行代码中是否有多条语句操作共享数据
2、如何解决
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行。即所有操作共享数据的这些语句都要放在同步范围中
3、如果范围太小:没锁住所有有安全问题的代码,范围太大:没发挥多线程的功能。

5.6 同步机制的优缺点:
1,优点:解决了线程安全问题
2,缺点:操作同步代码时,只能有一个线程参与,其他线程阻塞,相当于单线程,运行时间变长,效率降低

*/

package leanthread;

import com.sun.org.apache.xpath.internal.objects.XNumber;

/**
 * @Description:
 * @Version:1.0.0.1
 * @Date:2020--03--06--21:57
 * @Author:wisdomcodeinside
 * @E-mail:[email protected]
 */
public class ThreadTest3 {
     
    public static void main(String[] args) {
     
        //测试Runnable实现类中使用同步代码块解决同步问题
        SynchronizedWindow s = new SynchronizedWindow();
        Thread Window1 = new Thread(s);
        Thread Window2 = new Thread(s);
        Thread Window3 = new Thread(s);
        Window1.setName("卖票窗口1");
        Window1.start();
        Window2.setName("卖票窗口2");
        Window2.start();
        Window3.setName("卖票窗口3");
        Window3.start();
        //测试Thread继承子类中使用同步代码块解决同步问题

        for (int i = 0; i < 5; i++) {
     
            new People(i + "号线程").start();
        }
        //测试Runnable实现类中使用同步方法解决同步问题
        TenMultiple t = new TenMultiple();
        Thread find1 = new Thread(t, "查询线程1");
        Thread find2 = new Thread(t, "查询线程2");
        Thread find3 = new Thread(t, "查询线程3");
        find1.start();
        find2.start();
        find3.start();
        //测试Thread继承子类中使用同步方法解决同步问题
        for (int i = 0; i < 5; i++) {
     
            new EatFood(i + "号吃货线程").start();
        }

    }
}

//Runnable实现类中使用同步代码块
class SynchronizedWindow implements Runnable {
     
    private int ticket = 20;//线程共用一个对象,不用加上static
    Object obj = new Object();

    @Override
    public void run() {
     
//      Object obj = new Object();//不能在此处造锁,会破坏唯一性
        while (true) {
     
            synchronized (obj) {
     //或者填入this,使用当前类的对象作为锁,因为使用的时候只创建一个对象,可以保证锁的唯一性
                if (ticket > 0) {
     
                    try {
     
                        Thread.sleep(100);//增加阻塞状态,提高非同步情况下的重票几率
                    } catch (InterruptedException e) {
     
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出一张票," + "票号为:" + ticket);
                    ticket--;
                } else {
     
                    System.out.println("票已卖完,谢谢关照");
                    break;
                }
            }
        }
    }
}

//Thread继承子类中使用同步代码块
class People extends Thread {
     
    //private static Object obj = new Object();//也可以使用静态属性,保证类的所有对象共用一把锁
    @Override
    public void run() {
     
        synchronized (People.class) {
     //或者填入静态属性obj
            //Java中的类也是一个对象,是Class 类的对象,类只会被类加载器加载一次,所以可以保证锁的唯一性
            //使用类名.class的方式调用类对象
            System.out.println(Thread.currentThread().getName() + "天仙下凡");
            try {
     
                sleep(100);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "人间失格");
            System.out.println(Thread.currentThread().getName() + "驾鹤西游");
        }
    }

    public People(String name) {
     
        super(name);
    }
}

//Runnable实现类中使用同步方法
class TenMultiple implements Runnable {
     
    private int number = 100;

    @Override
    public void run() {
     
        while(true) {
     
            FIndNumber();
            if(number <= 0){
     
                break;
            }
        }
    }

    public synchronized void FIndNumber() {
     

        if (number % 10 == 0 && number != 0) {
     
            System.out.println(Thread.currentThread().getName() + "找到一个10的倍数" + number);
        }
        try {
     
            Thread.sleep(100);
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }
        number--;
    }
}

//Thread继承子类中使用同步方法
class EatFood extends Thread{
     
    private static int number = 20;
    @Override
    public void run() {
     
        while(true) {
     
            eat();
            if (number <= 0){
     
                break;
            }
        }
    }
    public synchronized static void eat(){
     
        if(number > 0) {
     
            System.out.println(Thread.currentThread().getName() + "食用了第" + (21 - number) + "份大碗宽面");
            try {
     
                sleep(100);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            number--;
        }else{
     
            System.out.println("面已经吃完了");
        }
    }

    public EatFood(String name) {
     
        super(name);
    }
}
//线程安全的单例懒汉式
class Single{
     
    private static Single single = null;
    private Single(){
     

    }
    //方式一,直接将方法声明为static synchronized.
//    public static synchronized  Single getInstance(){
     
//        if(single == null){
     
//            single = new Single();
//        }
//          return single;
//    }

    //方式二效率更好
    public static Single getInstance(){
     
        if(single == null) {
     //如果已经有了对象则不会进入同步代码块,直接返回对象
            synchronized (Single.class) {
     
                if (single == null) {
     //同步里面必须再做一次判断,一开始可能有多个线程进行到此处
                    //如果不加判断,后面的线程依然会创建新对象。
                    single = new Single();
                }
            }
        }
        return single;
    }
}

你可能感兴趣的:(java,学习笔记,java)