ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java多线程小白入门笔记

2021-09-21 16:33:38  阅读:88  来源: 互联网

标签:java 入门 Thread void start 线程 new 多线程 public


java多线程小白入门

继承Thread重写run()方法的方式

public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1=new MyThread();
        MyThread my2=new MyThread();

        my1.start();
        my2.start();
    }
}
public class MyThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println(i);

        }
    }
}

为什么要重写run()方法?

run()用来封装被线程执行的代码。

run()和start()的区别?

run()简介

run()是用来封装线程执行的代码,直接调用的话就相当于普通方法的执行。

start()简介

启动线程,由JVM调用此线程的run()方法。

设置和获取线程名称

Thread类中设置和获取线程名称的方法:

  • void setName(String name):将此线程的名称更改为参数name
  • String getName():返回此线程的名称
public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1=new MyThread();
        MyThread my2=new MyThread();
        my1.setName("my1线程");
        my2.setName("my2线程");

        my1.start();
        my2.start();
    }
}
public class MyThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println(getName()+"输出:"+i);

        }
    }
}

设置线程名称并获取
上面使用的是无参方法,也可以使用有参方法直接将name传进去。如此需要在MyThread中添加一个有参构造方法。如下:

public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1=new MyThread("my1线程");
        MyThread my2=new MyThread("my2线程");
        my1.start();
        my2.start();
    }
}
public class MyThread extends Thread{
    public MyThread(String name){
        super(name);
    }
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println(getName()+"输出:"+i);

        }
    }
}

如何查看Main方法的线程名称呢?

使用currentThread()方法可以返回当前正在执行的线程的引用。

System.out.println(Thread.currentThread().getName());

如此即可获得主线程的名称。
获取主线程的名称

线程优先级

Java使用的是抢占式线程调度模型

Thread类中设置和获取优先级的方法

public final int getPriority():返回此线程的优先级

public final void setPriority(int newPriority):更改此线程的优先级

线程的优先级范围1-10

可以通过Thread.MAX_PRIORITY,Thread.MIN_PRIORITY来查看。
线程优先级高代表获取cpu时间片的几率高。

线程控制

线程控制的方法

  • static void sleep(long mills):使当前正在执行的线程停留(暂停执行)指定的毫秒数
  • void join():等待这个线程死亡
  • void setDaemon(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出。

sleep示例

public class ThreadSleepDemo {

    public static void main(String[] args) {
        ThreadSleep ts1=new ThreadSleep();
        ThreadSleep ts2=new ThreadSleep();
        ThreadSleep ts3=new ThreadSleep();

        ts1.setName("ts1");
        ts2.setName("ts2");
        ts3.setName("ts3");

        ts1.start();
        ts2.start();
        ts3.start();
        ```
        ```
        public class ThreadSleep extends Thread{
    @Override
    public void run() {
        for(int i=0;i<99;i++){
            System.out.println(getName()+":"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

join用法

public class ThreadJoinDemo {

    public static void main(String[] args) {
        ThreadJoin tj1=new ThreadJoin();
        ThreadJoin tj2=new ThreadJoin();
        ThreadJoin tj3=new ThreadJoin();
        tj1.setName("tj1");
        tj2.setName("tj2");
        tj3.setName("tj3");

        tj1.start();
        try {
            tj1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tj2.start();
        tj3.start();
    }

}
public class ThreadJoin extends Thread{
    @Override
    public void run() {
        for(int i=0;i<99;i++){
            System.out.println(getName()+":"+i);
        }
    }
}

Daemon方法

public class ThreadDaemonDemo {
    public static void main(String[] args) {
        ThreadDaemon td1=new ThreadDaemon();
        ThreadDaemon td2=new ThreadDaemon();

        td1.setName("td1");
        td2.setName("td2");
        Thread.currentThread().setName("tediouscat");
        td1.setDaemon(true);
        td2.setDaemon(true);

        td1.start();
        td2.start();
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

public class ThreadDaemon extends Thread{
    @Override
    public void run() {
        for(int i=0;i<99;i++){
            System.out.println(getName()+":"+i);
        }
    }

}

线程的生命周期

线程的生命周期

实现Runnable接口的方式实现多线程

创建线程的另一个方法是是声明一个实现Runnable接口的类,然后那个类实现了Run()方法,然后可以实现类的实例。在创建Thread时作为参数传递,并启动。

步骤:

  • 定义一个类MyRunnable实现Runnable接口
  • 在Runnable类中重写run()方法
  • 创建MyRunnable类的对象
  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
  • 启动线程
public class MyRunnableDemo {
    public static void main(String[] args) {
        MyRunnable my=new MyRunnable();
        Thread t1=new Thread(my,"线程1");
        Thread t2=new Thread(my,"线程2");

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

    }
}
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<99;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

Runnable方式多线程的好处

  • 避免了Java单继承的局限性
  • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好地体现了面向对象程序设计思想。

卖票案例

需求:某电影院有100张票,三个卖票窗口。设计一个程序模拟该电影院卖票。

思路:

  1. 定义一个SellTicket类实现Runnable接口,里面定义一个成员变量private int tickets=100;
  2. 在SellTicket类中重写Run()方法实现卖票,步骤如下:
    票数大于0,就卖票并告知哪一个窗口
    卖了票之后总票数-1
    票没有了,但还有人来问,死循环让动作一直执行。
    3.定义一个测试类SellTicketDemo,里面有main方法,如下:
    创建SellTickets对象
    创建3个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应窗口的名称
    启动线程
public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st=new SellTicket();
        Thread t1=new Thread(st,"一号窗口");
        Thread t2=new Thread(st,"二号窗口");
        Thread t3=new Thread(st,"三号窗口");

        t1.start();
        t2.start();
        t3.start();

    }
}
public class SellTicket implements Runnable{
    private  int tickets=100;
    @Override
    public void run() {

        while (true){
            if (tickets > 0) {
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                tickets--;
            }

        }

其实这里还有点不太明白,为什么卖出的票没有相同?如果同时访问的很多,恰好两个线程同时访问tickets的话岂不是会两个窗口卖出同一张票?

卖票案例的思考

实际上卖票需要有一个出票时间,假设每次出票时间为100ms
增加一个sleep(100),这种情况下已经出现了刚刚问题。
在这里插入图片描述

线程同步

数据安全问题判断规则:

  • 是否多线程环境
  • 是否有共享数据
  • 是否有多条语句操作共享数据

解决思路

破坏上面三个环境中的一个。前两条都无从下手,只能从第三条去考虑。把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行。而Java提供了同步代码块的方法来解决这个问题。

同步代码块

锁多条语句操作共享数据,可以使用同步代码块来实现

格式

syschronized(任意对象){多条语句操作共享数据的代码}

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st=new SellTicket();
        Thread t1=new Thread(st,"一号窗口");
        Thread t2=new Thread(st,"二号窗口");
        Thread t3=new Thread(st,"三号窗口");

        t1.start();
        t2.start();
        t3.start();

    }
}
public class SellTicket implements Runnable{
    private  int tickets=100;
    private Object obj=new Object();
    @Override
    public void run() {

        while (true){
            synchronized (obj){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }


        }


    }
}

同步的好处与弊端

  • 好处:解决了多线程的数据安全问题
  • 弊端:线程很多时会降低程序的运行效率

同步方法

同步方法:把synchronized关键字加到方法上

格式

修饰符 synchronized 返回值类型 方法名(方法参数){}

同步方法的锁对象

this对象

同步静态方法:把synchronized加到静态方法上

格式

修饰符 static synchronized 返回值类型 方法名 (方法参数){}

锁对象

在上面情况中,锁是SellTicket.class(那个类的字节码文件对象),后面反射中会有涉及讲解。
类名.class

线程安全的类

String Buffer

  • 线程安全,可变的字符序列
  • 从版本JDK5开始,被StringBuilder替代。通常应该使用StringBuilder类,因为他支持所有的相同操做,但他更快因为不执行同步。

Vector

  • 从Java2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。与新的集合实现不同。Vector被同步,如果不需要线程安全的实现建议使用ArrayList替代Vetor。

Hashtable

  • 该类实现了一个哈希表,它将键映射到值,任何非null对象都可以映射到键或者值。
  • 从Java2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。与新的集合实现不同。Hashtable被同步,如果不需要线程安全的实现建议使用HashMap替代Hashtable。

Lock

同步代码块和同步方法的锁对象不能直接看到在哪里上了锁,在哪里取消了锁。为了更清晰的表达符合添加所和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。Lock中获得了提供锁和释放锁的方法。

  • void lock():获得锁
  • void unlock():释放锁

Lock接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。
ReentrantLock的构造方法

  • ReentrantLock():创建一个ReentrantLock的实例。

代码

public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st=new SellTicket();

        Thread td1=new Thread(st,"一号窗口");
        Thread td2=new Thread(st,"二号窗口");
        Thread td3=new Thread(st,"三号窗口");

        td1.start();
        td2.start();
        td3.start();
    }
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicket implements Runnable{
    private int tickets=100;
    private Lock lock=new ReentrantLock();
    @Override
    public void run() {
        while (true){
            try{
                lock.lock();
                if(tickets>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }finally{
                lock.unlock();
            }
        }

    }
}

生产者和消费者

概述

生产者和消费者问题实际上包含了两类线程:

  • 生产者线程用于生产数据
  • 消费者线程用于消费数据

为了解耦生产者和消费者的关系,通常会采用共享的数据区域

  • 生产者生产数据之后直接放在数据共享区域中,并不需要关心消费者的行为
  • 消费者只需从共享数据区取数据,并不需要关心生产者的行为

为了体现生产和消费过程中的等待和唤醒,Java提供了几个方法,这几个方法在Object类中。

  • void wait():导致现在线程等待,直到另一个线程调用该对象的notify()或notifyAll()方法
  • void notify():唤醒正在等待对象监视器的单个线程
  • void notifyAll():唤醒正在等待对象监视器的所有线程

生产者和消费者案例

生产者消费者案例中包含的类:

  • 奶箱类(Box):定义一个变量表示第x瓶奶,提供存储牛奶和获取牛奶的操作
  • 生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶操作
  • 消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶操作
  • 测试类(BoxDemo):里面有main方法,main方法中步骤如下:
    创建奶箱对象,这里是共享数据区
    创建生产者对象,把奶箱对象作为构造方法参数传递
    创建消费者对象,把奶箱对象作为构造方法参数传递
    创建两个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
    启动线程

代码

public class BoxDemo {
    public static void main(String[] args) {
        Box b=new Box();

        Producer p=new Producer(b);
        Customer c=new Customer(b);

        Thread t1=new Thread(p);
        Thread t2=new Thread(c);

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


    }
}
public class Customer implements Runnable{
    private Box b;

    public Customer(Box b) {
        this.b=b;
    }

    @Override
    public void run() {
        while (true){
            b.get();
        }

    }
}
public class Box {
    private int milk;
    private boolean state=false;
    public synchronized void put(int milk){
        if(state){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.milk=milk;
        System.out.println("送奶工将第"+milk+"瓶牛奶放入奶箱");
        state=true;
        notifyAll();
    }
    public synchronized void get(){
        if(!state){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("用户拿到第"+milk+"瓶牛奶");
        state=false;
        notifyAll();
    }
}
public class Producer implements Runnable{
    private Box b;
    public Producer(Box b) {
        this.b=b;
    }

    @Override
    public void run() {
        for(int i=1;i<=5;i++){
            b.put(i);
        }

    }
}

标签:java,入门,Thread,void,start,线程,new,多线程,public
来源: https://blog.csdn.net/miaoxiaocheng/article/details/120394493

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有