java多线程教程

Posted 施莱

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java多线程教程相关的知识,希望对你有一定的参考价值。

多线程

文章目录

1.程序、进程、多线程

1.1、程序

​ 说起进程,就不得不说下程序,程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。

1.2、进程

​ 进程则是执行程序的一次执行过程,他是一个动态的概念,是系统资源分配的单位。

1.3、线程

​ 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程是CPU调度和执行的单位

注意:很多 多线程是模拟出来的,真正的多线程是指多个cpu,即多核,如服务器,如果是模拟出来的多线程,即在一个cpu的情况下,在同一时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

​ 每一个java应用程序都有一个主线程。

2、Thread类

2.1.1、构造方法

// 分配一个新的线程对象。
public Thread()
// 分配一个指定名字的新的线程对象。
public Thread(String name)
// 分配一个带有指定目标新的线程对象。
public Thread(Runnable target)
// 分配一个带有指定目标新的线程对象并指定名字。
public Thread(Runnable target,String name)

2.1.2、常用方法

// 设置线程名称
void setName(String name)
// 获取当前线程名称。
public String getName()
// 导致此线程开始执行; Java虚拟机调用此线程的run方法。
public void start()

// 此线程要执行的任务在此处定义代码。
public void run()
// 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
public static void sleep(long millis)
// 返回对当前正在执行的线程对象的引用。
public static Thread currentThread()

3、多线程的创建方式

3.1、方式一:继承Thread类

1.创建一个集成于Thread类的子类 (通过ctrl+o(override)输入run查找run方法)
2.重写Thread类的run()方法
3.创建Thread子类的对象
4.通过此对象调用start()方法

run start 区别

start方法的作用:1.启动当前线程 2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)
调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。

run方法的作用:在主线程中调用以后,直接在主线程一条线程中执行了该线程中run的方法。(调用线程中的run方法,只调用run方法,并不新开线程)

总结:我们不能通过run方法来新开一个线程,只能调用线程中重写的run方法(可以在线程中不断的调用run方法,但是不能开启子线程,即不能同时干几件事),start是开启线程,再调用方法(即默认开启一次线程,调用一次run方法,可以同时执行几件事)

代码实现:

package Thread1;

public class ThreadTest1 extends Thread

    @Override
    public void run() 
        for (int i=0;i<=2;i++)
            System.out.println("当前线程名:"+ThreadTest1.currentThread().getName());
        
    

    public static void main(String[] args) 

        ThreadTest1 threadTest1 = new ThreadTest1();
        ThreadTest1 threadTest2 = new ThreadTest1();
        ThreadTest1 threadTest3 = new ThreadTest1();

        threadTest1.setName("线程1");
        threadTest2.setName("线程2");
        threadTest3.setName("线程3");

        threadTest1.start();
        threadTest2.start();
        threadTest3.start();

    


运行结果:
当前线程名:线程1
当前线程名:线程3
当前线程名:线程2
当前线程名:线程3
当前线程名:线程1
当前线程名:线程1
当前线程名:线程3
当前线程名:线程2
当前线程名:线程2

进程已结束,退出代码 0

3.2、方式二:实现Runable接口

1.创建一个实现了Runable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类中的构造器中,创建Thread类的对象
5.通过Thread类的对象调用start()

代码实现:

package Rnnable1;

import sun.security.mscapi.CPublicKey;

public class RunnableTest1 implements Runnable

    int waterAmount;//用int变量模拟水量

    public void setWaterAmount(int w)
        waterAmount=w;
    

    @Override
    public void run() 
       while (true)
           String thread = Thread.currentThread().getName();
           System.out.println(thread);
           if (thread.equals("dog"))
               System.out.println("家狗喝水");
               waterAmount=waterAmount-2;
           
           else if (thread.equals("cat"))
               System.out.println("家猫喝水");
               waterAmount=waterAmount-2;
           
           System.out.println("剩:"+waterAmount);

           if (waterAmount<=0)
               return;
           
       

    

    public static void main(String[] args) 

        RunnableTest1 runnableTest1 = new RunnableTest1();
        runnableTest1.setWaterAmount(10);

        Thread thread = new Thread(runnableTest1);
        Thread thread2 = new Thread(runnableTest1);
        thread.setName("dog");
        thread2.setName("cat");

        thread.start();
        thread2.start();

    


运行结果:
cat
家猫喝水
剩:8
cat
家猫喝水
剩:6
cat
家猫喝水
剩:4
cat
家猫喝水
dog
剩:2
cat
家猫喝水
剩:0
家狗喝水
剩:-2

3.3、比较方式一和方式二

开发中,优先选择实现Runnable接口的方式
原因:

1:实现的方式没有类的单继承性的局限性

继承Thread不灵活,因为Java只允许单继承(继承了Thread之后如果再想继承别的类就不可能了),实现Runnable接口避免单继承的局限性

2:实现的方式更适合用来处理多个线程有共享数据的情况

联系:Thread也是实现自Runnable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中

4、多线程静态代理模式

  • 代理对象和代理要继承同一接口
  • 代理对象要代理真实角色

好处:

  • 代理对象可以做真实对象做不了的事情
  • 真实对象专注于做自己的事情

代码实现:

package Rnnable1;

public class test02 


    public static void main(String[] args) 
        
        You you = new You();
        Marry daiLi = new daiLi(you);
        daiLi.marry();
        
        // test01 test01 = new test01();
        //Thread thread2 = new Thread(test01);
        //thread2.start();

    


    interface Marry
        void marry();
    

    class You implements Marry
        @Override
        public void marry() 
            System.out.println("结婚了");
        

    



    class daiLi implements Marry

        private Marry target;

        public daiLi(Marry target)
            this.target=target;
        

        public void before()
            System.out.println("准备");
        
        @Override
        public void marry() 
            before();
            this.target.marry();
            after();
        
        public void after()
            System.out.println("结束");
        

    

5、线程的状态和生命周期

51、线程的状态图

Java中的线程的生命周期大体可分为5种状态。

  1. 新建(NEW):新创建了一个线程对象。

  2. 可运行(RUNNABLE,也叫就绪):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权 。

  3. 运行(RUNNING):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码。

  4. 阻塞(BLOCKED):阻塞状态是指线程因为某种原因放弃了cpu 使用权,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:

    (一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
    (二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。

    (三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。

  5. 死亡(DEAD):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生。

5.2、线程方法

setPriority(int newPriority)更改线程的优先级
static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠
void join()等待该线程终止,其他线程才能 执行
static void yield()暂停当前正在执行的线程对象,并执行其他线程
void interrupt()中断线程(最好别用这个方式)
boolean isAlive()测试线程是否处于活动状态

1、停止线程

不推荐使用JDK提供的stop() destroy() 方法 [已废弃]

推荐线程自己停止下来,建议使用一个标志位进行终止变量,当flag=false,则终止线程运行

代码实现:

package MutilThreading;

public class test02 implements Runnable

    private boolean flag=true;

    @Override
    public void run() 
        int i=0;
        while (flag)
            System.out.println("run。。。thread"+i++);
        

    


    public void flag()
        this.flag=false;
    

    public static void main(String[] args) 
        test02 test02 = new test02();
        Thread thread = new Thread(test02);
        thread.start();

        for (int i = 0; i < 100; i++) 
            System.out.println("main"+i);
            if (i==50)
                test02.flag();
                System.out.println("该线程停止");
            
        
    

2、线程休眠

  • sleep 指定当前线程阻塞的毫秒数
  • sleep 存在异常
  • sleep 时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁

代码实现:

package Rnnable1;

//模拟网络延时  放大问题的发生性
public class test01 implements Runnable

    int i=10;

    @Override
    public void run() 
        while (true)
            try 
                Thread.sleep(200);
             catch (InterruptedException e) 
                e.printStackTrace();
            
            System.out.println(Thread.currentThread().getName()+"拿了第"+i--+"票");
            if (i<0)
                break;
            
        
    

    public static void main(String[] args) 
        test01 test01 = new test01();

        Thread thread = new Thread(test01);
        Thread thread2 = new Thread(test01);
        Thread thread3 = new Thread(test01);

        thread.start();
        thread2.start();
        thread3.start();
    
    

3、线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让cpu 重新调度,礼让不一定成功,看cpu 心情

3、线程强制执行

package MutilThreading;

public class testJoin implements Runnable

    @Override
    public void run() 
        for (int i=0;i<=1000;i++)
            System.out.println(Thread.currentThread().getName()+"-->"+i);
        
    

    public static void main(String[] args) throws InterruptedException 

        testJoin testJoin = new testJoin();
        Thread thread = new Thread(testJoin);

        thread.start();

        for (int i=1;i<500;i++)
            if (i==150)
                thread.join();//插队,让上面那个线程执行完毕,再执行主线程
            
            System.out.println("主线程"+"-->"+i);
        

    


4、线程的优先级

  • java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行

  • 线程的优先级用数字表示,范围从1-10

  • Thread.MIN_PRIORITY =1;

  • Thread.MAX_PRIORITY=10;

  • Thread.NORM_PRIORITY=5;

  • 使用一下方式改变或获取优先级

  • getPriority() setPriority(int xxx)

优先级低只是意味着获得调度的概率低,并不是优先级低的就不会被调用,这都是看cpu 的调度

package MutilThreading;

public class testPriority implements Runnable

    @Override
    public void run() 
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    


    public static void main(String[] args) 
        testPriority testPriority = new testPriority();

        Thread thread = new Thread(testPriority);
        Thread thread2 = new Thread(testPriority);
        Thread thread3 = new Thread(testPriority);
        Thread thread4 = new Thread(testPriority);
        Thread thread5 = new Thread(testPriority);


        thread.start();

        thread2.setPriority(8);
        thread2.start();

        thread3.setPriority(Thread.MAX_PRIORITY);
        thread3.start();

        thread4.setPriority(Thread.MIN_PRIORITY);
        thread4.start();

        thread5.setPriority(2);
        thread5.start();
        
    



//运行结果
Thread-2-->10
Thread-3-->1
Thread-0-->5
Thread-1-->8
Thread-4-->2

5.3、守护线程

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

虚拟机必须确保用户线程执行完毕

虚拟机不用等待守护线程执行完毕 如后台记录操作日志、监控内存、垃圾回收等待。。

调用方法 setDaemon();

6、线程同步

6.1、线程同步:多个线程操作同一个资源

线程同步就是并发

6.2、并发与并行

并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
并发:同一个对象多个线程同时操作,一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事

6.3、线程的安全问题:

什么是线程安全问题呢?

线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

上述例子中:创建三个窗口卖票,总票数为100张票
1.卖票过程中,出现了重票(票被反复的卖出,ticket未被减少时就打印出了)错票。
2.问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也来操作车票。(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去)

生动理解的例子:有一个厕所,有人进去了,但是没有上锁,于是别人不知道你进去了,别人也进去了对厕所也使用造成错误。

3.如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到此线程的生命周期结束
4.在java中,我们通过同步机制,来解决线程的安全

6.4、同步代码块和同步方法

1.为什么要使用synchronized

在并发编程中存在线程安全问题,主要原因有:
1.存在共享数据
2.多线程共同操作共享数据。
关键字synchronized可以保证在同一时刻,只有一个线程可以执行某个方法或某个代码块,同时synchronized可以保证一个线程的变化可见(可见性),即可以代替volatile。

2.实现原理

synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性

3.synchronized的三种应用方式

Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:

  • 普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
  • 静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
  • 同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

4.synchronized的作用

Synchronized是Java中解决并发问题的一种最常用最简单的方法 ,他可以确保线程互斥的访问同步代码

6.4.1、同步方法

public synchronized void method(int args)

​ synchronized 方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized 方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行

​ 缺陷:若将一个大的方法申明为synchronized 将会影响效率

代码实现:

package MutilThreading;

public class buyTickets implements Runnable 


    public static void main(String[] args) 

        buyTickets buyTickets = new buyTickets();

        Thread thread = new Thread(buyTickets,"黄牛党");
        Thread thread2 = new Thread(buyTickets,"老人");
        Thread thread3 = new Thread(buyTickets,"年轻人");

        thread.start();
        thread2.start();
        thread3.start();
    

    private int ticket=10;
    boolean flag=true;
    @Override
    public void run() 
        while (flag)
            try 
                buy();
             catch (InterruptedException e) 
                e.printStackTrace();
            
        
    

    //synchronized  同步方法  锁的是 this
    public synchronized void buy() throws InterruptedException 
        if (ticket==Java-多线程实验

用java编写多线程银行ATM 模拟程序

java多线程回顾3:线程安全

java线程之线程同步

java多线程有几种实现方法?线程之间如何同步

Java线程教程:使用Java创建线程和多线程