JAVA_基础多线程的生命周期、线程安全、线程锁、线程通信与创建线程(二)

线程的同步

同步代码块实现:继承Thread线程安全问题

① 操作共享数据的代码,即为需要被同步的代码。(不能包含代码多了,也不能包含代码少了)
② 共享数据:多个线程共同操作的变量。比如:ticket就是共享数据。
③ 同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。
要求:多个线程必须要共用同一把锁。
补充:在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当同步监视器。

class RWindow2 extends Thread {
    public static int ticket = 100;
    private static Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            //正确的
//            synchronized (obj){
            synchronized (RWindow2.class) {//Class clazz = Window2.class,Window2.class只会加载一次
//            synchronized (this) {//错误的方式:this代表着t1,t2,t3三个对象
                if (ticket > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class WindowTest2 {
    public static void main(String[] args) {
        RWindow2 rWindow1 = new RWindow2();
        RWindow2 rWindow2 = new RWindow2();
        RWindow2 rWindow3 = new RWindow2();
        rWindow1.setName("窗口1");
        rWindow2.setName("窗口2");
        rWindow3.setName("窗口3");
        rWindow1.start();
        rWindow2.start();
        rWindow3.start();
    }
}

同步代码块实现:实现Runnable线程安全问题

class RWindow implements Runnable {
    private int ticket = 100;
//    Object obj = new Object();
    @Override
    public void run() {
        while(true) {
//            synchronized(obj) {//方法一
              synchronized(this) { //方式二:此时的this:唯一的RWindow的对象
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,票号:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class WindowTest1 {
    public static void main(String[] args) {
        RWindow rWindow = new RWindow();
        Thread thread1 = new Thread(rWindow);
        thread1.setName("窗口一");
        thread1.start();
        Thread thread2 = new Thread(rWindow);
        thread2.setName("窗口二");
        thread2.start();
        Thread thread3 = new Thread(rWindow);
        thread3.setName("窗口三");
        thread3.start();
    }
}

同步方法实现:接口Runnable线程安全问题

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

class RWindow2 implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
            show();
        }
    }
    public synchronized void show() {//同步监视器:this
//        synchronized(this) {
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":卖票,票号:" + ticket);
                ticket--;
            }
//        }
    }
}
public class WindowTest2 {
    public static void main(String[] args) {
        RWindow2 rWindow = new RWindow2();
        Thread thread1 = new Thread(rWindow);
        thread1.setName("窗口一");
        thread1.start();
        Thread thread2 = new Thread(rWindow);
        thread2.setName("窗口二");
        thread2.start();
        Thread thread3 = new Thread(rWindow);
        thread3.setName("窗口三");
        thread3.start();
    }
}

同步方法实现:继承Thread线程安全问题

class RWindow3 extends Thread {
    private static int ticket = 100;
    @Override
    public void run() {
        while (true) {
            show();
        }
    }
      private static synchronized void show() {//同步监听器:Window4.class
//    private synchronized void show() {//同步监听器:rWindow1,rWindow2,rWindow3 此种解决方式是错误的
        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) {
        RWindow3 rWindow1 = new RWindow3();
        RWindow3 rWindow2 = new RWindow3();
        RWindow3 rWindow3 = new RWindow3();
        rWindow1.setName("窗口一");
        rWindow1.start();
        rWindow2.setName("窗口二");
        rWindow2.start();
        rWindow3.setName("窗口三");
        rWindow3.start();
    }
}

线程的死锁问题

死锁:

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

解决方法:

  • 专门的算法、原则。
  • 尽量减少同步资源的定义。
  • 尽量避免嵌套同步。
/** 演示死锁问题 */
public class ThreadTest {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        new Thread(){
            @Override
            public void run() {
                synchronized(s1){
                    s1.append("a");
                    s2.append("1");
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2) {
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized(s2){
                    s1.append("c");
                    s2.append("3");
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1) {
                        s1.append("d");
                        s2.append("4");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

Lock(锁)解决线程安全问题

解决线程安全问题方式三:Lock(锁) ---JDK5.0新增

synchronizedlock的异同

相同点:二者都可以解决线程安全问题。

不相同synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器,Lock需要手动的启动同步(lock()),同时结束同步也需要手动实现(unlock())。

建议(优先使用顺序)Lock 同步代码块(已经进入了方法体,分配了相应资源)同步方法 (在方法体之外)。

class Window implements Runnable {
    private int ticket = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock(true);
    @Override
    public void run() {
        while (true) {
            try {
                //2.调用clock()
                lock.lock();
                //2.调用Lock()
                if (ticket > 0) {
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":售票,票号为" + ticket);
                    ticket--;
                } else {
                    break;
                }
            } finally {
                //3.调用解锁方法:unLock
                lock.unlock();
            }
        }
    }
}
public class LockTest {
    public static void main(String[] args) {
        Window window = new Window();
        Thread t1 = new Thread(window);
        Thread t2 = new Thread(window);
        Thread t3 = new Thread(window);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

线程通信

涉及到的三个方法:

  • wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
  • notify():一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。
  • notifyAll():一旦执行此方法,就会唤醒所有被wait的线程。

1)wait()notify()notifyAll()三个方法必须使用在同步代码块或同步方法中。
2)wait()notify()notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常。
3)wait()notify()notifyAll()三个方法是定义在java.lang.Object类中。

sleep() 和 wait()的异同?

相同点:
1) 一旦执行方法,都可以使得当前的线程进入阻塞状态。

不同点:
1)两个方法声明的位置不同:Thread类中声明sleep() , Object类中声明wait()
2)调用的要求不同:sleep()可以在任何需要的场景下调用。 wait()必须使用在同步代码块或同步方法中。
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

class Number {
    private int number = 1;
    private Object obj = new Object();
    public Number(int number) {
        this.number = number;
    }
    public int getNumber() {
        return number;
    }
    // 累加数字
    public void printer(int number){
        synchronized (obj) {
            obj.notify();
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "添加成功。当前数字:" + this.number);
                try {
                    Thread.currentThread().sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.number += number;
                try {
                    //使得调用如下wait() 方法的线程进入阻塞状态
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class Preson implements Runnable {
    private Number number;

    public Preson(Number number) {
        this.number = number;
    }
    @Override
    public void run() {
        while (true) {
            if (this.number.getNumber() <= 100) {
                number.printer(1);
            }else {
                break;
            }
        }
    }
}
public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number(1);
        Preson preson = new Preson(number);
        Thread thread = new Thread(preson);
        thread.setName("甲");
        thread.start();
        Thread thread2 = new Thread(preson);
        thread2.setName("乙");
        thread2.start();
    }
}

JDK5.0新增线程创建方式

新增方式一:实现Callable接口

1)与使用Runnable相比, Callable功能更强大些。
2)相比run()方法,可以有返回值 。
3)方法可以抛出异常。
4)支持泛型的返回值 。
5)需要借助FutureTask类,比如获取返回结果。

class NumberThread 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接口实现类的对象 */
        NumberThread numberThread = new NumberThread();
        /** 4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTaskd的对象 */
        FutureTask futureTask = new FutureTask(numberThread);
        /** 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();
        }
    }
}

如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?

call()可以有返回值。
call()可以抛出异常,被外面的操作捕获,获取异常的信息。
Callable是支持泛型的

新增方式二:使用线程池

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程, 对性能影响很大。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完 放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交 通工具。

好处
1)提高响应速度(减少了创建新线程的时间)。
2)降低资源消耗(重复利用线程池中线程,不需要每次都创建)。
3)便于线程管理 :
corePoolSize:核心池的大小 。
maximumPoolSize:最大线程数 。
keepAliveTime:线程没有任务时最多保持多长时间后会终止。

class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
class NumberThread2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}
public class ThreadPool {
    public static void main(String[] args) {
       /** 1.提供指定线程数量的线程池 */
        ExecutorService service = Executors.newFixedThreadPool(10);
        /** 设置线程池的属性 */
        /** 由于service是ExecutorService接口,需要使用service的实现类对他进行属性设置 */
        System.out.println(service.getClass());//查看service的实现类
        /** 强转方式一: */
        ThreadPoolExecutor service1 = (ThreadPoolExecutor)service;
//        service1.setCorePoolSize(15);
        /** 强转方式二: */
//        ((ThreadPoolExecutor) service).setKeepAliveTime(1000);
        /** 2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象 */
        service.execute(new NumberThread1());//适合使用于Runnable
        service.execute(new NumberThread2());//适合使用于Runnable
        service.shutdown();
    }
}
原文地址:https://www.cnblogs.com/BeautifulGirl230/p/14228226.html