Java创建线程的三种方式和synchronized同步线程示例

1、什么是进程

​ 进程是程序再一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位,在计算机系统中,独立运行的一个程序,这个东西叫做进程。例如:QQ 、微信 、360。

​ 独立性:

​ 每个软件都是独立的,使用的资源都是系统分配的。CPU 内存 网络 磁盘使用

​ 并发性:

​ 操作系统中,可以同事运行多个程序,多个进程。

​ 互斥性:

​ 每个程序直接都是没有干扰,游戏助手,网游加速器,英雄联盟,绝地求生

​ 动态性:

​ 程序在运行的时候,使用的系统资源是随着当前程序运行所需在不断变化的

2、什么是线程

​ 线程是基于进程,例如我们使用的电脑管家,可以同时运行,病毒查杀,一键加速,垃圾清理等,每一个功能都是一个单独的进程,个人理解的是,线程是进程中的某一个功能

​ 线程的特征

​ 资源抢占:

​ 每个线程都是抢占进程从系统中分配的资源、CPU、内存、网络、硬盘等

​ 每个线程都会在进程的执行时间以内,抢占执行资源

​ 资源共享

​ 进程中分配的系统资源是对于当前进程内的线程,是共享的。

3、线程的创建和启动

Java使用Thread类代表线程,所有线程对象都是Thread类或其子类的实例

3.1、继承Thread类创建并启动线程21

public class FirstThread extends Thread {
    private int i;
    @Override
    public void run() {
        for (int i=0 ; i<=10 ; i++){
            //返回当前线程的名称
            String threadName = getName()+"i";
            System.out.println(threadName);
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            //获取当前线程
            System.out.println(Thread.currentThread().getName()+"i");
            //创建并启动第一个线程
            FirstThread firstThread01 = new FirstThread();
            firstThread01.start();
            //创建并启动第二个线程
            FirstThread firstThread02 = new FirstThread();
            firstThread02.start();

        }
    }
}

在这里插入图片描述
继承Thread类,重写run()方法,也就是这个线程需要完成的任务,线程对象的start()方法来启动该线程

3.2、实现Runnable接口创建线程类(推荐方法)

public class FirstRunnable implements Runnable {
    public void run() {
        for (int i=0 ; i<=10 ; i++){
            //返回当前线程的名称(使用Runnable接口创建线程的时候,不能直接使用getName)
            String threadName = Thread.currentThread().getName()+""+i;
            System.out.println(threadName);
        }
    }
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            //获取当前线程
            System.out.println(Thread.currentThread().getName()+""+i);
            FirstRunnable firstRunnable = new FirstRunnable();
            //创建并启动二个单独的线程
            new Thread(firstRunnable,"子线程名称0").start();
            new Thread(firstRunnable,"子线程名称1").start();
        }
    }
}

实现Runnable接口,同意的在run()方法中编写需要线程实现的操作,采用线程对象的start()方法启动。

3.2.1、Thread类常用方法

Constructor 构造方法
	Thread();
		创建一个Thread类对象,没有任何的Runnable接口实现类对象和线程名,线程的名字是默认名
	Thread(Runnable target);
		创建一个Thread类对象,使用Runnable接口的实现类对象作为当前方法参数,也是执行的线程方法
	Thread(String name);
		创建一个Thread类对象,给予当前线程一个名字
	Thread(Runnable target, String name);
		创建一个Thread类对象,使用Runnable接口的实现类来作为线程方法,并且起一个线程的名字

Method 成员方法
	void setName(String name);
		设置当前线程的名字
	String getName(); 
		获取当前线程的名字
	void setPriority(int newPriority);
		设置线程的优先级,线程的优先级是从 1 ~ 10 1最低, 10最高,默认为5
		线程优先级越高,执行的概率越高,但是不能保证一定执行!!!
	int getPriority();
		获取当前线程的优先级
	static Thread currentThread();
		获取当前方法所处代码块的线程类对象
	static void sleep(int ms);
		线程进入指定时间的休眠状态
你认为哪一个方式更好?

​ 自定义线程类遵从Runnable接口更合适
因为Java语言是一门单继承语言,如果一个类遵从的Thread类,就无法继承于其他类,可能会增加项目逻辑复杂度,但是Java语言可以遵从多个接口,并不会影响其他逻辑。

3.3、实现Callable接口通过FutureTask包装器来创建Thread线程(不常用)

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

public class FirstCallable {
    public static void main(String[] args) {
        //创建Callable对象
        FirstCallable rt = new FirstCallable();
        //先使用Lambda表达式创建Callable<Integer>对象
        //使用FutureTask来包装Callable对象
        FutureTask<Integer> task = new FutureTask<>((Callable<Integer>)()->{
            int i=0;
            for(;i<100;i++) {
                System.out.println(Thread.currentThread().getName()+"循环变量i的值:"+i);
            }
            return i;
        });
        for(int i=0;i<100;i++) {
            System.out.println(Thread.currentThread().getName()+" 的循环变量i的值:"+i);
            if(i==20) {
                //实质还是以Callable对象来创建并启动的
                new Thread(task,"有返回值的线程").start();

                /*Thread t = new Thread(task);
                t.start();*/
            }
        }
        try {
            System.out.println("子线程的返回值:"+task.get());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

4、synchronized 同步代码块

一般用于处理在一个程序中,存在不同线程之间的共享资源问题。

synchronized 关键字用于修饰函数和代码块,以实现同步,当多个线程在执行被synchronized 修饰的代码的时候,会等待上一个线程处理完毕之后,再执行被修饰的代码。加锁的代码块,被称为“互斥区”或者“临界区”。synchronized 加到static静态方法上是给Class类上锁。


注:此图来源 https://blog.csdn.net/qq_37438740/article/details/81109226
这里我们假设一个场景,例如电影院出售电影票。出售100张电影票,通过三个渠道去销售,淘票,猫眼,美团,共出售100张电影票,但是不能出现超卖或者一张票多次出售的情况。

我们先来看一下对抢票操作不加synchronized 的时候情况

public class SynchronizedCodeDemo implements Runnable {
    //100张电影票
    private static int tickets = 100;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            getticket();
            System.out.println(Thread.currentThread().getName()+"抢到了电影票____目前电影票的数量为"+tickets);
        }
    }

    public static void main(String[] args) {
        SynchronizedCodeDemo synchronizedCodeDemo = new SynchronizedCodeDemo();
            new Thread(synchronizedCodeDemo,"淘票").start();
            new Thread(synchronizedCodeDemo,"猫眼").start();
            new Thread(synchronizedCodeDemo,"美团").start();
    }
    //抢票方法
    public  void getticket(){
        tickets -=1;
    }
}

在这里插入图片描述

我们可以看到出现了一票多卖,或者超卖的现象,这时候我们就需要对购买票的操作加上锁。

public class SynchronizedCodeDemo implements Runnable {
    //100张电影票
    private static int tickets = 100;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            boolean isSuccessTicket = getticket();
            if (isSuccessTicket){
                System.out.println(Thread.currentThread().getName()+"抢到了电影票____目前电影票的数量为"+tickets);
            }else {
                System.out.println(Thread.currentThread().getName()+"没有抢到电影票____目前电影票的数量为"+tickets);

            }
        }
    }

    public static void main(String[] args) {
        SynchronizedCodeDemo synchronizedCodeDemo = new SynchronizedCodeDemo();
            new Thread(synchronizedCodeDemo,"淘票").start();
            new Thread(synchronizedCodeDemo,"猫眼").start();
            new Thread(synchronizedCodeDemo,"美团").start();
    }
    //抢票方法
    public  boolean getticket(){
        synchronized("加锁"){
            //判断电影票数量是否足够
            if (tickets >0){
                tickets -=1;
                return true;
            }
            return false;
        }
    }
}

在这里插入图片描述

可以看到,我们使用synchronized加锁以后,可以看到避免了一票多卖,或者超卖的现象。
注意:

  • 一旦有一个线程进入加锁的代码块,其他线程只能在锁对象之外等待,该线程执行完成,锁打开,所有执行该同步代码块的线程,再重新抢占
  • Sleep方法不会打开锁对象
  • 同步代码块包含的代码越少越好
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值