Java多线程

导读:本篇文章讲解 Java多线程,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

Java多线程

1.1 概念

多线程指的是程序中包含多个不同的执行单元,一个程序中可以允许多个线程执行不同的任务

1.2 使用环境

  1. 程序需要执行两个及以上的任务
  2. 需要实现一些等待的任务(输入,文件读写,网络,搜索…)
  3. 需要一些后台运行的程序

1.3优缺点

优点:

  • 提高程序的响应.

  • 提高CPU的利用率.

  • 改善程序结构,将复杂任务分为多个线程,独立运行.

缺点:

  • 线程越多占用内存越多
  • 多线程需要协调和管理,所以需要CPU时间跟踪线程
  • 多个线程之间对共享资源的竞用访问,需要解决这个问题

2.线程同步

2.1并发与并行

并发:多个CPU同时执行多个任务

并行:一个CPU同时执行多个任务(同一个对象被多个线程同时操作.如痛死运行多个QQ)

2.2多线程同步

多线程同时读取同一份共享资源时,可能会引起冲突,需要引入线程”同步机制”(即各个线程之间有先来后到的顺序)

同步: 排队 + 锁

排队 : 对共享资源操作时,几个线程之间要排队,不能同时操作

锁: 保证数据在方法中被访问时的正确性,在访问时加入锁机制

3同步锁:

3.1 synchronized关键字

Java中synchronized关键字同步方法或代码块

  1. synchronized(同步对象){需要被同步的代码块}

  2. synchronized 返回值类型 方法名(){需要 被同步的代码} 适用于实现了Runnable接口的类

  3. static synchronized 返回值类型 方法名(){需要 被同步的代码} 适用于继承Thread的类

我们用一个窗口售票的程序来演示

  1. 继承Thread的方式

为代码块添加同步锁

/*
    售票线程
*/
public class TicketThread extends Thread{

    //使用static关键字,将成员变量设为整个类的共享资源
    static int num = 10;//剩余票数
    static Object obj = new Object();//保证只有一个
    @Override
    public void run() {
        while (true){
            //synchronized 同步锁,就像一个对象,但要求对象只有一个线程
            synchronized (obj){
                if(num>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"剩余票:"+num);
                    num--;
                }else {
                    break;
                }
            }  //synchronized同步代码块,执行完成后,同步对象(锁)会自动释放
        }
    }
}

为方法添加同步锁

/*
    售票线程
*/
public class TicketThread extends Thread{

    //使用static关键字,将成员变量设为整个类的共享资源
    static int num = 10;//剩余票数
    static Object obj = new Object();//保证只有一个
   
	@Override
	public void run() {
	  while (true){
	      if(num>0){
	          print();
	      }else{
	          break;
	      }
	  }
	}
   /*
        synchronized修饰方法时,同步对象锁,默认是this
        一旦创建多个线程对象  用static 修饰同步方法,同步对象-->线程类
        这种情况只针对继承了Thread的类(Thread的类实例化一个线程对象,就执行一个单独的任务,不加static,多个线程之间对共享资源num 的使用有冲突)
    */
   public  static synchronized   void  print(){
       if(num>0){
           try {
               Thread.sleep(1000);//休眠1s
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           System.out.println(Thread.currentThread().getName()+"剩余票:"+num);
           num--;
       }
   }
public class TicketThreadTest {
    public static void main(String[] args) {

        //创建两个线程对象   执行两个任务
        TicketThread t1 = new TicketThread();
        TicketThread t2 = new TicketThread();
        t1.setName("窗口1");
        t2.setName("窗口2");

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

运行结果:

在这里插入图片描述

  1. 实现Runnable的方式(同步代码块)
/*
    Thread   :  多个线程对象,执行多个任务
    Runnable :  多个线程对象,执行一个任务
 */
public class TicketRunnable implements Runnable{

    //使用static关键字,将成员变量设为整个类的共享资源
    static int num = 10;//剩余票数

    @Override
    public void run() {
        while (true){
          
            /*方式一
            synchronized(){要同步的代码块}
            添加同步对象,这种方式可以使用this表示锁对象(Runnable接口的实现类这样使用,Thread的子类需要创一个静态的对象,使用静态对象作为同步对象)
            */
            synchronized (this){
                if(num>0){
                    try {
                        Thread.sleep(1000);//休眠
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //输出剩余票数
                    System.out.println(Thread.currentThread().getName()+"剩余票:"+num);
                    num--;//票数-1
                }else{
                    break;
                }
            }//同步代码块,执行完成后,同步对象(锁)会自动释放
        }
    }
}

同步方法


/*
    Thread   :  多个线程对象,执行多个任务
    Runnable :  多个线程对象,执行一个任务
 */
public class TicketRunnable implements Runnable{
    //使用static关键字,将成员变量设为整个类的共享资源
	static int num = 10;//剩余票数

@Override
public void run() {
    while (true){
        if(num>0){//票数>0时输出剩余票数
            print();
        }else{
            break;//<0退出
        }
    }
}

//在方法声明中添加synchronized同步监视器,表示整个方法为同步方法
public synchronized void print(){
    if(num>0){
        try {
            Thread.sleep(1000);//休眠
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"剩余票:"+num);
        num--;//票数-1
    }
}
//测试类
public class TicketRunnableTest {
    public static void main(String[] args) {
        //创建一个任务对象
        TicketRunnable ticketRunnable  = new TicketRunnable();

        //创建两个线程,执行同一任务
        Thread t1 = new Thread(ticketRunnable,"窗口1");
        Thread t2 = new Thread(ticketRunnable,"窗口2");

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

运行结果:

在这里插入图片描述

3.2 Lock接口

JDK5.0开始,java提供了更强大的线程同步机制,通过显式定义同步锁对象

同步锁使用Lock对象 ,每次只能有一个线程对Lock对象加锁,线程访问共享资源之前要先获得Lock对象

ReetrantLock是Lock的实现类,可以显式的添加/释放锁

package day2.demo2;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockThreadDemo1 implements Runnable{

    //使用static关键字,将成员变量设为整个类的共享资源
    static int num = 10;//剩余票数
    Lock lock = new ReentrantLock();

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

    public   void  print(){
        try {
            lock.lock();//上锁
            try {
                Thread.sleep(1000);//休眠1s
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(num>0){
                System.out.println(Thread.currentThread().getName()+"剩余票:"+num);
                num--;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁,放在finally中,锁一定会被释放
        }
    }
}
public class LockThreadDemo1Test {
    public static void main(String[] args) {

        LockThreadDemo1 l = new LockThreadDemo1();

        //两个线程执行一个任务  l
        Thread t1 = new Thread(l);
        Thread t2 = new Thread(l);

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

运行结果:

4线程死锁

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

public class LockThreadDemo2 extends Thread{
    static Object objA = new Object();
    static Object objB = new Object();

    boolean flag;
    public LockThreadDemo2(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if(flag){
            synchronized (objA){
                System.out.println("If_objA");

                synchronized (objB){
                    System.out.println("If_objB");
                }
            }
        }else {
            synchronized (objB){
                System.out.println("Else_objB");
                synchronized (objA){
                    System.out.println("Else_objA");
                }
             }
        }
    }
}
public class LockThreadDemo2Test {
    public static void main(String[] args) {
        LockThreadDemo2 l1 = new LockThreadDemo2(true);
        LockThreadDemo2 l2 = new LockThreadDemo2(false);

        l1.start();
        l2.start();
    }
}

运行结果:

在这里插入图片描述

解决方法:

设计时考虑清楚锁的顺序,尽量减少嵌套的加锁交互数量

5. 线程通信

5.1交替打印1~100的案例:

package day2.demo2;
/*
    wait()      当前线程阻塞,释放同步监视器
    notify()    唤醒被wait的一个或多个线程(先唤醒优先级最高的)
    notifyAll() 唤醒所有被wait的线程
 */
public class PrintNum extends Thread{
    public static void main(String[] args) {

        PrintNum p1 = new PrintNum();
        PrintNum p2 = new PrintNum();
        p1.start();
        p2.start();
    }

    private static int num=0;
    private static Object obj = new Object();

    @Override
    public void run() {

        while (true){
            /*
            此处synchronized()内不能使用this,因为该类继承了Thread类
            创建线程对象会有不同的任务
             */
            synchronized (obj){//为代码块加锁
                obj.notify();//唤醒等待的线程,由于已经有线程中持有锁了,也是不能进入同步代码中
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(num<100){
                    System.out.println(Thread.currentThread().getName()+":"+(++num));
                }else {
                    break;
                }
                try {
                    obj.wait();//线程等待,释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

运行结果: 可以看到两个线程之间交替打印数字

在这里插入图片描述

在这里插入图片描述

5.2生产者/消费者问题

生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台 处取走产品,生产者一次只能生产固定数量的产品(比如:1), 这时柜台中不能 再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来 取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待

package day2.demo3;
/*
    柜台
 */
public class Counter {

    //柜台商品数,所有线程共享
    private static int num=1;

    //生产
    //同步对象锁是this,(同一个Counter对象)
    public synchronized void add(){
        while (true){
            this.notify();//唤醒消费者线程
            if(num<1){//当商品数不足一时,开始生产(++)
                num++;
                System.out.println("生产了"+num+"个");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }else {
                try {
                    wait();//阻塞生产者线程
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //消费
    public synchronized void sub(){
        while (true){
            this.notify();//唤醒生产者线程
            if(num>0){//当商品数>0时,开始消费(--)
                System.out.println("消费了"+num+"个");
                num--;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }else{
                try {
                    this.wait();//阻塞消费者线程
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

//消费者线程
public class ConsumerThread extends Thread {
    Counter counter;
    
    //构造方法
    public ConsumerThread(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
            counter.sub();
    }
}
//生产者线程
public class ProducterThread extends Thread {

    Counter counter;

    //构造方法
    public ProducterThread(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
            counter.add();
    }
}

package day2.demo3;

public class Test {

    public static void main(String[] args) {
        //新建Counter任务对象
        Counter counter = new Counter();

        //新建消费者线程
        ConsumerThread consumerThread = new ConsumerThread(counter);
        //新建生产者线程
        ProducterThread producterThread = new ProducterThread(counter);

        consumerThread.start();//启动消费者线程
        producterThread.start();//启动生产者线程
    }
}

运行结果:

在这里插入图片描述

6. 新增创建线程方式

实现Callable接口

package day2.demo4;

import java.util.concurrent.Callable;

public class CallableDemo implements Callable<Byte> {


    /*
        实现Callable与Runnable接口相比
        call()相比于run():
        可以有返回值
        方法可以抛出异常
        支持泛型的返回值
        借助FutureTask类,获取返回结果
     */
    @Override
    public Byte call() throws Exception {

        for (int i = 0; i < 10; i++) {
            System.out.println(i);
        }
        return 10;
    }
}

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
/*
	测试类
*/
public class Test {
    public static void main(String[] args) {

        CallableDemo callableDemo = new CallableDemo();
        //接收任务
        FutureTask<Byte> f = new FutureTask(callableDemo);
        //创建线程
        Thread t = new Thread(f);

        t.start();
        try {
            Byte res = f.get();//获取call()的返回值
            System.out.println("返回值:"+res);//
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/15644.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!