Java多线程

程序、进程、线程

  1. 程序:一段静态的代码,静态对象

  2. 进程:正在运行的一个程序,是一个动态的过程,有生命周期。进程作为资源分配的单位,系统在运行时会为每个进程分配内存区域。
  3. 线程:进程可以进一步细化为线程,同一个进程可以支持多个线程就叫多线程,线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器
  • 每个线程各自有一套虚拟机栈和程序计数器
  • 每个进程各有一份方法区和堆
  • 单核CPU和多核CPU:一个JAVA应用程序至少有三个线程,main主线程,gc()垃圾回收线程,异常处理线程
  • 并行和并发:

并行——多个CPU同时执行多个任务,如:多个人同时做不同的事儿。

​ 并发——一个CPU同时执行多个任务,如:秒杀、多个人做同一件事。


使用多线程的优点

1、提高程序的响应。对图形化界面更有意义,可增强用户体验

2、提高计算机系统CPU的利用率

3、改善程序结构,进程分为线程独立运行,有利于理解和修改

线程的创建和使用

方式一:继承于Thread类

1、创建Thread类的子类

2、重写Thread类的run()方法——>将此线程执行的操作声明在run方法中

3、创建Thread类的子类对象

4、通过此对象调用start():作用(1)启动当前线程;(2)调用当前线程的run()

package java5;

//1、创建Thread类的子类
class MyThread extends Thread{
    //2、重写Thread类的run()方法
    @Override
    public void run() {
        for(int i = 0;i < 100;i++){
            if(i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //3.创建Thread类的子类对象
        //MyThread t1 = new MyThread();
        MyThread t1 = new MyThread();//这里new MyThread()之后直接alt + enter就可以造对象了

        //4、通过此对象调用start()
        t1.start();

    }
}

问题:

  1. 不能通过run()的方式执行线程,用run()只是普通的调用方法,一定!一定!要是 start()
  2. 如果还要再启动一个线程,就再新造一个MyThread对象,再start()——要想启动多个线程,就要去造多个对象,再去调start()方法。
package java5;

public class ThreadDemo {
    /*
    练习:创建两个分线程,其中一个线程遍历100以内的偶数,另一个遍历100以内的奇数。
     */
    public static void main(String[] args) {
//        MyThread1 m1 = new MyThread1();
//        MyThread2 m2 = new MyThread2();
//        m1.start();
//        m2.start();

        //上面的四行还可以改成创建Thread类的匿名子类的方式
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100;i++){
                    if(i % 2 == 0)
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100;i++){
                    if(i % 2 != 0)
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                }
            }
        }.start();
    }

}
class MyThread1 extends Thread{
    public void run(){
        for (int i = 0; i < 100;i++){
            if(i % 2 == 0)
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}
class MyThread2 extends Thread{
    public void run(){
        for (int i = 0; i < 100;i++){
            if(i % 2 != 0)
                System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

Thread里的常用方法:

  • start():①启动当前线程 ②调用当前线程的run()
  • run():通常需要重写此方法
  • currentThread():静态方法,返回执行当前代码的线程
  • getName():获取当前线程的名字
  • setName():设置当前线程的名字
  • yield():释放当前CPU的执行权
  • join():在线程A中调用B的该方法,线程A进入阻塞状态直到线程B执行完之后线程A才继续执行
  • sleep():让当前线程强制阻塞

线程的优先级:

  1. MAX——10,MIN——1,NORM——5(默认的优先级)
  2. 涉及的方法:getPriority()——返回线程的优先值,setPriority()——改变线程的优先级
  3. 高优先级的线程不一定就先执行,只是大概率先执行。

方式二:实现Runnable接口

  1. 创建一个实现Runnable接口的类.
  2. 实现类实现Runnable中的抽象方法:run()方法.
  3. 创建实现类的对象.
  4. 将对象作为参数传递到Thread类的构造器中,创建Thread类的对象.
  5. 通过此对象调用start()方法.
package java5;
/*
例子:创建三个窗口卖票,总票数为100张,使用实现Runnable接口的方式
 */
class Window1 implements Runnable{
    private static int ticket = 100;

    @Override
    public void run() {
        while(true){
            if(ticket > 0){
                System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
                ticket --;
            }else{
                break;
            }
        }
    }
}
public class WindowTest {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);//这里形参都是m,本身就可以实现共享数据


        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

两种方式的对比

开发中:优先选择,实现Runnable接口的方式。

原因:Runnable没有单继承性的局限性,更适合多个线程共享数据的情况.

相同点:都需要重写run并且将线程要执行的逻辑声明在run()中.

线程的生命周期

线程的同步(解决线程安全问题) 

方式一:同步代码块

synchronized(同步监视器){

        //需要被同步的代码

}

说明:1.操作共享数据的代码,即为需要被同步的代码,包含的代码一定要刚刚好,不能少也不能多。

        共享数据:多个线程共同操作的变量。如卖票中的ticket。

        2.同步监视器俗称“锁”。任何一个类的对象,都可以充当锁。

要求:多个线程必须要共用同一把锁。(实现的方式中一般写在run()方法前面;继承的方法中,要把synchronized声明为static,确保共享数据是唯一的)

实现类方法

package java5;


    /*
    例子:创建三个窗口卖票,总票数为100张,使用实现Runnable接口的方式
     */
    class Window1 implements Runnable{
        private static int ticket = 100;
        Object obj = new Object();//锁,这个obj一定要是共用的,唯一的,放的位置要正确
        public void run(){
            synchronized(obj){
                while(true){
                    if(ticket > 0){
                        System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
                        ticket --;
                    }else{
                        break;
                    }
                }
            }
        }



    }

    public class WindowTest {
        public static void main(String[] args) {
            Window1 w = new Window1();

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

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


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

        }
    }

(1)实现类方法可以使用this,就不用每次再造对象了。

(2)继承类方法可以用类去充当同步监视器——synchronized(Window2.class——类也是对象,万物皆对象)这里的Window2只会加载一次,this要慎用,一定要注意唯一性!!

方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,就把这个方法弄成同步方法。

1、同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。

2、非静态的同步方法(一般是实现Runnable类的方式),同步监视器是this;静态的同步方法(一般是继承thread类的方式),同步监视器是:当前类本身

//同步方法+实现Runnable
class MyRun implements Runnable{
    private static int ticket=100;
    @Override
    public void run(){
        show();//先同步方法,然后把这个方法包到run方法中
    }
    public synchronized void show(){//这里的同步监视器就是this
        for (tikect>0) {
            System.out.println(getName()+"买票,票号为:"+ticket);
            ticket--;
        }
    }
}
public static void main(String[] args) {
    MyRun r=new MyRun();
    
    Thread t1=new Thread(r);
    Thread t2=new Thread(r);
    Thread t3=new Thread(r);
    t1.start();
    t2.start();
    t3.start();
}
//同步方法+实现Thread类
class MyThread extends Thread{
    private static int ticket=100;
    @Override
    public void run(){
        show();
    }
    public static synchronized void show(){//必须是静态的,这里的同步监视器是当前类
        for (tikect>0) {
            System.out.println(Thread.currentThread.getName()+"买票,票号为:"+ticket);
            ticket--;
        }
    }
}

同步使得速度变慢,但是为了安全,还是需要用的。

线程安全的单例模式:懒汉式

原始的两种模式:

(1)懒汉式(延迟对象的创建,线程不安全)

package java5;

public class SingleTest2 {
    public static void main(String[] args) {

        Order order1 = Order.getInstance();
        Order order2 = Order.getInstance();
        System.out.println(order1 == order2);

    }
}
//懒汉式,什么时候需要用,什么时候造对象
class Order{
    //1.私有化类的构造器
    private Order(){

    }
    //2.声明当前类对象,没有初始化,对象必须为static
    private static Order instance = null;//主要区分点private static Bank instance = new Bank();

    //3.声明public,static的返回当前类对象的方法
    public static Order getInstance(){
        if(instance == null){
            instance = new Order();
        }
        return instance;
    }


}

(2)饿汉式(对象的加载时间过长,它是线程安全的)

package java5;

/**
 * 单例设计模式(某个类只能存在一个实例)
 *
 */
public class SingletonTest1 {
    public static void main(String[] args) {

        Bank bank1 = Bank.getInstance();
        Bank bank2 = Bank.getInstance();
        System.out.println(bank1 == bank2);

    }
}
//饿汉式,先提前造好对象
class Bank{
    //1.私有化类的构造器
    private Bank(){

    }
    //内部创建类的静态对象
    private static Bank instance = new Bank();//private用不了,所以要有公共的方法

    //提供公共的静态方法,返回类的对象
    public static Bank getInstance(){
        return instance;
    }


}

线程安全的懒汉式

class Order{
    //1.私有化类的构造器
    private Order(){

    }
    //2.声明当前类对象,没有初始化,对象必须为static
    private static Order instance = null;//主要区分点private static Bank instance = new Bank();

    //3.声明public,static的返回当前类对象的方法
    public static Order getInstance(){
        if(instance == null){
            synchronized(Bank.class){
                if(instance == null){
                    instance = new Order();
                }
            }
        }
        return instance;
    }
}

死锁的理解

1、同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃,造成了线程的死锁,没有异常没有提示,但所有线程都在阻塞状态。

Lock(锁)

1、synchronized与Lock的异同?

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

异:Lock自动锁定和解锁,synchronized在执行完相应的同步代码块之后,自动地释放同步监视器,Lock需要手动的启动同步——Lock(),同时结束同步也需要手动的实现——unlock()

优先顺序:

Lock——>同步代码块——>同步方法

线程的通信

涉及到的三个方法:

wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器

notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级高的

notifyall():一旦执行此方法,就会唤醒所有被wait的线程

说明:1.这三者必须是只能出现在同步代码块和同步方法中

2,这三个方法的调用者必须是同步代码块或同步方法中同步监视器。否则出现异常。

3.这三个方法是定义在java.lang.Object类中。

面试题:sleep()和wait()的异同:
同:一旦执行,就会使线程进入阻塞状态。

异:(1)声明的位置不同,Thread类中声明sleep(),Object类中声明wait()

(2)调用要求不同,sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或者同步方法中

(3)关于是否释放同步监视器? 

如果两个方法都使用在同步代码块或者同步方法中,sleep()不会释放锁,而wait()会释放锁。

线程通信例题:消费者,生产者问题



*生产者(Productor)将产品交给店员(CLerk),而消费者(Customer)从店员处取走产品,

店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员

会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品

了,店员会告诉消费者等一下,如果店中有产品J通知消费者来取走产品。
 

问题思考:

是否是多线程问题?——生产者线程,消费者线程

是否有共享数据?——是,店员(或者产品)

如何解决线程安全问题?——同步机制,有三种方法

是否设计线程通信?是

package java5;


import java.util.Properties;

class Clerk{

    private int productCount = 0;
    //生产产品
    public synchronized void produceProduct() {
        if(productCount < 20){
            productCount ++;
            System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品");
            notify();
        }else{
            try {
                wait();//有wait(),一般都要有notify()
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
//消费产品
    public synchronized void consumeProduct() {
        if(productCount > 0){
            System.out.println(Thread.currentThread().getName() + ":开始消费第" + productCount + "个产品");
            productCount --;
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Producer extends Thread{//涉及线程问题——实现?继承?
    private Clerk clerk;

    public Producer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始生产产品。。。。");
        while(true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }//让时间更久一些
            clerk.produceProduct();
        }
    }
}


class Customer extends Thread{
    private Clerk clerk;

    public Customer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始消费产品。。。。");
        while(true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumeProduct();
        }
    }
}

public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer p1 = new Producer(clerk);
        p1.setName("生产者1");

        Customer c1 = new Customer(clerk);
        c1.setName("消费者1");

        p1.start();
        c1.start();
    }
}

新增多线程的方式——实现callable接口(建议使用)

使用的好处:
1、call()可以有返回值。

2、call()可以抛出异常,被外面的操作捕获,获取异常的信息

3、callable是支持泛型的

使用线程池

提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁地创建,销毁,实现重复利用,类似于公共工具。

好处:1、提高响应速度(减少了创建线程的时间)

2、降低资源消耗

3、便于线程管理

        corePoolSize:核心池的大小

        maxmumPoolSize:最大线程数

        keepAliveTime:线程没有任务时最多保持多长时间会终止。

●JDK 5.0起提供了线程池相关API: ExecutorService和Executors

ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

➢void execute(Runnable command):执行任务1命令,没有返回值,一般用来执行

Runnable接口

➢<T> Future<T> submit(Callable<T> task): 执行任务,有返回值,一般又来执行

Callable接口

➢void shutdown():关闭连接池

package java7;


import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;

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(i);
            }
        }
    }
}
public class ThreadPool {
    public static void main(String[] args) {
        //提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);//一般用的是这种方法
        
        //执行指定的线程的操作,需要提供实现runnable或者callable接口实现类的对象
        service.execute(new NumberThread());
//        service.submit()适合使用于runnable
        //关闭线程池
        service.shutdown();

    }
}


●Executors: 工具类、 线程池的工厂类,用于创建并返回不同类型的线程池

➢Executors. newCachedThreadPool():创建一个可 根据需要创建新线程的线程池

➢Executors.newFixedThreadPolln); 创建一-个可重 用固定线程数的线程池

➢Executors.newSingle ThreadExecutor():创建一-个只有 一-个线程的线程池

➢Executors.newScheduledThreadPool(n): 创建一 个线程池,它可安排在给定延迟后运

行命令或者定期地执行。

总结

实现多线程的方式:

(1)Thread继承类

(2)runnable接口

(3)callable接口

(4)使用线程池

线程安全的解决方式:
(1)同步代码块

(2)同步方法

(3)Lock锁

重点掌握:创建方式、同步、买票问题、懒汉式、饿汉式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值