自学JavaDay14

多线程

多线程的创建

package com.th1024.multiplethreads.ThreadDemo;

/**
 * 多线程的创建
 * 
 * 方式一:继承于Tread类
 * 1. 创建一个继承于Thread类的子类
 * 2. 重写Thread类中的run()方法
 * 3. 创建Thread类的子类的对象
 * 4. 通过此对象调用start()
 * 例子:遍历100以内的所有偶数
 *
 * 方式二:实现Runnable接口--开发中优先选择
 * 1. 定义子类,实现Runnable接口
 * 2. 子类中重写Runnable接口中的run方法
 * 3. 通过Thread类含参构造器创建线程对象
 * 4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中
 * 5. 调用Thread类的start方法:开启线程,调用Runnable子类接口中的run方法
 * 优点:
 * 1. 避免了单继承的局限性
 * 2. 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源
 *
 * @author TuHong
 * @create 2021-01-23 14:05
 */
public class ThreadTest {

    public static void main(String[] args) {

        //3. 创建Thread类的子类的对象
        MyThread1 t1 = new MyThread1();
        //4. 通过此对象调用start()--分线程执行run()方法
        t1.start();
//        t1.run();//只是在主线程调用run方法

        //启动其他线程,不能通过已经start()的线程去执行
//        t1.start();//异常---IllegalThreadStateException
        //需重新创建一个线程的对象
//        MyThread1 t2 = new MyThread1();
//        t2.start();

        //主线程
        for (int i = 0; i < 100; i++) {
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ":"  + i);
            }
        }

        //3. 通过Thread类含参构造器创建线程对象
        //4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中
        MyThread2 myThread2 = new MyThread2();
        //5. 调用Thread类的start方法:开启线程,调用Runnable子类接口中的run方法
        Thread t3 = new Thread(myThread2);
        t3.start();
        //再启动一个线程,输出100以内的质数
        Thread t4 = new Thread(myThread2);
        t4.start();
        //创建匿名子类的方式创建线程
//        new Thread(new MyThread2()).start();

    }
}

//1. 创建一个继承于Thread类的子类
class MyThread1 extends Thread{

    //2. 重写Thread类中的run()方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":"  + i);
            }
        }
    }
}

//1. 定义子类,实现Runnable接口
class MyThread2 implements Runnable{

    //2. 子类中重写Runnable接口中的run方法
    @Override
    public void run() {
        //输出100以内的质数
        label:for (int i = 2; i < 100; i++) {
            for (int j = 2; j <= Math.sqrt(i); j++) {
                if(i % j == 0){
                    continue label;
                }
            }
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

Thread中常用的方法

package com.th1024.multiplethreads.ThreadDemo;

/**
 * 测试Thread中的常用方法:
 * 1. start():启动当前线程,调用当前线程的run()
 * 2. run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
 * 3. currentThread():静态方法,返回当前代码的线程
 * 4. getName():获取当前线程的名字
 * 5. setName():设置当前线程的名字
 * 6. yield():释放当前cpu的执行权
 * 7. join():在线程a中调用线程b的join(),此时线程a进入阻塞状态,直到线程b完全执行完之后,
 *            线程a才会结束阻塞状态
 * 8. sleep(long millis):让当前线程“睡眠”指定的毫秒。在这段时间内,线程是阻塞状态
 * 9. isAlive():判断当前线程是否存活
 *
 * @author TuHong
 * @create 2021-01-24 10:50
 */
public class ThreadMethodTest {

    public static void main(String[] args) {
        Thread1 t1 = new Thread1("Thread1");

//        t1.setName("线程1");
        t1.start();

        //给主线程命名

        Thread.currentThread().setName("主线程");

        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }

            if(i == 20){
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        //判断分线程是否存活
        System.out.println(t1.isAlive());
    }
}

class Thread1 extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {

            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }

//            if(i % 20 == 0){
//                yield();
//            }
        }
    }

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

两种方式解决线程不安全问题

package com.th1024.demo;

/**
 * 例子:创建三个窗口卖票,总票数为100张
 *
 * 方式一:使用继承Thread类的方式
 * 存在线程安全问题
 *
 * 方式二:使用实现Runnable接口的方式
 * 存在线程安全问题
 *
 * 1. 问题:重票和错票
 * 2. 原因:某个线程进行操作共享数据时,还没执行完,其它线程就参与进来也进行了操作
 * 3. 解决:当一个线程操作共享数据时,其它线程不能参与进来,直到线程操作完成时,其它线程才可以操作,
 *      即使该线程出现阻塞,其它线程也不能参与
 * 4. 方式:同步机制
 *    优点:解决了线程不安全问题
 *    缺点:操作同步代码时,只能有一个线程参与,相当于单线程,效率低
 *
 * 方式一:同步代码块
 *      synchronized(同步监视器){
 *          //需要被同步的代码
 *      }
 * 说明:1. 操作共享数据的代码,即为需要被同步的代码
 *      2. 共享数据:多个线程共同操作的变量
 *      3. 同步监视器。俗称:锁。任何一个类的对象都可以充当锁
 *         要求:多个线程必须拥有同一个监视器对象
 *
 * 方式二:同步方法
 *      如果操作共享数据的代码完整地声明在一个方法中,不妨将这个方法声明为同步方法
 *  说明:1. 同步方法仍然涉及到同步监视器,只是不需要显式声明
 *       2. 非静态的同步方法,同步监视器是:this
 *          静态的同步方法,同步监视器是:当前类本身
 *
 * @author TuHong
 * @create 2021-01-24 13:18
 */

class Window1 extends Thread{

    private static int ticket = 100;
    static Object obj = new Object();//声明为静态,每个对象共享一个静态变量

    @Override
    public void run() {

        while(ticket > 0){
        //while(true) {
            //同步代码块解决继承Thread类方式的线程安全问题
//            synchronized(Window1.class){//使用Window1类作为对象,只会加载一次  //synchronized (obj) {//不能使用this,Window1的对象不唯一
//                if (ticket > 0) {
//
//                    try {
//                        Thread.sleep(100);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//
//                    System.out.println(getName() + ": 卖票,票号为:" + ticket);
//                    ticket--;
//                } else {
//                    break;
//                }
            sell();
//            }
        }
    }

    private static synchronized void sell(){//同步监视器:Window1.class
    //private synchronized void sell(){//同步监视器:w1,w2,w3
        if (ticket > 0) {

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
            ticket--;
        }
    }
}

class Window2 implements Runnable{

    private int ticket = 100;
//    Object obj = new Object();
//    Dog dog = new Dog();
    @Override
    public void run() {

        while(ticket > 0){
        //while(true) {
            //同步代码块解决实现Runnable接口方式的线程安全问题
//            synchronized(this){//this代表唯一的Window2的对象  //synchronized (dog) {
//                if (ticket > 0) {
//
//                    try {
//                        Thread.sleep(100);
//                    } catch (InterruptedException e) {
//                        e.printStackTrace();
//                    }
//                    System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
//                    ticket--;
//                } else {
//                    break;
//                }
            sell();
//            }
        }
    }

    private synchronized void sell(){//同步监视器:this

        if (ticket > 0) {

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ": 卖票,票号为:" + ticket);
            ticket--;
        }
    }
}

public class WindowTest {

    public static void main(String[] args) {

//        Window1 w1 = new Window1();
//        Window1 w2 = new Window1();
//        Window1 w3 = new Window1();
//
//        w1.setName("窗口一");
//        w2.setName("窗口二");
//        w3.setName("窗口三");
//
//        w1.start();
//        w2.start();
//        w3.start();

        Window2 window2 = new Window2();

        Thread t1 = new Thread(window2);
        Thread t2 = new Thread(window2);
        Thread t3 = new Thread(window2);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        t1.start();
        t2.start();
        t3.start();
    }
}

//class Dog{
//
//}

例子:解决单例模式中的懒汉式的线程不安全问题

package com.th1024.demo;

/**
 * 使用同步机制将单例模式中的懒汉式改写为线程安全的
 *
 * @author TuHong
 * @create 2021-01-25 11:20
 */
public class BankTest {
}

class Bank{

    private Bank(){}

    private static Bank instance = null;

    public static Bank getInstance() {
        // public static synchronized Bank getInstance(){
        //方式一:效率稍差
//        synchronized (Bank.class) {
//            if(instance == null){
//                instance = new Bank();
//            }
//            return instance;
//        }
        //方式二:效率更高
        if (instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}

死锁

package com.th1024.demo;

/**
 * 演示线程的死锁问题
 *
 * 1. 死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
 *
 * 2. 说明:出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
 *
 *
 * @author TuHong
 * @create 2021-01-25 11:37
 */
public class DeadLockTest {

    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.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.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锁

package com.th1024.demo;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 解决线程安全问题的方式三:Lock锁 -- JDK5.0新增
 *
 * 1. 面试题:synchronized和lock的异同?
 *      相同:都可以解决线程不安全的问题
 *      不同:synchronized机制在执行完相应的同步代码以后,自动地释放同步监视器
 *           lock需要手动地启动同步(lock()),同时结束同步也需要手动地实现(unlock())
 *   优先使用顺序:lock->同步代码块(已经进入了方法体,分配了相应资源)->同步方法(在方法体之外)
 *
 * 2. 面试题:如何解决线程安全问题?有几种方式?
 *
 * @author TuHong
 * @create 2021-01-25 13:23
 */
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("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        t1.start();
        t2.start();
        t3.start();

    }
}

class Window implements Runnable {

    private int ticket = 100;

    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {

        while (true) {

            try{

                //2. 调用锁定方法: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 {
                //3. 调用解锁方法:unlock()
                lock.unlock();
            }
        }
    }
}

创建多线程的另外两种方式

实现Callable接口

package com.th1024.demo;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 创建线程的方式三:实现Callable接口。-- JDK5.0新增
 *
 * 如何理解实现Callable接口的方式创建多线程比实现Runnable接口的方式强大?
 * 1. call()方法可以有返回值
 * 2. call()可以抛出异常
 * 3. Callable支持泛型
 *
 * @author TuHong
 * @create 2021-01-25 16:06
 */

//1. 创建一个实现Callable接口的实现类
class NumThread implements Callable{

    //2. 实现call(),将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {

        int sum = 0;
        for (int i = 0; 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 = 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();
        }

    }
}

使用线程池

package com.th1024.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 创建线程的方式四:使用线程池
 *
 * 好处:
 * 提高响应速度(减少了创建新线程的时间)
 * 降低资源消耗(重复利用线程池中,不需要每次都创建)
 * 便于线程管理
 * corePoolSize:核心池的大小
 * maximumPoolSize:最大线程数
 * keepAliveTime:线程没有任务时最多保持多长时间后会终止
 *
 * @author TuHong
 * @create 2021-01-25 16:30
 */

class NumberThread 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 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);
            }
        }
    }
}

public class ThreadPool {

    public static void main(String[] args) {
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //设置线程池的属性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime(10);

        //2. 执行指定的线程操作,需提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适用于Runnable
        service.execute(new NumberThread1());//适用于Runnable
//        service.submit();//适用于Callable

        //3. 关闭连接池
        service.shutdown();
    }
}