多线程

  • 1.进程与线程的区别

1.根本区别

进程是作为资源分配的单位,而线程是调度与执行的单位

2.开销区别

每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销。

线程可以看做轻量级的进程,同一类线程共享代码和数据空间。

每个线程有独立运行栈和程序计数器(PC),线程切换的开销小

3.包含关系

一个进程可以包含多个线程,线程是进程的一部分。

  • 2.多线程的优缺点

优点:资源利用率更好;程序设计在某些情况下更简单;程序响应更快

缺点:设计更为复杂

  • 3.线程的创建

一共有四种方式

1.继承Thread类+子类对象+start()
2.实现Runnable接口 + 静态代理+start()   -->推荐
3.juc 包下Callable接口  了解
4.使用线程池创建

3.1 继承Thread类 + 子类对象+start()

创建 Thread 子类的一个实例并重写 run 方法, run方***在调用 start() 方法之后自动被执行

public class TestThread { 

public static void main(String[] args) { 

// 创建线程类对象 

SomeThread oneThread = new SomeThread(); 

// 启动线程 

oneThread.start(); 

} 

}

// 创建线程类 

class SomeThead extends Thread{ 

@Override 

public void run() 

{

//do something here 

} 

}

缺点:我们的类已经从一个类继承(如小程序必须继承自 Applet 类),则无法再继承Thread 类,异常只能捕获。

3.2 实现Runnable接口 + 静态代理+start() -->推荐

步骤:

1.创建实现 Runnable 接口的实现类 + 重写 run() 方法

2.创建一个实现类对象

3.利用实现类对象创建Thread类对象

4.启动线程

实现资源共享
接口多实现,类只能单继承
run()方法中定义线程体
如果出现异常只能捕获不能抛出,因为run方法的父类没有抛出异常,子类方法也就不能抛出异常

public class TestThread2 implements Runnable { 

SomeRunnable r1 = new SomeRunnable(); 

Thread thread1 = new Thread(r1); 

thread1.start(); 

Thread thread2 = new Thread(new 

SomeRunnable()); 

thread2.start(); 

}

// 创建Runnable子类 

class SomeRunnable implements Runnable{ 

@Override 

public void run() 

{ 

//do something here 

} 

}

3.3 实现Callable接口实现(了解)

步骤:

  1. 创建实现 Callable 接口的实现类 + 重写 call() 方法

  2. 创建一个实现类对象

  3. 由 Callable 创建一个 FutureTask 对象

  4. 由 FutureTask 创建一个 Thread 对象

  5. 启动线程

    public class CallAbleTest { 
    
    public static void main(String[] args) 
    
    throws Exception{ 
    
    MyCallable callable = new 
    
    MyCallable(); 
    
    // 将Callable包装成FutureTask, 
    
    FutureTask也是一种Runnable 
    
    FutureTask<Integer> futureTask = new 
    
    FutureTask<>(callable); 
    
    // 将FutureTask包装成Thread 
    
    new Thread(futureTask).start(); 
    
    System.out.println(futureTask.isDone()); 
    
    System.out.println(futureTask.get()); 
    
    } 
    
    }
    
    class MyCallable implements Callable<Integer>{ 
    
    @Override 
    
    public Integer call() throws Exception {int sum = 0;
    
    for (int i = 0; i <= 100000; i++) { 
    
    sum += i; 
    
    }
    
    return sum; 
    
    } 
    
    } 

    call方法可以抛出异常
    call方法可以得到返回值

3.4 使用线程池创建

  1. 使用 Executors 类中的newFixedThreadPool(int num) 方法创建一个线程数量为num的线程池

  2. 调用线程池中的 execute() 方法执行由实现Runnable 接口创建的线程;调用 submit() 方法执行由实现 Callable 接口创建的线程

  3. 调用线程池中的 shutdown() 方法关闭线程池

    public class ThreadPoolTest {

    public static void main(String[]args)throws Exception {

    Thread.currentThread().setName("主线程");

    System.out.println(Thread.currentThread().getName() + ": 输出的结果" );

    // 通过线程池工厂创建线程数量为2的线程池

    ExecutorService service =Executors.newFixedThreadPool(2);

    //执行线程,execute()适用于实现Runnable接口创建的线程

    service.execute(new MyThread());

    //submit()适用于实现Callable接口创建的线程

    Future<integer> task =service.submit(new MyCallable()); </integer>

    System.out.println(task.get());

    // 关闭线程池
    service.shutdown();
    }
    }

  • 4.线程的状态
    线程的五种状态
  1. 新建状态

使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  1. 就绪状态

当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  1. 运行状态

如果就绪状态的线程获取 CPU 资源,就可以执行run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  1. 阻塞状态
    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。
    可以分为三种:

等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

同步阻塞:线程在获取 synchronized同步锁失败(因为同步锁被其他线程占用)。

其他阻塞:通过调用线程的 sleep() 或 join() 发出

了 I/O请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪态。

  1. 死亡状态

一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

如何控制线程进入终止状态:
    1.正常运行结束   2.stop ,destroy() 过时 3.通过标识判断
如何进入就绪状态:
    1.start  2.线程切换  3.yield 礼让线程   4.阻塞解除
如何进入阻塞状态
    1.sleep    2.join 插队   3.wait  4.IO操作

    注意:
    一个线程如果进入阻塞状态,阻塞解除没有办法直接恢复到运行,会直接恢复就绪。
    一个线程如果一旦终止,没有办法恢复。

4.1 Sleep方法:抱着资源睡觉

sleep() 方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态。当一个执行到sleep方法,当前线程就会休眠指定时间,在休眠过程中,让出cpu的资源。

Thread.sleep(1000);//里面放一个int型的整数 表示休眠多少ms 1s=1000ms

4.2 yield方法:礼让线程

yield() 方法和 sleep() 方法类似,也不会释放“锁标志”,区别在于,它没有参数,即 yield() 方法只是使当前线程重新回到可执行状态,所以执行yield() 的线程有可能在进入到可执行状态后马上又被执行。让出CPU的使用权,从运行态直接进入就绪态。让CPU重新挑选哪一个线程进入运行状态

public class ThreadStateDemo08 implements Runnable{
    public static void main(String[] args) {
        new Thread(new ThreadStateDemo08(),"A").start();
        new Thread(new ThreadStateDemo08(),"B").start();
    }


    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"开始了");
        Thread.yield(); //礼让
        System.out.println(Thread.currentThread().getName()+"结束了");
    }
}

4.3 join方法

join方法使只有当前调用的该方法线程结束后,其他线程才会继续往后执行,才会继续往后执行。

public class JoinDemo09 {
    public static void main(String[] args) {
        new Thread(new Father()).start();
    }
}

class Father implements Runnable{

    @Override
    public void run() {
        System.out.println("想抽烟了... 叫儿子去买烟..");
        System.out.println("给儿子100块钱... 煊赫门");
        //创建儿子线程
        Thread th = new Thread(new Son());
        th.start();

        //插队
        try {
            th.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("接过烟,抽一***过来了");
        System.out.println("把零钱给儿子...");
    }
}

class Son implements Runnable{

    @Override
    public void run() {
        System.out.println("接过钱去买烟....");
        System.out.println("路边看到一个游戏厅,进去玩10s钟");
        System.out.println("赶紧去买烟...");
        System.out.println("把烟给老爸...");
    }
}

如果没有加join方法,父亲可能还没等儿子把烟递回来就抽烟了,不符合逻辑

5.线程的基本信息

判断线程状态Thread.State
getState() 获取线程状态  返回值是一个枚举类型

线程的优先级

线程的优先级用数字表示,范围是1-10。如果没有为线程赋予优先级,则线程的缺省值是5

void setPriority(int newPriority); 

注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调用优先级低的线程。

  • 6.线程的同步与死锁

    线程安全问题:

      当多线程共享同一份资源的时候,才有可能存在线程不安全问题
    
      同步锁: synchronized
          同步方法 : 同步静态方法  同步成员方法
          同步块{} : synchronized(this|类名.class|资源){
                  代码段...
          }
    
          {} -> 排队执行的代码块
          this|类名.class|资源 : 线程要求的执行{}中代码的执行条件
    
         注意:
          同步的代码范围如果 太大,效率低,如果太小,锁不住
          锁不变的内容(this|资源..|类..)

6.1 同步类

类的class对象,相当于把整个类锁住了,效率相对较低

public class SynDemo11 implements Runnable{

    int tickets  = 100;


    @Override
    public void run() {
        //A B  C
        while(true){
            //停止循环的条件
            //A B  C
            synchronized (SynDemo11.class)//锁住这个类的所有资源 如果有另一个类的对象想拿,必须得等锁释放
            {
                if(tickets<=0){
                    break;
                }
                //A B  C
                //买票
                System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets--);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        //资源
        SynDemo11 web = new SynDemo11();
        //创建线程
        Thread th1 = new Thread(web,"张绍杰");
        Thread th2 = new Thread(web,"何志豪");
        Thread th3 = new Thread(web,"阮志志");

        //线程的开启
        th1.start();
        th2.start();
        th3.start();
    }
}

6.2同步对象this

this,相当于把调用成员方法的对象,相当于锁住了整个对象,整个对象的所有 资源都锁住了,如果只想锁住某一个资源,只锁住这个资源就可以 -->推荐锁资源

public class SynDemo12 implements Runnable{

    int tickets  = 100;


    @Override
    public void run() {
        //A B  C
        while(true){
            //停止循环的条件
            //A B  C
            synchronized (this){  //当前调用成员方法的对象
                if(tickets<=0){
                    break;
                }
                //A B  C
                //买票
                System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets--);
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        //资源
        SynDemo12 web = new SynDemo12();
        //创建线程
        Thread th1 = new Thread(web,"张绍杰");
        Thread th2 = new Thread(web,"何志豪");
        Thread th3 = new Thread(web,"阮志志");

        //线程的开启
        th1.start();
        th2.start();
        th3.start();
    }
}

6.3同步资源

资源,一定要锁不变的东西,引用数据类型 -->自定义的 引用数据类型的地址

如果不是自定义的引用数据类型,可能会出现线程不安全

public class SynDemo13 implements Runnable{
    //资源  100张 票
    Ticket tickets  = new Ticket();


    @Override
    public void run() {
        //A B  C
        while(true){
            //停止循环的条件
            //A B  C
            synchronized (tickets){  //当前调用成员方法的对象
                if(tickets.num<=0){
                    break;
                }
                //A B  C
                //买票
                System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets.num--);
                try {
                    Thread.sleep(20);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        //资源
        SynDemo13 web = new SynDemo13();
        //创建线程
        Thread th1 = new Thread(web,"张绍杰");
        Thread th2 = new Thread(web,"何志豪");
        Thread th3 = new Thread(web,"阮志志");

        //线程的开启
        th1.start();
        th2.start();
        th3.start();
    }
}

//票
class Ticket{
    int num = 100;
}
  • 7.死锁

死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。

如何解决死锁问题:

  1. 往往是程序逻辑的问题。需要修改程序逻辑。
  2. 尽量不要同时持有两个对象锁。
全部评论

相关推荐

头像
昨天 15:56
东北大学 Java
点赞 评论 收藏
分享
点赞 评论 收藏
分享
WesterlyDrift:你拍完照又把选项改回去的样子真的很狼狈😤😤
点赞 评论 收藏
分享
1 6 评论
分享
牛客网
牛客企业服务