开启新线程的方法and解决线程安全问题的方法

并发与并行

并发:两个或多个事件(线程)在同一时间段内发生(一个Cpu交替执行)

并行:两个或多个事件(线程)在同一时刻发生

线程与进程

进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。

进入到内存的程序就是进程

线程:能独立运行的基本单位,也是独立调度和分派的基本单位,  不具备独立的内存空间

 

进程和线程的区别

  1. 地址空间:进程是资源分配的基本单位线程与资源分配无关,它属于某个进程,与其它线程一起共享此进程的地址空间
  2. 切换和调度:线程上下文切换比进程上下文切换要快得多
  3. 在多线程OS中,进程不是一个可执行的实体。

 

线程调度

  • 分时调度:所有线程轮流使用cpu的使用权,平均分配每个线程使用时间
  • 抢占式调度:优先让优先级高的线程使用CPU,如果优先级相同,则随机分配,Java则是抢占式调度

主线程:执行main方法的线程

main方法执行过程:JVM执行main方法,首先将main方法放入栈内存中,开辟一条main通向CPU的路

                                 径,这个路径就叫做main(主)线程。

创建多线程

第一种方式:

1、创建一个Thread的子类

2、重写Thread的run() 方法,run中写的是新线程执行的任务

3、创建一个Thread的子类对象

4、main中调用该对象的start方法

第二种方式:

1、创建一个实现类实现Runnable接口,实现类中实现run()方法,设置线程任务

2、创建一个实现类对象

3、创建一个thread对象,构造方法中传递Runnable实现类对象名称

4、利用Thread对象调用start()方法,开启新线程执行run()方法

线程多次启动是违法的,线程结束执行结束后,不能再重新启动

两种方法的区别(Runnable的优点)

1、Java不允许多继承,使用Runnable实现后,还可以继承其它类

2、方便资源共享(共享一个实例对象),将设置线程任务和开启新线程进行了分离(解耦),增强了程序的可扩展性,

public class SnapUpThread implements Runnable {
    private int ticket = 100;
    static int i = 0;
    String name = "用户";

    @Override
    public void run() {        //设置线程任务
        i++;
        int nameNumber = i;
        while(true){
            if (ticket <= 0) {
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(name + nameNumber + "抢到票:" + ticket--);
        }

    }
}
public static void main(String[] args) {
        SnapUpThread runnable = new SnapUpThread();
        new Thread(runnable).start();        //开启新线程
        new Thread(runnable).start();        //开启新线程
    }

结果:

开启新线程的方法and解决线程安全问题的方法_第1张图片

 

多线程的原理

       当JVM执行main方法时,开辟了一条从main方法通向CPU的路径(线程),当执行到Thread th = new newThread();时,由再开辟一条从th通向CPU的路径,执行到start()方法时便开始执行th线程的run方法。

       对于CPU而言,便有了选择权,随机选择线程执行,这就有了程序的随机打印结果。同样也可以认为,两个线程同时抢夺CPU的执行权(执行时间)。

 

Thread常用方法

  • 获取线程名称两种方式:通过对象名来获取线程名称(对象.getName()),或者通过Thread的静态方法获取当前线程Thread.currentThread().getName()  
  • 设置线程名称两种方式:通过对象名来获取线程名称(对象.setName()),或者在新线程中写一个带参name构造方法,调用父类构造,并传入name参数
  • 暂停:Thread.sleep(xxx毫秒);使当前线程暂停xxx秒

 

线程安全问题

开启新线程的方法and解决线程安全问题的方法_第2张图片

 

解决办法

       在卖票时(操作共享数据),即使失去cpu控制权,也不允许其它线程来操作共享数据。

       当一个线程1抢到cpu使用权后,执行到synchronized语句时,获得了唯一的锁对象,因此就算失去cpu使用权,别的线程因为没有得到锁对象,会一直在synchronized语句阻塞,直到线程1释放出锁对象。

1、同步代码块实现

synchronized(锁对象){

//操作共享数据的语句

}

开启新线程的方法and解决线程安全问题的方法_第3张图片

注意:

  • 锁对象可以是任意类型对象
  • 多个线程中的锁对象必须是同一个
  • 静态方法中的锁对象比较特殊,是本类的class属性-->class文件对象

2、同步方法实现

把操作共享数据的语句封装成一个方法,方法修饰符后增加synchronized

(同步方法的锁对象其实就是创建的Runnable实现类对象,所以在第一个方法中也可以使用this作为锁对象传入)

public synchronized int payTicket(){
   if (ticket <= 0) {
        return 0;
   }
   System.out.println(name + nameNumber + "抢到票:" + ticket);
   ticket--;
   return 1;
}
int a = 1;
while(a == 1){
    a = payTicket();
}

3、使用Java.util.concurrent.locks.ReentrantLock

1、创建ReentrantLock rt = new ReentrantLock();

2、在操作共享数据的语句前加锁:rt.lock();

3、在操作完共享数据的语句后放掉锁:rt.unlock();

你可能感兴趣的:(多线程)