java_线程、同步、线程池

时间:2022-07-23
本文章向大家介绍java_线程、同步、线程池,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

线程

Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例

Thread类常用方法

构造方法 - public Thread():分配一个新的线程对象。 - public Thread(String name):分配一个指定名字的新的线程对象。 - public Thread(Runnable target):分配一个带有指定目标新的线程对象。 - public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

常用方法 - public string getName():获取当前线程名称。 - public void start():导致此线程开始执行;Java虚拟机调用此线程的run方法。 - public void run():此线程要执行的任务在此处定义代码。 - public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。 - public static Thread currentThread():返回对当前正在执行的线程对象的引用。

创建线程方式一

Java中通过继承Thread类来创建并启动多线程的步骤如下: 1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把 run()方法称为线程执行体。 2. 创建Thread子类的实例,即创建了线程对象 3. 调用线程对象的start()方法来启动该线程

测试类:

public class Demo{
    public static void main(String[] args) {
        // 创建自定义线程对象
        MyThread mt = new MyThread("新线程");
        // 开启新线程
        mt.start();
        // 在主方法中执行for循环
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程正在执行" + i);
        }
    }
}

自定义线程类:

public class MyThread extends Thread {
    // 定义指定线程名称的构造方法
    public MyThread(String name) {
        // 调用父类的String参数的构造方法,指定线程的名称
        super(name);
    }
    /**
     * 重写run方法,完成该线程执行的逻辑
     */
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(getName() + "正在执行" + i);
        }
    }
}

流程图:

程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的start方法,另外一个新的线程也启动了,这样,整个应用就在多线程下运行。

创建线程方式二

Java中通过实现Runnable接口来创建并启动多线程的步骤如下: 1. 定义Runnable接口的实现类,并重写该接口的run () 方法,该run () 方法的方法体同样是该线程的线程执行体。 2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。 3. 调用线程对象的start () 方法来启动线程。

测试类:

public class Demo {
    public static void main(String[] args) {
        // 创建自定义类对象线程任务对象
        MyRunnable mr = new MyRunnable();
        // 创建线程对象
        Thread t = new Thread(mr, "新线程");
        t.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程正在执行" + i);
        }
    }
}

自定义线程类:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "正在执行" + i);
        }
    }
}

通过实现Runnable接口,使得该类有了多线程类的特征。run () 方法是多线程程序的一个执行目标。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。

Thread和Runnable的区别

如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。

实现Runnable接口比继承Thread类所具有的优势: 1. 适合多个相同的程序代码的线程去共享同一个资源。 2. 可以避免java中的单继承的局限性。 3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和数据独立。 4. 线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类。

匿名内部类方式实现线程的创建

public class Demo {
    public static void main(String[] args) {
        //使用匿名内部类方法;直接创建Thread类的子类对象
        /*
         * new Thread() { public void run() { for (int i = 0; i < 10; i++) {
         * System.out.println("新线程正在执行" + i); } } }.start();
         */
        //使用匿名内部类方式;直接创建Runnable接口实现类对象
        Runnable r = new Runnable() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("新线程正在执行" + i);
                }
            }
        };
        new Thread(r).start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程正在执行" + i);
        }
    }
}

线程安全

两个或两个以上的线程在访问共享资源时,仍然能得到正确的结果则称之为线程安全

模拟卖50张电影票

public class Ticket implements Runnable {
    private int ticket = 50;
    // 买票操作
    @Override
    public void run() {
        // 每个窗口买票操作,窗口永远开启
        while (true) {
            if (ticket > 0) {
                // 使用sleep方法模拟买票
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                // 获取当前对象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖票:" + ticket--);
            }
        }
    }
}

测试类:

public class Demo {
    public static void main(String[] args) {
        // 创建线程任务对象
        Ticket ticket = new Ticket();
        // 创建三个窗口卖票
        Thread t1 = new Thread(ticket, "窗口一");
        Thread t2 = new Thread(ticket, "窗口二");
        Thread t3 = new Thread(ticket, "窗口三");

        // 同时开始卖票
        t1.start();
        t2.start();
        t3.start();
    }
}

结果出现了这种现象:

这种问题,几个窗口(线程)票数不同步了,称为线程不安全

线程同步

当我们使用多个线程访问统一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题.

要解决上述多线程并发访问多一个资源的安全性问题,java中提供了同步机制(synchronized)来解决,有三种方式完成同步操作: 1. 同步代码块 2. 同步方法 3. 锁机制

同步代码块

同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问

synchronized(同步锁){
    需要同步操作的代码
}

同步锁注意事项 1.锁对象可以是任意类型。 2.多个线程对象要使用同一把锁。

同步代码块实现线程安全

public class Ticket implements Runnable {
    private int ticket = 50;
    Object lock = new Object();

    // 买票操作
    @Override
    public void run() {
        // 每个窗口买票操作,窗口永远开启
        while (true) {
            synchronized (lock) {
                if (ticket > 0) {
                    // 使用sleep方法模拟买票
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    // 获取当前对象的名字
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "正在卖票:" + ticket--);
                }
            }
        }
    }
}

同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法保证A线程执行该方法的时候,其他线程只能在方法外等着

public synchronized void method(){
    可能会产生线程安全问题的代码
}

同步方法实现线程安全

public class Ticket implements Runnable {
    private int ticket = 50;
    // 买票操作
    @Override
    public void run() {
        // 每个窗口买票操作,窗口永远开启
        while (true) {
            sellTicket();
        }
    }
    // 锁对象是谁调用这个方法就是谁,this
    public synchronized void sellTicket() {
        if (ticket > 0) {
            // 使用sleep方法模拟买票
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // 获取当前对象的名字
            String name = Thread.currentThread().getName();
            System.out.println(name + "正在卖票:" + ticket--);
        }
    }
}

Lock锁

java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。 Lock常用方法 - public void lock():加同步锁。 - public void unlock():释放同步锁。

Lock锁实现线程安全

public class Ticket implements Runnable {
    private int ticket = 50;
    Lock lock = new ReentrantLock();
    // 买票操作
    @Override
    public void run() {
        // 每个窗口买票操作,窗口永远开启
        while (true) {
            lock.lock();
            if (ticket > 0) {
                // 使用sleep方法模拟买票
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                // 获取当前对象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖票:" + ticket--);
            }
            lock.unlock();
        }
    }

}

线程池

线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

线程池能够带来三个好处:

  1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存

线程池的使用

Java里面线程池的顶级接口是java.util.concurrent.Executors

public static ExecutorService newFixedThreadPool(int nThreads) :返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)

public Future<?> submit(Runnable task) :获取线程池中的某一个线程对象,并执行

Runnable实现类代码:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我要一个教练");
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("教练来了: " + Thread.currentThread().getName());
    System.out.println("教我游泳,交完后,教练回到了游泳池");
    }
}

线程池测试类:

public class Demo{
    public static void main(String[] args) {
        // 创建线程池对象
        ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
        // 创建Runnable实例对象
        MyRunnable r = new MyRunnable();
        //自己创建线程对象的方式
        // Thread t = new Thread(r);
        // t.start(); ---> 调用MyRunnable中的run()
        // 从线程池中获取线程对象,然后调用MyRunnable中的run()
        service.submit(r);
        // 再获取个线程对象,调用MyRunnable中的run()
        service.submit(r);
        service.submit(r);
        // 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
        // 将使用完的线程又归还到了线程池中
        // 关闭线程池
        //service.shutdown();
    }
}