Java基础 之 多线程的简单介绍

前言

在学多线程之前,需要将什么是程序进程线程 搞清楚

  • 程序(program):是为了完成特定任务、用某种语言编写成的指令集合,即一段静态的代码

  • 进程(process): 是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程。存在自身的产生、运行、消亡的过程,即一个生命周期,就像一个运行的浏览器。进程作为资源的分配单位,系统在运行的时候会为每一个进程分配不同的内存区域。

  • 线程(thread):进程可以细分为为线程,是一个程序内部的一套执行路径。

    • 一个进程如果支持多个线程同时运行,则就是支持多线程。

      • 线程作为调度和执行的单位,每个线程都拥有独立的运行栈程序计数器
    • 其中一个进程中的多个线程可以共享同一个内存单元/内存地址空间,即可以公用同一个对象。使用多线程主要的优点和缺点如下

      • 好处:使得线程之前通信更加简便、高效
      • 坏处:可能会出现线程安全的问题。

生命周期

java程序的生命周期有5个状态:新建就绪运行阻塞死亡

线程

什么是多线程

多线程简单的来说就是在一个程序中运行的多个线程

就比如电脑中有多个应用同时运行。这一个个程序就相当于一个个的进程

如何创建一个线程

在java中使用多线程有四种方式

  • 继承Thread类
    1. 定义子类继承Thread类
    2. 子类重写Thread类中的run()方法,在我们调用子类对象的start()就会自动调用run()
    3. 创建子类对象
    4. 调用线程对象(即子类对象)的start()启动线程,这个时候就会在主线程中运行一个副线程(即我们创建的线程对象)。
  • 实现Runnable接口
    1. 定义子类,是按Runnalbe接口
    2. 在子类中重写run()方法
    3. 利用Thread类的含参构造器构造子类,将实现Runnable接口的实现类对象作为参数传递到Thread类的含参构造器中
    4. 调用Thread类的start()方法启动线程.
  • 实现Callable接口
    • 与实现Runnable接口相似,不过它可以抛出异常,支持泛型,且在借助FutureTask类下,run()可以有返回值,并获取结果
  • 使用线程池

虽然是有4种方式,但是都是需要重写父类/接口中的run()方法.

继承Thread类
public class Test1 {
    public static void main(String[] args) {
    ThreadTest test = new ThreadTest();
    test.start();//调用start()后,就会自动调用run()里面的内容

    }
}
class ThreadTest extends Thread{
    @Override
    public void run() {
        System.out.println("线程启动!");
    }
}

实现Runnable接口
public class Test1 {
    public static void main(String[] args) {
        Thread test1 = new Thread(new ThreadTest());
        test1.start();

    }
}
class ThreadTest implements Runnable{
    @Override
    public void run() {
        System.out.println("实现Runnable接口");
    }
}

实现Callable接口

在jdk5.0以后,新增了利用Callable接口来完成线程的创建的方式。其中先来说一说为什么要加入这种方式来创建多线程。

在之前的两种创建线程的方式当中,都是通过重写run()来完成线程的创建,但是run()是无返回值的。而在新增的通过实现Callable接口来创建线程的方式,是通过重写call()call()run()方法具有一样的功能,相似的用法,但call()可以有返回值

不过,通过实现Callable接口方式来创建线程的方式还需要借助FutureTask类,其中,该类实现了也Runnable接口

public class CallableTest {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumPrint numPrint = new NumPrint();
		//4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask<Integer> futureTask = new FutureTask(numPrint);
		//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用statr()
        new Thread(futureTask).start();

        try {
            //通过FutureTask类中的get()来获得call()的返回值
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
//1.创建一个实现Callable的实现类
class NumPrint implements Callable<Integer> {
    //2.实现Call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            sum = sum + i;
            System.out.println(i);
        }
        return sum;
    }
}
线程池

jdk5.0以后,也新增了一个用线程池创建线程的方式

线程池顾名思义,就是一个可以容纳很多线程的池子,当我们创建线程的时候,就可以从线程池中取。

优点

  • 提高响应速度(减少了创建线程的时间)
  • 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
  • 便于管理

线程池中主要的几个属性

    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务是最多保持多长时间后会终止
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(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}
public class ThreadPool {
    public static void main(String[] args) {
        //1.提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor serice1 = (ThreadPoolExecutor) service;
        //设置线程池的属性
//        System.out.println(service.getClass());
//        serice1.setCorePoolSize(15);
//        serice1.setKeepAliveTime(10);

        //2.执行指定的线程的操作。需要提供实现Runnalbe接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合于用于Runnable
        service.execute(new NumberThread1());//适合于用于Runnable

//        service.submit(Callable callable);//适合使用与Callalbe
        //3.关闭连接池
        service.shutdown();

    }
}

Thread中的方法

主要的使用的方法

方法描述
viod start()启动当前线程,并调用run()方法
vod run()通常需要重写Thread类中的此方法,将要创建的执行操作声明在此方法之中
static Thread currentThread返回执行当前代码的线程
String getName()获得当前线程的名字
void setName()设置当前线程的名字
static void yield()释放当前CPU的执行权
void join()在线程a中调用线程b的join(),此时线程a就进入了阻塞状态,直到线程b完全执行后,线程a才会结束阻塞状态
void sleep(long millitime)让当前线程"睡眠"millitime毫秒,在当前millitime毫秒中,当前线程时阻塞状态的
boolean isAlive判断当前线程是否存活
int getPriority()返回此线程的状态
void setPriority(int P)设置线程的优先级

线程优先级

高优先级的线程会抢占低优先级cpu的执行权,但这只是从概率上来说,高优先级的线程高概率的情况下先被执行,并不意味这需要先执行完优先级的线程,才会执行低优先级的线程。

优先级最高有是MAX_PRIORITY:10

​ 最低是MIN_PRIORITY:1

​ 默认的优先级是:NORM_PRIORITY:5

public class Test1 extends Thread {
    public static void main(String[] args) {
        Thread test1 = new Thread(new ThreadTest());
        test1.setName("线程一");
        test1.start();//我这里没有设置它的优先级

    }
}
class ThreadTest implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i<=100;i++){
            if (i % 2 == 0)
                System.out.println(Thread.currentThread().getName()+"\t" + i + " 优先级是:"+Thread.currentThread().getPriority());
        }
    }
}

多线程

多线程的创建

要实现多线程,就需要不同的线程有一个共享的资源

例如我们在网上抢票,多个窗口出售就那么一些票。这就可以看作是多线程。

其中通过实现Runnable接口的方式与继承Thread类的方式略有不同,不过效果都一样

通过实现Runnable接口
public class Test1{
    public static void main(String[] args) {
        ThreadTest test = new ThreadTest();//由于是实现Runnable接口来创建线程,我们就需要将总票数固定为100张,就需要创												建一个对象,然后将这对象作为参数传给Thread的构造器。
        Thread window1 = new Thread(test);
        Thread window2 = new Thread(test);

        window1.setName("窗口1");
        window2.setName("窗口2");

        window1.start();
        window2.start();
    }
}
class ThreadTest implements Runnable{//这里实现Runnable接口
    //总票数只有100张
    int ticket = 100;
    @Override
    public void run() {
      while(ticket>0) {
          System.out.println(Thread.currentThread().getName() + "售出" + ticket + "张票");
          ticket--;
      }
        }
    }
继承Thread类
public class Test2 {
    public static void main(String[] args) {
        ThreadTest1 threadTest1 = new ThreadTest1();
        ThreadTest1 threadTest2 = new ThreadTest1();

        threadTest1.setName("1号窗口");
        threadTest2.setName("2号窗口");

        threadTest1.start();
        threadTest2.start();
    }
}

class ThreadTest1 extends Thread {
    //总票数100
    static int ticket = 100;//这里的票数需要声明为static,才能被多个线程共享同一个资源
    @Override
    public void run() {
        while(ticket>0) {
            System.out.println(Thread.currentThread().getName() + "售出" + ticket + "张票");
            ticket--;
        }
    }
}

输出结果

在这里我们会发现有一些票重复出现,这就涉及到线程安全问题

线程安全问题

怎么解决

出现这样的原因是因为我们cpu执行的速度很快,当我们cpu处理一个进程里面的一个线程的时候,这个线程的内容还没结束,另一个线程就进来了,就会造成上面的这个现象。

我们需要使用多线程的时候,尤其是这种情况的时候,需要使用到java的同步机制

当我们遇到这种问题的时候,可以在某一个线程访问共享资源的时候,给共享资源上一道锁(需要同一道锁),防止还有另外的线程来访问这个共享资源。这道锁叫做同步监听器

同步锁:

  • 任何对象都可以作为同步锁。
  • 同步方法的锁:静态方法(类名.class),非静态方法(this)
  • 同步代码块的锁:随便,通常为this或类名.class

原来的具体的操作有三种方法

在jdk5.0以后,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

方法一:同步代码块
synchronized(同步锁){
	需要被同步的代码
}

还是以上面买票的例子

public class Test1 {
    public static void main(String[] args) {
        ThreadTest test = new ThreadTest();

        Thread window1 = new Thread(test);
        Thread window2 = new Thread(test);

        window1.setName("窗口1");
        window2.setName("窗口2");

        window1.start();
        window2.start();
    }
}

class ThreadTest implements Runnable {
    int ticket = 10000;//如果设置100张票的话,因为cpu运行效率老快了,效果不明显

    @Override
    public void run() {
        while (ticket > 0) {
            synchronized (this) {//在这里添加。代码块,这里的同步锁是this,也就是ThreadTest的对象。这里保证了两个线程共用								  了一个锁。
                System.out.println(Thread.currentThread().getName() + "售出" + ticket + "张票");
                ticket--;
            }
        }
    }
}
//这里不可以将synchronized(){}移到while循环的外面,因为这样的话如果当线程1启动后,由于有同步锁的存在,其他线程就进入不了while循环,也就参与不了这个过程,就变成了全程线程1在执行了。

方法二:同步方法
public synchronized void show(String name){
	需要被同步的代码
}
public class Test1 {
    public static void main(String[] args) {
        ThreadTest test = new ThreadTest();

        Thread window1 = new Thread(test);
        Thread window2 = new Thread(test);

        window1.setName("窗口1");
        window2.setName("窗口2");

        window1.start();
        window2.start();
    }
}

class ThreadTest implements Runnable {
    int ticket = 10000;
    @Override
    public void run() {
        while (ticket > 0) {
            sell();
        }
    }
    public synchronized void sell(){
        System.out.println(Thread.currentThread().getName() + "售出" + ticket + "张票");
        ticket--;
    }
}

我们需要把握synchronized的圈定范围,不能大了也不能小了。范围太小会没锁住所有的有安全问题的代码;范围太大会导致部分功能失效。

方法三:Lock锁

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

public class Test1 {
    public static void main(String[] args) {
        ThreadTest test1 = new ThreadTest();
        ThreadTest test2 = new ThreadTest();
        test1.setName("线程1");
        test2.setName("线程2");
        test1.start();
        test2.start();

    }
}

class ThreadTest extends Thread{
    static int number = 100;
    private final ReentrantLock lock = new ReentrantLock();
    public void run(){
        while(true){
            lock.lock();
            try {
                if (number > 0){
                    System.out.println(currentThread().getName() + " " + number);
                    number--;
                }else{
                    break;
                }
            }
            finally {
                lock.unlock();
            }
        }

    }
}

死锁问题

死锁就是不同的线程分别占用对方所需要的资源但是不是释放cpu执行权,都在等待对方放弃自己所需要的同步资源 ,就形成了线程的死锁。

public class DeadLock {
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();

        Thread test1 = new Thread(thread1);
        Thread test2 = new Thread(thread2);

        test1.start();
        test2.start();
    }

}
//第一个线程Thread1
class Thread1 implements Runnable {
    static StringBuffer str1 = new StringBuffer();
    static StringBuffer str2 = new StringBuffer();

    @Override
    public void run() {
        //这里先用str1作为锁,然后用str2作为锁
        synchronized (str1) {
            str1.append("a");
            str2.append(1);
        }
        synchronized (str2) {
            str1.append("b");
            str2.append(2);
            System.out.println(str1);
            System.out.println(str2);

        }
    }
}
//第二个线程Thread2
class Thread2 implements Runnable {
    //调用Thread1的str1与str2。
    StringBuffer str2 = Thread1.str2;
    StringBuffer str1 = Thread1.str1;
    @Override
    public void run() {
        //这里先用str2作为锁,然后用str1作为锁,与线程Thread1相反
        synchronized (str2) {
            str1.append("c");
            str2.append(3);
        }
        synchronized (str1) {
            str1.append("d");
            str2.append(4);
            System.out.println(str1);
            System.out.println(str2);
        }
    }
}

这样运行是不会出现死锁问题的,死锁是线程拿着锁不放,先看看结果

那么我就让线程里面进入第二个锁之前,都让他们睡着

public class DeadLock {
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();

        Thread test1 = new Thread(thread1);
        Thread test2 = new Thread(thread2);

        test1.start();
        test2.start();
    }

}
//第一个线程Thread1
class Thread1 implements Runnable {
    static StringBuffer str1 = new StringBuffer();
    static StringBuffer str2 = new StringBuffer();

    @Override
    public void run() {
        //这里先用str1作为锁,然后用str2作为锁
        synchronized (str1) {
            str1.append("a");
            str2.append(1);
        }
        //线程睡着了,sleep()是让线程睡着,但是不会释放锁
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (str2) {
            str1.append("b");
            str2.append(2);
            System.out.println(str1);
            System.out.println(str2);

        }
    }
}
//第二个线程Thread2
class Thread2 implements Runnable {
    //调用Thread1的str1与str2。
    StringBuffer str2 = Thread1.str2;
    StringBuffer str1 = Thread1.str1;
    @Override
    public void run() {
        //这里先用str2作为锁,然后用str1作为锁,与线程Thread1相反
        synchronized (str2) {
            str1.append("c");
            str2.append(3);
        }
        //线程睡着了
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (str1) {
            str1.append("d");
            str2.append(4);
            System.out.println(str1);
            System.out.println(str2);
        }
    }
}

然后就发现什么都打印不出来,就说明都获取不到线程里面的第二个锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值