java线程

目录

一. 程序 进程 线程的概念

1.1 进程和线程的关系

二. 线程的创建

2.1 继承Thread类

2.2 实现Runnable接口

2.3 实现Callable接口 

三. Thread类常用方法

四. 线程的状态

 五. 多线程

5.1 多线程优缺点

六. 解决多线程访问共享资源问题

6.1 卖票案例

6.2 线程同步

6.2.1 synchronized关键字

6.2.1.1 synchronized修饰代码块

6.2.1.2 synchronized修饰方法

6.2.2 ReentrantLock类

6.2.3 synchronized和ReentrantLock的区别

6.3 利用线程同步解决卖票案例

6.3.1 synchronized关键字(继承Thread类)

6.3.2 synchronized关键字(实现Runnable接口)

6.3.3 ReentrantLock类(继承Thread类) 

6.3.4 ReentrantLock类(实现Runnable接口)

 七. 线程通信

7.1 案例一(两个线程交替打印数字)

7.2 案例二(生产者消费者模型)


一. 程序 进程 线程的概念

程序: 为了实现某种功能,通过编程语言写的一系列的指令的集合.

指的是在硬盘上存储的静态代码

进程: 被操作系统调度到内存中执行的程序(正在执行的程序),是操作系统进行资源分配的最小单位

线程: 进程可以进一步细化为线程,是进程中的最小执行单元(任务),是cpu调度的最小单位

1.1 进程和线程的关系

1.一个进程中可以包含多个线程(一个QQ可以有多个聊天窗口)

2.一个线程只能隶属于一个进程(QQ聊天窗口只能隶属于QQ进程)

3.一个进程中至少包含一个线程(主线程,java的main方法就是用来启动主线程的)

4.在主线程中可以创建并启动其他线程

5.一个进程的所有线程共享该进程的所有资源

二. 线程的创建

2.1 继承Thread类

要想在java程序中创建一个线程,第一种方式是继承Thread类,实现其中的run()方法,将线程中想要执行的任务写在run方法中,再调用Thread中的start()方法

注意:一定不要调用run()方法,如果调用该方法,那么并没有创建一个线程,该程序中还是main一个主线程,代码的执行逻辑仍然是从上向下执行,只有调用了start()方法才是真正创建了一个独立的线程

public class MyThread extends Thread{
    /*
    java中创建线程方式1
    写一个类继承java.lang.Thread
    重写run()
     */


    /*
    线程中要执行的任务都要写在run()中,或者在run()中调用
     */
    @Override
    public void run() {
        /*for (int i = 0; i < 1000; i++) {
            System.out.println("MyThread:"+i);
        }*/
        test();
    }

    public void test()
    {
        for (int i = 0; i < 1000; i++) {
            System.out.println("MyThread:"+i);
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //创建并启动线程
        MyThread myThread = new MyThread();
        //myThread.run();//这不是启动线程,只是一个方法调用,没有启动线程,还是单线程模式

        myThread.start();//这才是启动一个线程

        for (int i = 0; i < 1000; i++) {
            System.out.println("main:"+i);
        }
    }
}

2.2 实现Runnable接口

定义一个类实现Runnable接口,实现里面的run()方法,此时只是创建了一个线程要执行的任务,然后再创建Thread类的对象,通过构造方法传入自定义类的对象即可完成线程的创建

public class Task implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("自定义线程:"+i);
        }
    }
    /*
    java中创建线程方式2:
    只先创建要执行的任务,创建一个类,实现Runnable接口
    重写任务执行的run()

    实现Runnable接口创建的优点:
    1.因为java是单继承,一旦继承一个类就不能在继承其他类,避免单继承的局限
    2.适合多线程来处理同一份资源时使用
     */
}
public class TaskTest {

    public static void main(String[] args) {
        //创建任务
        Task task = new Task();
        //创建线程,并为线程指定执行任务
        Thread thread = new Thread(task);
        thread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("main:"+i);
        }
    }
}

2.3 实现Callable接口 

实现Callable接口创建线程是一种比较强大的线程创建方式,相比于上面两种方式,该方式可以有返回值,也可以抛异常,实现该接口,并实现里面的call()方法

public class SumTask<T> implements Callable<T> {

    @Override
    public T call() throws Exception {
        Integer sum = 0;
        for (int i = 0; i < 10; i++) {
            sum += i;
        }
        return (T)sum;
    }
}
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        SumTask<Integer> sumTask = new SumTask<>();
        FutureTask<Integer> futureTask = new FutureTask<>(sumTask);
        Thread thread = new Thread(futureTask);
        thread.start();
        Integer sum = futureTask.get();//获取call方法返回的执行结果
        System.out.println(sum);
    }
}

 注意:

1.由于Thread的构造方法只允许传入一个实现了Runnable接口的方法,所以我们要利用一个FutureTask类进行转换(将Callable转化为Runnable),该类的构造方法允许传入一个Callable接口,并且该类实现了RunnableFuture接口,而RunnableFuture接口又继承了Runnable接口和Future接口,通过多态性可将Callable转为Runnable

2.可以通过FutureTask类中的get()方法获得call()方法的返回值

三. Thread类常用方法

 run()用来定义线程要执行的任务代码
start()启动线程的
currentThread()获取到当前线程
getId()获取线程id
setName()为线程设置名字
 getState()获取线程的状态
getPriority()获取线程的优先级
setPriority(10)设置线程优先级  优先级为1-10 默认是5  作用是为操作系统调度算法提供的
getName()获取线程名字
join()等待调用了join()方法的线程执行完毕,其他线程再执行
yield()主动礼让,让出cpu执行权
sleep(long millis)让进程睡眠阻塞一定时间,参数是毫秒

四. 线程的状态

新建:刚创建一个线程对象,没有调用start()方法启动该线程

就绪(可运行状态):调用start()方法后,线程就进入了就绪状态,在就绪队列中等待cpu调度

运行:被操作系统调度到cpu上执行

阻塞:进程因没有获得相应的资源而处于等待,例如调用了sleep(), 有线程调用了join(),线程中进行Scanner输入

死亡/销毁:进程的run()方法执行完等,使进程被销毁

 五. 多线程

多线程:在同一个程序中创建了多个线程执行

5.1 多线程优缺点

优点:

1. 提高了cpu的利用率

2. 提高程序运行的效率

3. 改善程序结构, 例如将一个大的任务拆分成若干个小任务执行

缺点:

1. 线程过多,增加了内存开销

2. cpu开销大

3. 当多个线程对同一份共享资源进行访问时,如果不加以控制,就会出现严重的错误,比如:电商购物,买票等

六. 解决多线程访问共享资源问题

6.1 卖票案例

public class TicketThread extends Thread{

    static int num = 100000;

        @Override
    public void run() {

        while(true){
           
                if(num > 0){
                    System.out.println(Thread.currentThread().getName()+"买到了第"+num+"张票");
                    num--;
                }else{
                    break;
                }
            }
        }
}
public class TicketTest {
    public static void main(String[] args) {
        TicketThread thread1 = new TicketThread();
        thread1.setName("窗口一");
        thread1.start();

        TicketThread thread2 = new TicketThread();
        thread2.setName("窗口二");
        thread2.start();

    }
}

由于窗口一和窗口二两个线程对同一个资源num进行访问,且没有加以控制就会导致出票错误,有可能重票,有可能错票,这在实际生活中是很严重的错误

可以看到窗口一和窗口二同时买到了第10张票. 

6.2 线程同步

解决该问题的方法就是线程同步,即在对共享资源进行访问时加一把锁,一次只允许一个进程访问,等这个进程访问完,其他进程才可以访问该共享资源

6.2.1 synchronized关键字

上述说的加锁就是在对进程同时访问的代码块中加synchronized关键字,即可完成加锁

6.2.1.1 synchronized修饰代码块

synchronized(同步锁对象){

    同步代码块

    }   


 /*
    同步锁对象作用:用来记录有没有线程进入到同步代码块中,如果有线程进入到同步代码块,那么其他线程就不能进入同步代码块
                直到上一个线程执行完同步代码块的内容,其他线程才能进入
    同步锁对象的要求:可以是任意类的对象
                  同步锁对象必须是唯一的(多个线程拿到的是同一个对象)
     */

同步锁对象作用:用来记录有没有线程进入到同步代码块中,如果有线程进入到同步代码块,那么其他线程不能进入同步代码块中

同步锁对象的要求:可以是任意类的对象,同步锁对象必须是唯一的(多个线程拿到的是同一个对象)

 比如:被static修饰的就是同一个对象

6.2.1.2 synchronized修饰方法

在方法前面加上synchronized关键字

public class TicketTask extends Thread{
    static int num = 10;//模拟有10张票
    static Object obj = new Object();

    /*
    synchronized修饰方法时,同步锁对象不需要我们指定
    同步锁对象会默认提供:
    1.非静态的方法--锁对象默认是this
    2.静态方法--锁对象是当前类的Class对象(类的对象,一个类的对象只有一个)
     */
    public static synchronized void Print()
    {
        if(num>0){
            System.out.println(Thread.currentThread().getName()+"买到了第"+num+"张票");
            num--;
        }
    }
    @Override
    public void run() {
        while(true)
        {
            if(num <= 0)
            {
                break;
            }
            Print();
        }
    }
}

synchronized修饰方法时,同步锁对象不用我们指定,会默认生成

1.非静态方法:锁对象默认是this,当前对象

2.静态方法:锁对象默认是当前类的Class对象(类的对象,一个类的对象只有一个)

6.2.2 ReentrantLock类

ReentrantLock类中的lock()方法和unlock()方法也可以实现,synchronized关键字的作用,只不过需要注意的是,使用这两个方法时需要我们手动的加锁和释放锁,而且也要注意两个不同的对象要用同一个ReentrantLock对象

public class TicketThread extends Thread{
    static int num = 10;
    static ReentrantLock lock = new ReentrantLock();//实现锁控制的对象

    @Override
    public void run() {
        while (true){
                try{
                    lock.lock();//0-1  加锁
                    if(num>0){
                        System.out.println(Thread.currentThread().getName()+"买到了第"+num+"张票");
                        num--;
                    }else{
                        break;
                    }
                }finally {
                    lock.unlock();//1-0  释放锁
                }
            }
    }
    /*
    synchronized 和 ReentrantLock区别:

    synchronized是一个关键字,控制依靠底层编译后的指令去实现
    synchronized可以修饰一个方法,还可以修饰一个代码块
    synchronized是隐式的加锁和释放锁,一旦方法或代码块中运行结束或出现异常,会自动释放锁

    ReentrantLock是一个类,是依靠java代码去控制(底层有一个同步队列)
    ReentrantLock只能修饰代码块
    ReentrantLock需要手动的加锁,手动的释放锁,所以释放锁最好写在finally中,一旦出现异常,保证锁能释放
     */

}

6.2.3 synchronized和ReentrantLock的区别

1.synchronized是一个关键字,底层依靠编译后的指令实现,ReentrantLock是一个类内部依靠java代码实现(底层有一个同步队列)

2.synchronized可以修饰一个代码块,也可以修饰一个方法,,ReentrantLock只能修饰代码块

3.synchronized是隐式的加锁释放锁,一旦方法或代码块中运行结束或出现异常,会自动释放锁

ReentrantLock需要手动的加锁,手动的释放锁,所以释放锁最好写在finally中,一旦出现异常,保证能释放锁

6.3 利用线程同步解决卖票案例

6.3.1 synchronized关键字(继承Thread类)

synchronized修饰代码块

public class TicketThread extends Thread{

    static int num = 10;
    static Object obj = new Object();
   
    @Override
    public void run() {

        while(true){
            synchronized (obj)
            {
                if(num > 0){
                    System.out.println(Thread.currentThread().getName()+"买到了第"+num+"张票");
                    num--;
                }else{
                    break;
                }
            }
        }

    }
}

synchronized修饰方法

public class TicketThread extends Thread{

    static int num = 1000;
    
    public static synchronized void print()
    {

       if(num > 0)
       {
           System.out.println(Thread.currentThread().getName()+"买到了第"+num+"张票");
           num--;
       }
    }

    @Override
    public void run() {
        while(true)
        {
            if(num <= 0)
            {
                break;
            }
            print();
        }
    }
}

6.3.2 synchronized关键字(实现Runnable接口)

 synchronized修饰代码块

public class TicketTask implements Runnable{
    int num = 10;//模拟有10张票


      @Override
    public void run() {

           while (true){
               synchronized (this){// obj对象的作用:记录有没有线程进入到同步代码块    要求:多个线程对应同一个同步锁对象
                   if(num>0){
                       System.out.println(Thread.currentThread().getName()+"买到了第"+num+"张票");
                       num--;
                   }else{
                       break;
                   }
               }
           }
    }
}

synchronized修饰方法

public class TicketTask implements Runnable{
    int num = 10;//模拟有10张票

 public  synchronized void Print()
    {
        if(num>0){
            System.out.println(Thread.currentThread().getName()+"买到了第"+num+"张票");
            num--;
        }
    }

    @Override
    public void run() {
        while(true)
        {
            if(num <= 0)
            {
                break;
            }
            Print();
        }
    }
}

6.3.3 ReentrantLock类(继承Thread类) 

public class TicketThread extends Thread{
    static int num = 10;
    static ReentrantLock lock = new ReentrantLock();//实现锁控制的对象

    @Override
    public void run() {
        while (true){
                try{
                    lock.lock();//0-1  加锁
                    if(num>0){
                        System.out.println(Thread.currentThread().getName()+"买到了第"+num+"张票");
                        num--;
                    }else{
                        break;
                    }
                }finally {
                    lock.unlock();//1-0  释放锁
                }
            }
    }
}

6.3.4 ReentrantLock类(实现Runnable接口)

 修饰代码块

public class TicketTask implements Runnable{
    ReentrantLock lock = new ReentrantLock();
    int num = 1000;
    @Override
    public void run() {
        while(true)
        {
            try {
                lock.lock();
                if (num > 0) {
                    System.out.println(Thread.currentThread().getName() + "买到了第" + num + "张票");
                    num--;
                }else{
                    break;
                }
            }finally {
                lock.unlock();
            }
        }
    }
}

 七. 线程通信

Objtct类中有wait()方法,notify()方法,notifyAll()方法,可以用来实现线程间的通信

wait()方法使进入同步代码块中的方法进入等待状态
notify()方法唤醒上一个在等待状态的线程
notifyAll()方法唤醒所有在等待状态的线程

 注意:

这三个方法都必须在同步代码块中调用,并且必须通过同步锁对象调用

wait()和sleep()的区别

1. sleep()方法属于Thread类的方法,wait()方法属于Object类的方法

2.sleep()方法睡眠时间到后,会被自动唤醒,wait()方法,需要被notify()或notifyAll()方法唤醒

3.sleep()方法不会释放锁对象,wait()方法会释放锁对象

7.1 案例一(两个线程交替打印数字)

public class PrintNumThread extends Thread{
    static int num = 1;
    static Object obj = new Object();

    @Override
    public void run() {
        while(num<=100) {

            synchronized (obj) {
                obj.notify();
                System.out.println(Thread.currentThread().getName() + ":" + num);
                num++;
                try {
                    obj.wait();//让线程等待,会自动释放锁,notify(),wait()必须都在同步代码块(同步方法)中通过同步锁对象调用
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }finally {
                    obj.notify();
                }
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        PrintNumThread thread1 = new PrintNumThread();
        thread1.setName("线程1");
        thread1.start();
        PrintNumThread thread2 = new PrintNumThread();
        thread2.setName("线程2");
        thread2.start();
    }
}

7.2 案例二(生产者消费者模型)

public class ProductorThread extends Thread{
    Counter counter;
    public ProductorThread(Counter counter)
    {
        this.counter = counter;
    }
    @Override
    public void run() {
        while(true)
        {
            try {
                counter.add();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

生产者线程,用来生产产品

public class CustomerThread extends Thread{
    Counter counter;
    public CustomerThread(Counter counter)
    {
        this.counter = counter;
    }
    @Override
    public void run() {
        while(true)
        {
            try {
                counter.sub();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

消费者线程,用来消费产品

public class Counter {
    int num = 0;//柜台可以存放的商品数量



    //生产者调用 添加商品
    public synchronized void add() throws InterruptedException {
            if(num <= 0)
            {
                this.notify();
                num = 1;
                System.out.println("生产者生产了一件商品");
            }else{
                this.wait();
            }
    }


    //消费者调用 取走商品
    public synchronized void sub() throws InterruptedException {
        if(num>0)
        {
            this.notify();
            num = 0;
            System.out.println("消费者消费了一件商品");
        }else{
            this.wait();
        }
    }
}

柜台类,用于表示柜台上有多少个产品和增加产品消费产品的方法,分别给生产者和消费者调用

public class Test {
    public static void main(String[] args) {
        Counter counter = new Counter();
        ProductorThread productorThread = new ProductorThread(counter);
        productorThread.setName("生产者");
        productorThread.start();
        CustomerThread customerThread = new CustomerThread(counter);
        customerThread.setName("消费者");
        customerThread.start();
    }
}

测试类,创建生产者消费者线程,一边生产一边消费 

  • 12
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值