Java多线程快速入门

进程与线程

进程(软件)

  • 是指一个在内存中运行的应用程序,每个进程都有一个独立的内存空间

线程(软件的执行路径)

  • 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程
  • 线程实际上是进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程

线程调度

分时调度

  • 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间

抢占式调度

  • 优先让优先级高的线程使用CPU,如果线程优先级相同,那么会随机选择一个,Java使用的是抢占式调度
  • 多线程并不能提高程序的运行速度,但能够提高程序的运行效率,让CPU的使用率更高

同步与异步

同步:排队执行,效率低但安全

异步:同时执行,效率高但是数据不安全

并发与并行

并发:指多个事件在同一时间段内发生

并行:指多个事件在同一时刻发生(同时发生)

Java实现多线程的两种方式

1. 继承Thread

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("新线程"+i);
        }
    }
}

复写run方法来编写新线程的任务,通过调用start()方法开启新线程

实现Runnable接口

public class RunnableImpl implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("新线程执行了");
        }
    }
}
 //1. 创建一个任务对象
RunnableImpl r = new RunnableImpl();
//2. 创建一个线程并为其分配一个任务
Thread t = new Thread(r);
//3. 执行这个线程
t.start();
for (int i = 0; i < 10; i++) {
  System.out.println("主线程被执行了");
}

实现Runnable接口与继承Thread相比有如下优势

  1. 通过创建任务然后给线程分配的方式来实现多线程,更适合多个线程执行相同任务的情况
  2. 可以避免单继承带来的局限性
  3. 任务与线程本身是分离的,提高了程序的健壮性
  4. 线程池技术,接受Runnable类型的线程,不接受Thread类型的线程

线程阻塞

线程的所有消耗时间的操作都可以看作是阻塞,常见的如文件的读取,读取用户输入等

线程中断

 Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName()+i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        //直接return则线程自杀,不处理则线程继续执行
                        System.out.println("继续执行");
                    }
                }
            }
        });
        t.start();

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
  //给t添加中断标记,会抛出InterruptedException异常,进入上面的catch块,如何处理由程序员在catch块中决定
        t.interrupt();

守护线程

  • 线程分为守护线程和用户线程

  • 守护线程用于守护用户线程,当所有用户线程结束时,守护线程自动死亡

  • 当一个进程不包含任何存活的用户线程时,进行结束

  • 通过对用户线程执行setDaemon(true)将该线程设置为守护线程,在用户线程全部结束时,守护线程自动结束

线程安全问题

  • 多个线程同时使用使用修改同一资源,容易产生线程不安全问题
  1. 同步代码块(隐式锁)

    Object o = new Object();
    synchronized(o){
      
    }
    
  1. 同步方法(隐式锁)

    • 如果不是静态的同步方法它的锁是this(调用它的对象)
    • 如果是静态方法同步方法的锁是该类的.class
public  synchronized boolean 方法名(){
  
}
  1. 显式锁
private Lock l =new ReentrantLock();
l.lock();//调用lock方法上锁
l.unlock();//调用unlock方法解锁

公平锁与不公平锁

公平锁:先来先到,排队执行(A线程先到,解锁后则A线程先获得锁)

不公平锁:抢占式

  • 显式锁中fair参数为true即为公平锁

线程死锁

  • 在任何出现锁的的方法中不要再调用出现锁的方法,不然有可能会出现死锁的情况

多线程通信问题

  • 生产者与消费者问题
    • 生产着消费者问题的场景是:消费者消费生产者生产出并且放在队列里面的产品,如果产品用完了消费者需要等待,如果队列满了,生产者等待。
public class Demo4  {

    /**
     * 多线程通信问题, 生产者与消费者问题
     * @param args
     */
    public static void main(String[] args) {
        Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();
    }

    //厨师
    static class Cook extends Thread{
        private Food f;
        public Cook(Food f) {
            this.f = f;
        }

        @Override
        public void run() {
            for(int i=0;i<100;i++){
                if(i%2==0){
                    f.setNameAndSaste("老干妈小米粥","香辣味");
                }else{
                    f.setNameAndSaste("煎饼果子","甜辣味");
                }
            }
        }
    }
    //服务生
    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f) {
            this.f = f;
        }
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.get();
            }
        }
    }
    //食物
    static class Food{
        private String name;
        private String taste;

        //true 表示可以生产
        private boolean flag = true;

        public synchronized void setNameAndSaste(String name,String taste){
            if(flag) {
                this.name = name;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste = taste;
                flag = false;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public synchronized void get(){
            if(!flag) {
                System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
                flag = true;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

线程的六种状态

  1. NEW
    • 线程刚刚被创建但是还未启动
  2. RUNNABLE
    • 在JVM中执行的线程处于此状态
  3. BLOCKED
    • 被阻塞等待监视器锁定的线程属于此状态
  4. WAITING
    • 无限期等待另一个线程执行特定操作的线程处于此状态
  5. TIMEDWAITTING
    • 正在等待另一个线程执行最多指定等待时间的的线程处于此状态
  6. TERMINATED
    • 已退出的线程处于此状态

带返回值的线程Callable

Runnable 与 Callable

//Callable接口
public interface Callable {
    V call() throws Exception;
} //Runnable接口
public interface Runnable {
    public abstract void run();
}

Callable使用步骤

1. 编写类实现Callable接口 , 实现call方法 
class XXX implements Callable {
    @Override
    public  call() throws Exception {
                return T; 
    }
}

2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask future = new FutureTask<>(callable); 

3. 通过Thread,启动线程
new Thread(future).start();

Runnable 与 Callable的相同点

  • 都是接口 都可以编写多线程程序 都采用Thread.start()启动线程

  • Runnable 与 Callable的不同点 Runnable没有返回值;Callable可以返回执行结果

  • Callable接口的call()允许抛出异常;Runnable的run()不能抛出 Callable获取返回值

  • Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执 行,如果不调用不会阻塞。

线程池

缓存线程池

长度无限制

任务加入后的执行流程

1. 判断线程池是否存在空闲线程
2. 存在则使用
3. 不存在,则创建线程,并放入线程池,然后使用
//创建缓存线程池
ExecutorService service = Executors.newCachedThreadPool();
//向线程池中加入新的任务
service.execute(new Runnable() {
  @Override
  public void run() {
    System.out.println(Thread.currentThread().getName()+"执行了");
  }
});

定长线程池

//创建固定线程数量的线程池 
ExecutorService service = Executors.newFixedThreadPool(10);

单线程线程池

//创建单线程的线程池 
ExecutorService service = Executors.newSingleThreadExecutor();

周期定长线程池

//创建一个有两个线程周期定长线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 1.   定时执行一次
* 参数1. 定时执行的任务
* 参数2. 时长数字
* 参数3. 时长数字的时间单位,TimeUnit的常量指定
*/
service.schedule(new Runnable() {
  @Override
  public void run() {
    System.out.println("被执行了");
  }
},3, TimeUnit.SECONDS);
/**
* 2.   周期性执行任务
* 参数1  任务
* 参数2  延长时间数字(第一次执行是在什么时间以后)
* 参数3  周期时长数字(每隔多久执行一次)
* 参数4  时长数字单位
*/
service.scheduleWithFixedDelay(new Runnable() {
  @Override
  public void run() {
    System.out.println("周期性任务被执行了");
  }
},3,1,TimeUnit.SECONDS);

Lambda表达式

  • 当接口只有一个抽象方法的时候才可以使用Lambda表达式
  • 如果任务只有一行代码,可以同时省略大括号和分号(有大括号必须有分号)
Thread t = new Thread(()->{
    System.out.println("使用Lambda表达式实现的线程任务被执行了。");
});
t.start();

在网络编程中使用多线程

//为每一个连接的客户端开启一个线程,并与其交互
ServerSocket serverSocket = new ServerSocket(55555);
while (true){
  Socket socket = serverSocket.accept();
  new Thread(new Runnable() {
    @Override
    public void run() {
      try {
        OutputStream os = socket.getOutputStream();
        PrintStream ps =  new PrintStream(os);
        ps.println("欢迎来到我的服务器");
        InputStream is = socket.getInputStream();
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String text = br.readLine();
        System.out.println(text);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }).start();
}

你可能感兴趣的:(Java多线程快速入门)