JAVA高级之线程安全问题

线程安全问题

一、线程的创建方式
第一种方式:继承Thread类的方式:

  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
  3. 创建Thread类的子类的对象
  4. 通过此对象调用start():①启动当前线程 ② 调用当前线程的run()

第二种方式:实现Runnable接口的方式:

  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
  3. 创建Thread类的子类的对象
  4. 通过此对象调用start():①启动当前线程 ② 调用当前线程的run()

第三种方式:实现Callable接口

  1. 创建一个实现Callable的实现类
  2. 实现call方法,将此线程需要执行的操作声明在call()中
  3. 创建Callable接口实现类的对象
  4. 将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
  5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
  6. 获取Callable中call方法的返回值
    如图:
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
     
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
     
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
     
            if(i % 2 == 0){
     
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}


public class ThreadNew {
     
    public static void main(String[] args) {
     
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();

        try {
     
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        } catch (ExecutionException e) {
     
            e.printStackTrace();
        }
    }

}

第四种方法:使用线程池创建
此方法见其他文章单独说明

二、线程安全的解决:
方式一:同步代码块
1.操作共享数据的代码,即为需要被同步的代码。 -->不能包含代码多了,也不能包含代码少了。
2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。

①:当使用继承Thread类创建的线程时:方法中的变量应定义为静态变量,且做同步锁的类必须为静态类,因为此时当你创建多个线程是,每创建一个线程的同时会产生一个类,为了使所有线程均使用同一把锁,则需要设置为静态,类中的变量也是如此。
如图:三个窗口实现卖票

package com.cn.java1;



class Window3 extends Thread{
     
    private static int ticket = 100;
    private static Object  obj = new Object();

    @Override
    public void run() {
     
        while (true) {
     
            synchronized (obj/Window3.class) {
     //这里不能为this,this表示当前类的对象,而此时当前类的对象不同。 Window3.class表示类本身
                if (ticket > 0) {
     
                    try {
     
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
     
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
     
                    break;
                }
            }
        }
    }
}


public class WindowTest3 {
     
    public static void main(String[] args) {
     
        Window3 t1 = new Window3();
        Window3 t2 = new Window3();
        Window3 t3 = new Window3();
        t1.start();
        t2.start();
        t3.start();
        t1.setName("窗口1:");
        t2.setName("窗口2:");
        t3.setName("窗口3:");

    }
}

②Runnable接口来创建多线程,此时因为所有线程均使用同一个类,所以此时不需要使用静态变量和方法,如图:

class Window1 implements Runnable {
     
    private int ticket = 100;
    Object obj = new Object();

    @Override
    public void run() {
     
        while (true) {
     
            synchronized (obj) {
       //此时的同步锁也可使用this,表示当前类的对象,因为全部均使用同一个类
                if (ticket > 0) {
     
                    try {
     
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
     
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                    ticket--;
                } else {
     
                    break;
                }
            }
        }
    }
}

public class WindowTest1 {
     
    public static void main(String[] args) {
     
        Window1 w = new Window1();  //这里便是三个线程使用的同一类
        Thread s1 = new Thread(w);
        Thread s2 = new Thread(w);
        Thread s3 = new Thread(w);
        s1.setName("窗口1");
        s2.setName("窗口2");
        s3.setName("窗口3");
        s1.start();
        s2.start();
        s3.start();
    }
}

方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。

关于同步方法的总结:

  1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
  2. 非静态的同步方法,同步监视器是:this
    静态的同步方法,同步监视器是:当前类本身

①此时直接在方法下使用synchronized即可,同步监视器为this,即当前类的对象,三个窗口线程均使用w,所以相同

package com.cn.java1;

class Window2 implements Runnable {
     
    private static int ticket = 100;
    Object obj = new Object();

    @Override
    public void run() {
     
        while (true) {
     
            show();
            }
        }

    private synchronized void show(){
      //同步监视器 为this即t1,t2,t3
        if (ticket > 0) {
     
            try {
     
                Thread.sleep(10);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
        }
    }
}



public class WindowTest2 {
     
    public static void main(String[] args) {
     
        Window1 w = new Window1();
        Thread s1 = new Thread(w);
        Thread s2 = new Thread(w);
        Thread s3 = new Thread(w);
        s1.setName("窗口1");
        s2.setName("窗口2");
        s3.setName("窗口3");
        s1.start();
        s2.start();
        s3.start();
    }
}

②此时必须将同步的方法设置为静态方法,因为每个线程对应的类不同

package com.cn.java1;



class Window3 extends Thread{
     
    private static int ticket = 100;
//    private static Object  obj = new Object();

    @Override
    public void run() {
     
        while (true) {
     
            show();
        }
    }

    private static synchronized void show(){
      //同步监视器 t1,t2,t3
        if (ticket > 0) {
     
            try {
     
                Thread.sleep(10);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
            ticket--;
        }
    }
}


public class WindowTest3 {
     
    public static void main(String[] args) {
     
        Window3 t1 = new Window3();
        Window3 t2 = new Window3();
        Window3 t3 = new Window3();
        t1.start();
        t2.start();
        t3.start();
        t1.setName("窗口1:");
        t2.setName("窗口2:");
        t3.setName("窗口3:");

    }
}

方式三:Lock(锁) 注意当定义为true时,每个线程依次执行,当默认不填或为false时,线程和上述执行相同,谁抢到谁先执行

以下为Runnable接口实现线程,如果为继承时,ReentrantLock应定义为静态类。

package com.cn.java2;

import java.util.concurrent.locks.ReentrantLock;

class Window implements Runnable {
     
    private int ticket = 100;
    private ReentrantLock lock = new ReentrantLock(); //注意当定义为true时,每个线程依次执行,当默认不填或为false时,线程和上述执行相同,谁抢到谁先执行

    @Override
    public void run() {
     
        while (true) {
     
            try {
     
                //调用lock锁
                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();
            }
        }
    }
}

public class LockTest {
     
    public static void main(String[] args) {
     
        Window w = new Window();
        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}


三、synchronized与Lock的异同?
相同:二者都可以解决线程安全问题
不同:synchronized机制在执行完相应的同步代码以后,自动释放同步监视器。
Lock需要手动的启动同步(Lock()),同时结束同步也需要手动的实现(unlock())

四、建议会用顺序
Lock -> 同步代码块 -> 同步方法

就到这里啦,谢谢大家❥(^_-)

你可能感兴趣的:(java高级编程,java)