聊聊java多线程创建方式及线程安全问题

什么是线程

线程被称为轻量级进程,是程序执行的最小单位,它是指在程序执行过程中,能够执行代码的一个执行单位。每个程序程序都至少有一个线程,也即是程序本身。

线程的状态

  • 新建(New):创建后尚未启动的线程处于这种状态
  • 运行(Runable):Runable包括了操作系统线程状态的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间。
  • 等待(Wating):处于这种状态的线程不会被分配CPU执行时间。等待状态又分为无限期等待和有限期等待,处于无限期等待的线程需要被其他线程显示地唤醒,没有设置Timeout参数的Object.wait()、没有设置Timeout参数的Thread.join()方法都会使线程进入无限期等待状态;有限期等待状态无须等待被其他线程显示地唤醒,在一定时间之后它们会由系统自动唤醒,Thread.sleep()、设置了Timeout参数的Object.wait()、设置了Timeout参数的Thread.join()方法都会使线程进入有限期等待状态。
  • 阻塞(Blocked):线程被阻塞了,“阻塞状态”与”等待状态“的区别是:”阻塞状态“在等待着获取到一个排他锁,这个时间将在另外一个线程放弃这个锁的时候发生;而”等待状态“则是在等待一段时间或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。
  • 结束(Terminated):已终止线程的线程状态,线程已经结束执行。

多线程创建方法

继承Thread

/**
 * @Author GocChin
 * @Date 2021/5/11 11:56
 * @Blog: itdfq.com
 * @QQ: 909256107
 * @Descript:
 */
class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(currentThread().getName()+"运行了");
    }
}
class Test{
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        System.out.println(Thread.currentThread().getName()+":运行了");
        myThread.start();
    }
}

实现Runable接口创建多线程

/**
 * @Author GocChin
 * @Date 2021/5/11 12:37
 * @Blog: itdfq.com
 * @QQ: 909256107
 * @Descript: 实现Runable接口的方式创建多线程
 * 1.创建一个实现了Runable接口的类
 * 2.实现类去实现Runable中的抽象方法,run();
 * 3.创建实现类的对象
 * 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
 * 5.通过Thread类的对象调用start()
 */
class MThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i<100;i++){
            if (i%2!=0){
                System.out.println(i);
            }
        }
    }
}
public class ThreadTest1 {
    public static void main(String[] args) {
        //3.创建实现类的对象
        MThread mThread = new MThread();
        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread thread = new Thread(mThread);
        thread.start();
    }
}

Thread和Runable创建多线程对比

开发中:优先使用Runable
1.实现的方式没有类的单继承的局限性。
2.实现的方式跟适合处理多个线程有共享数据的情况。
联系:Thread类中也实现了Runable,两种方式都需要重写run()。

实现Callable接口

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @Author GocChin
 * @Date 2021/5/11 13:03
 * @Blog: itdfq.com
 * @QQ: 909256107
 * @Descript:
 */
class MCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum=0;
        for(int i=0;i<100;i++){
            sum+=i;
        }
        return sum;
    }
}
public class CallableTest {
    public static void main(String[] args) {
        //执行Callable 方式,需要FutureTask 实现实现,用于接收运算结果
        FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(new MCallable());
        new Thread(integerFutureTask).start();
        //接受线程运算后的结果
        Integer integer = null;
        try {
            integer = integerFutureTask.get();
            System.out.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

与Runable相比,Callable功能更强大

相比run()方法可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果

使用线程池进行创建

线程池创建的好处

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

corePoolSize:核心线程池的大小

maximumPoolSize:最大线程数

keepAliveTime:线程没有任务时最多保持多长时间后悔中止

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author GocChin
 * @Date 2021/5/11 13:10
 * @Blog: itdfq.com
 * @QQ: 909256107
 * @Descript:
 */
class Thread1 implements Runnable{

    @Override
    public void run() {
        for (int i=1;i<30;i++){
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}
public class ThreadPool {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService executorService= Executors.newFixedThreadPool(10);
        Thread1 threadPool = new Thread1();
        for (int i=0;i<5;i++){
            //为线程池分配任务
            executorService.submit(threadPool);
        }
        //关闭线程池
        executorService.shutdown();
    }
}

Thread中的常用方法start():

  • start():启动当前线程;调用当前线程的run();
  • run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中。
  • currentThread():静态方法,返回当前代码的线程。
  • getName():获取当前线程的名字。
  • setName():设置当前线程的名字。
  • yield():释放当前cpu的执行权,切换线程执行。
  • join():在线程a中调用线程b的join(),此时线程a会进入阻塞状态,知道线程b完全执行完毕,线程a 才结束阻塞状态。
  • stop():强制线程生命期结束。(过时了,不建议使用)
  • isAlive():判断线程是否还活着。
  • sleep(long millitime):让当前线程睡眠指定的事milltime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。

线程的优先级

线程的优先级等级

  • MAX_PRIORITY:10
  • MIN_PRIORITY:1
  • NORM_PRIORITY:5

涉及的方法

  • getPriority():返回线程的优先值
  • setPriority(int newPriority):改变线程的优先级

说明

  • 线程创建时继承父线程的优先级
  • 低优先级知识获得调度的概率低,并非一定是在高优先级线程之后才被调用

线程的同步

多线程卖票

基于实现Runable的方式实现多线程买票

package demo2;

/**
 * @Author GocChin
 * @Date 2021/5/11 13:37
 * @Blog: itdfq.com
 * @QQ: 909256107
 * @Descript: 创建三个窗口买票,总票数为100张,使用Runable接口的方式
 *      存在线程安全问题,待解决
 */
class Thread2 implements Runnable{

    private  int ticket=100;
    @Override
    public void run() {
        while (true){
            if (ticket>0) {
                System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}
public class Test1 {
    public static void main(String[] args) {
        Thread2 thread2 = new Thread2();
        Thread t1 = new Thread(thread2);
        Thread t2 = new Thread(thread2);
        Thread t3 = new Thread(thread2);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

实现结果,存在重复的票

如果在买票方法中加入sleep函数

public void run() {
        while (true){
            if (ticket>0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
                ticket--;
            }else {
                break;
            }
        }
    }

则运行结果可能会出现-1,表示也是不正常的

理想情况

极端情况

在java中,我们通过同步机制,来解决线程的安全问题。

同步代码块

synchronized(同步监视器){
	//需要被同步的代码
}

说明

  • 操作共享数据的代码就是需要被同步的代码。
  • 共享数据:多个线程共同操作的变量,比如本题中的ticket就是共享数据。
  • 同步监视器:俗称:锁。任何一个类的对象都可以充当锁。要求:多个线程必须要共用统一把锁
  • 同步的方式,解决了线程的安全问题---好处。但是操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。-----局限性
  • 使用Runable接口创建多线程的方式中,可以使用this关键字;在继承Thread类中创建多线程中,慎用this充当同步监视器,可以考虑使用当前类充当同步监视器。Class clazz = Windows.class 因此 类也是一个对象
  • 包裹操作共享数据的代码 不能多也不能少

修改之后的代码:

package demo2;

/**
 * @Author GocChin
 * @Date 2021/5/11 13:37
 * @Blog: itdfq.com
 * @QQ: 909256107
 * @Descript: 创建三个窗口买票,总票数为100张,使用Runable接口的方式
 *      存在线程安全问题,待解决
 */
class Thread2 implements Runnable{

    private  int ticket=100;

    Object object = new Object();
    @Override
    public void run() {
        while (true){
            synchronized(object) { //括号中的内容可以直接使用当前对象this去充当
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class Test1 {
    public static void main(String[] args) {
        Thread2 thread2 = new Thread2();
        Thread t1 = new Thread(thread2);
        Thread t2 = new Thread(thread2);
        Thread t3 = new Thread(thread2);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

结果

继承Thread的方式,去使用同步代码块,需要将声明的锁对象设为statci,否则创建的对象的同步监视器不唯一,就无法实现。

package demo2;

/**
 * @Author GocChin
 * @Date 2021/5/11 14:45
 * @Blog: itdfq.com
 * @QQ: 909256107
 * @Descript:
 */
class WindowsTest2 extends Thread{
    private static int ticket=100;
    private static   Object obj = new Object();

    @Override
    public void run() {
        while (true){
            synchronized (obj){ //这里不能使用this去充当,可以直接写一个Test.class   类也是对象
                if (ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(getName()+":买票,票号为:"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }
        }
    }
}
public class  Test2{
    public static void main(String[] args) {
        WindowsTest2 w1 = new WindowsTest2();
        WindowsTest2 w2 = new WindowsTest2();
        WindowsTest2 w3 = new WindowsTest2();
        w1.setName("窗口一");
        w2.setName("窗口二");
        w3.setName("窗口三");
        w1.start();
        w2.start();
        w3.start();
    }
}

同步方法

如果操作共享数据的代码完整的声明在一个方法中,可以将此方法声明为同步的。

通过实现Runable的方式实现同步方法。

package demo2;

/**
 * @Author GocChin
 * @Date 2021/5/11 13:37
 * @Blog: itdfq.com
 * @QQ: 909256107
 * @Descript: 创建三个窗口买票,总票数为100张,使用Runable接口的方式
 * 存在线程安全问题,待解决
 */
class Thread3 implements Runnable {

    private int ticket = 100;

    @Override
    public void run() {
        while (true) {
            show();
        }

    }
    private synchronized void show(){
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
            ticket--;
        }
    }
}

public class Test3 {
    public static void main(String[] args) {
        Thread3 thread3 = new Thread3();
        Thread t1 = new Thread(thread3);
        Thread t2 = new Thread(thread3);
        Thread t3 = new Thread(thread3);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}

通过实现继承Thread的方式实现同步方法。使用的同步监视器是this,则不唯一,就会报错。所以将该方法定义为static。当前的同步换时期就变成Test4.class

package demo2;

/**
 * @Author GocChin
 * @Date 2021/5/11 14:45
 * @Blog: itdfq.com
 * @QQ: 909256107
 * @Descript:
 */
class WindowsTest4 extends Thread{
    private static int ticket=100;
    private static   Object obj = new Object();

    @Override
    public void run() {
        while (true){
            show();
        }

    }
    public static synchronized void show(){//同步监视器不是this了,而是当前的类
//    public synchronized void show(){//同步监视器是this  ,t1,t2,t3
        if (ticket>0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":买票,票号为:"+ticket);
            ticket--;
        }
    }
}
public class  Test4{
    public static void main(String[] args) {
        WindowsTest4 w1 = new WindowsTest4();
        WindowsTest4 w2 = new WindowsTest4();
        WindowsTest4 w3 = new WindowsTest4();
        w1.setName("窗口一");
        w2.setName("窗口二");
        w3.setName("窗口三");
        w1.start();
        w2.start();
        w3.start();
    }
}

总结

  • 同步方法仍然设计到同步监视器,只是不需要我们去显示的声明。
  • 非静态的同步方法,同步监视器是:this静态的同步方法中,同步监视器是类本身。

Lock锁解决线程安全问题

synchronize与lock的异同

相同

  • 都可以解决线程安全问题

不同

  • synchronize机制在执行相应的同步代码以后,自动的释放同步监视器;Lock需要手动的启动同步lock(),同时结束同步也需要手动的实现unlock()。

建议优先使用顺序
Lock------>同步代码块(已经进入了方法体,分配了相应资源)---->同步方法(在方法体之外)

package demo2;

import java.util.concurrent.locks.ReentrantLock;

/**
 * @Author GocChin
 * @Date 2021/5/11 15:58
 * @Blog: itdfq.com
 * @QQ: 909256107
 * @Descript:
 */
class Lock1 implements Runnable{
    private int ticket=50;

    //1.实例化
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while(true){
            try {
                //2.调用lock锁定方法
                lock.lock();
                if (ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket);
                    ticket--;
                }else{
                    break;
                }
            } finally {
                //3.调用解锁方法
                lock.unlock();
            }
        }
    }
}
public class LockTest1 {
    public static void main(String[] args) {
        Lock1 lock1 = new Lock1();
        Thread t1 = new Thread(lock1);
        Thread t2 = new Thread(lock1);
        Thread t3 = new Thread(lock1);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();

    }
}

到此这篇关于聊聊java多线程创建方式及线程安全问题的文章就介绍到这了,更多相关java多线程创建内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 关于java中线程安全问题详解

    目录 一.什么时候数据在多线程并发的环境下会存在安全问题? 二.怎么解决线程安全问题? 三.银行 取钱/存钱 案例 为什么会出现线程安全问题 四.总结 一.什么时候数据在多线程并发的环境下会存在安全问题? 三个条件: 条件1:多线程并发. 条件2:有共享数据. 条件3:共享数据有修改的行为. 满足以上3个条件之后,就会存在线程安全问题. 二.怎么解决线程安全问题?         线程排队执行.(不能并发).用排队执行解决线程安全问题.这种机制被称为:线程同步机制. 三.银行 取钱/存钱 案例

  • JAVA多线程线程安全性基础

    目录 线程安全性 什么是线程安全的代码 什么是线程安全性 总结 线程安全性 一个对象是否需要是线程安全的,取决于它是否被多个线程访问,而不取决于对象要实现的功能 什么是线程安全的代码 核心:对 共享的 和 可变的 状态的访问进行管理.防止对数据发生不受控的并发访问. 何为对象的状态? 状态是指存储在对象的状态变量(例如实例或静态域)中的数据.还可能包括 其他依赖对象 的域. eg:某个HashMap的状态不仅存储在HashMap对象本身,还存储在许多Map.Entry对象中. 总而言之,在对象的

  • Java中ConcurrentHashMap是如何实现线程安全

    目录 语法: ConcurrentHashmap 的需要: 如何使 ConcurrentHashMap 线程安全成为可能? Hashtable.Hashmap.ConcurrentHashmap的区别 ConcurrentHashMap是一个哈希表,支持检索的全并发和更新的高预期并发.此类遵循与 Hashtable 相同的功能规范,并包含 Hashtable 的所有方法.ConcurrentHashMap 位于 java.util.Concurrent 包中. 语法: public class

  • java多线程创建及线程安全详解

    什么是线程 线程被称为轻量级进程,是程序执行的最小单位,它是指在程序执行过程中,能够执行代码的一个执行单位.每个程序程序都至少有一个线程,也即是程序本身. 线程的状态 新建(New):创建后尚未启动的线程处于这种状态 运行(Runable):Runable包括了操作系统线程状态的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间. 等待(Wating):处于这种状态的线程不会被分配CPU执行时间.等待状态又分为无限期等待和有限期等待,处于无

  • Java中关于线程安全的三种解决方式

    三个窗口卖票的例子解决线程安全问题 问题:买票过程中,出现了重票.错票-->出现了线程的安全问题 问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票 如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来,知道线程a操作完ticket时,其他线程才可以开始操作ticket,这种情况即使线程a出现了阻塞,也不能被改变 在Java中,我们通过同步机制,来解决线程的安全问题.(线程安全问题的前提:有共享数据) 方式一:同步代码块 synchroniz

  • 聊聊java多线程创建方式及线程安全问题

    什么是线程 线程被称为轻量级进程,是程序执行的最小单位,它是指在程序执行过程中,能够执行代码的一个执行单位.每个程序程序都至少有一个线程,也即是程序本身. 线程的状态 新建(New):创建后尚未启动的线程处于这种状态 运行(Runable):Runable包括了操作系统线程状态的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间. 等待(Wating):处于这种状态的线程不会被分配CPU执行时间.等待状态又分为无限期等待和有限期等待,处于无

  • Java多线程常见案例分析线程池与单例模式及阻塞队列

    目录 一.单例模式 1.饿汉模式 2.懒汉模式(单线程) 3.懒汉模式(多线程) 二.阻塞队列 阻塞队列的实现 生产者消费者模型 三.线程池 1.创建线程池的的方法 (1)ThreadPoolExecutor (2)Executors(快捷创建线程池的API) 2.线程池的工作流程 一.单例模式 设计模式:软件设计模式 是一套被反复使用.多数人知晓.经过分类编目.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性.程序的重用性. 单例模式:是设计模式的一种.

  • 生产消费者模式实现方式和线程安全问题代码示例

    生产者消费者模式的几种实现方式 拿我们生活中的例子来说,工厂生产出来的产品总是要输出到外面使用的,这就是生产与消费的概念. 在我们实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类.函数.线程.进程等). 产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者. 第一种:采用wait-notify实现生产者消费者模式 1. 一生产者与一消费者: 2. 一生产者与多消费者: 第二种: 采用阻塞队列实现生产者消费者

  • Java多线程编程安全退出线程方法介绍

    线程停止 Thread提供了一个stop()方法,但是stop()方法是一个被废弃的方法.为什么stop()方法被废弃而不被使用呢?原因是stop()方法太过于暴力,会强行把执行一半的线程终止.这样会就不会保证线程的资源正确释放,通常是没有给与线程完成资源释放工作的机会,因此会导致程序工作在不确定的状态下 那我们该使用什么来停止线程呢 Thread.interrupt(),我们可以用他来停止线程,他是安全的,可是使用他的时候并不会真的停止了线程,只是会给线程打上了一个记号,至于这个记号有什么用呢

  • Java多线程之如何确定线程数的方法

    关于多线程的线程数的确定,最近研读过几篇paper,在此做一下笔记,方便使用时翻看. 1.<Java 虚拟机并发编程>中介绍 就是说:线程数 = CPU的核心数 * (1 - 阻塞系数) 另一篇:<Java Concurrency in Practice>即<java并发编程实践>,给出的线程池大小的估算公式: Nthreads=Ncpu*Ucpu*(1+w/c),其中 Ncpu=CPU核心数,Ucpu=cpu使用率,0~1:W/C=等待时间与计算时间的比率 仔细推敲两

  • 详解Java多线程编程中的线程同步方法

    1.多线程的同步: 1.1.同步机制: 在多线程中,可能有多个线程试图访问一个有限的资源,必须预防这种情况的发生.所以引入了同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问. 1.2.共享成员变量的例子: 成员变量与局部变量: 成员变量: 如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作,这多个线程是共享一个成员变量的. 局部变量: 如果一个变量是局部变量,那么多个线程对同一个对象进行操作,每个线程都会有一个该局部变量的拷贝.他们

  • java多线程编程之从线程返回数据的两种方法

    一.通过类变量和方法返回数据 使用这种方法返回数据需要在调用start方法后才能通过类变量或方法得到数据.让我们先来看看例程2-13会得到什么结果. 复制代码 代码如下: package mythread; public class MyThread extends Thread{    private String value1;    private String value2; public void run()    {        value1 = "通过成员变量返回数据"

  • java多线程编程之向线程传递数据的三种方法

    在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果.但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别.由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据.本文就以上原因介绍了几种用于向线程传递数据的方法,在下一篇文章中将介绍从线程中返回数据的方法. 欲先取之,必先予之.一般在使用线程时都需要有一些初始化数据,然后线程利用这些数据进行加工处理,并

  • Java多线程编程中synchronized线程同步的教程

    0.关于线程同步 (1)为什么需要同步多线程? 线程的同步是指让多个运行的线程在一起良好地协作,达到让多线程按要求合理地占用释放资源.我们采用Java中的同步代码块和同步方法达到这样的目的.比如这样的解决多线程无固定序执行的问题: public class TwoThreadTest { public static void main(String[] args) { Thread th1= new MyThread1(); Thread th2= new MyThread2(); th1.st

随机推荐