Java学习笔记:多线程1

一、进程与线程的概述及意义

1、进程
a、概述:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
b、多进程的意义:单进程计算机只能做一件事情。而我们现在的计算机都可以一边玩游戏(游戏进程),一边听音乐(音乐进程),所以我们常见的操作系统都是多进程操作系统。也就是说能在同一个时间段内执行多个任务。多进程的作用不是提高执行速度,而是提高CPU的使用率。
2、线程
a、概述:在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。线程是程序使用CPU的基本单位。
b、多线程的意义:多线程的作用不是提高执行速度,而是为了提高应用程序的使用率
c、并发与并行
并发指的是在一段时间内同时运行多个程序;
并行指的是在一个时间点同时运行多个程序;

二、多线程的实现

由于线程是依赖进程而存在的,所以我们应该先创建一个进程出来。而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程。但是Java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序。但是Java可以去调用C/C++写好的程序来实现多线程程序。由C/C++去调用系统功能创建进程,然后由Java去调用这样的东西,然后提供一些类供我们使用。我们就可以实现多线程程序了。
1、多线程实现方式一:
A:声明Tread类的子类,该子类重写了thread类的run方法。
开启线程的方法:调用start方法,同一个线程对象不能重复开启线程
重写run方法:run方法里面的代码,是让线程去执行的,所以让线程执行的代码,就需要写到run方法里面

 class MyThread extends Thread{
            @Override
            public void run() {
                //一般run方法里面,放的是耗时的操作
                for (int i = 0; i < 100; i++) {
                    String name = this.getName();
                    System.out.println(name+"==="+i);
                }
            }
        }
    public class MyTest {
        public static void main(String[] args) {
            MyThread th1 = new MyThread();
            MyThread th2 = new MyThread();
            th1.start();
            th2.start();
        }
    }

B:获取和设置线程对象名称

  • public final String getName()//获取线程名称
  • public final void setName(String name)//设置线程名称

C:线程调度及获取和设置线程优先级

  • 线程的执行是随机的,哪个线程在某个时刻抢到了cpu的执行权,cpu就执行谁

  • 线程有两种调度模型:有分时和抢占式调度调度模型 。java中线程的调度是抢占式模型。

  • 分时调度模型:所有线程轮流使用cpu的使用权,平均分配每个线程占用cpu的时间片

  • 抢占式模型:优先让优先级高的线程使用cpu,如果线程优先级相同那么会随机选用一个优先级高的线程获取的cpu时间段相对多一些

  • 设置和获取线程优先级:

  • public final int getPriority() //获取线程的优先级

  • public final void setPriority(int newPriority)//设置线程的优先级

    扫描二维码关注公众号,回复: 9703975 查看本文章

注意事项: 有的时候我们给线程设置了指定的优先级,但是该线程并不是按照优先级高的线程执行,这是因为线程的优先级的大小仅仅表示这个线程被CPU执行的概率增大了.但是我们都知道多线程具有随机性,所以有的时候一两次的运行说明不了问题。
2、多线程实现方式二:
声明实现Runnable接口的类,该类重写run()方法,创建接口的子类对象,创建Thread对象,将接口子类对象作为参数传给Thread,启动线程。

class MyRunable implements Runnable{ 
    @Override
    public void run() {
        //需要线程执行代码
        for (int i = 0; i < 100; i++) {
            String name = Thread.currentThread().getName();
            System.out.println(name+"==="+i);
        }
    }
}
public class MyTest {
  public static void main(String[] args) {
        //Thread(Runnable target)
        //分配新的 Thread 对象。
        //Thread(Runnable target, String name) 参数2 线程名字
        MyRunable myRunable = new MyRunable();
        Thread th1 = new Thread(myRunable,"线程A");
        Thread th2 = new Thread(myRunable,"线程B");
        th1.start();
        th2.start();
   }
}

3、多线程实现方式三:
创建一个类实现Callable 接口,创建一个FutureTask类将Callable接口的子类作为参数传进去创建Thread类,将FutureTask对象作为参数传进去

public class MyCallable implements Callable<Integer> {
    //让线程来执行的方法
    @Override
    public Integer call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"==="+i);
        }
        return 100;
    }
}
    public class MyTest {
        public static void main(String[] args) throws Exception {
            MyCallable myCallable = new MyCallable();
            //FutureTask 这个是Runable接口的子类, 他可以封装一个Callable 任务
            FutureTask<Integer> task = new FutureTask<>(myCallable);
            Thread th1 = new Thread(task);
            th1.start();
            Integer integer = task.get();   //get()方法,可以获取线程执行完之后返回的结果
            System.out.println(integer);
        }
    }

注意: call() 方法和run() 都是要线程来执行的,区别是 call 执行完可以返回结果 而且这个方法可以抛异常。run() 线程执行完了,没有结果返回,不可以抛出异常。
4、线程中常用方法

  • public static Thread currentThread( );返回对当前正在执行的线程对象的引用。
  • public static void sleep(long millis); 线程休眠
  • 线程加入:public final void join( );等待该线程执行完之后,其他线程才能开始执行,在线程开启之后再加入。
  • public static void yield( );线程礼让,暂停当前正在执行的线程对象,并开启其他线程
  • public final void setDaemon(boolean on);设置守护线程,主线程是个用户线程当用户线程死亡后,守护线程也立即死亡
  • public final void stop( ); 强制停止线程让线程处于死亡状态
  • public void interrupt( );打断线程的阻塞状态

5、线程安全问题
A、出现数据安全问题符合三个条件

  • a:是不是多线程
  • b:多个线程是否有共享数据
  • c:是否有多条语句在操作这个数据

B、解决数据安全问题
a、程序出现问题,因为它满足上面的三个标准,那么我们只要将这个标准打乱,就可以解决这个问题.而上面的标准中a , b是不能打乱的,因此我们只能对c做处理,关键是怎么处理? 如果我们把操作共享数据的多条语句看做成一个整体,当一个线程执行这个整体的时候,其他的线程处于等待状态,也就说当一个线程执行这个整体的时候,其他线程 不能进行执行。
b、 基本思想:让程序没有安全问题的环境。把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可。需要使用同步代码块:
c、格式:

synchronized(对象){//不能在括号里直接new 对象 new 了 就没效果
    			要被同步的代码 ;
    		}
注意:	这个同步代码块保证数据的安全性的一个主要因素就是这个对象
	    这个对象要定义为静态成员变量 才能被所有线程共享
    	这个对象其实就是一把锁.

同步代码块的锁对象: 任意一个对象
同步方法的锁对象: 是this
静态同步方法的锁对象:就是当前类对应的字节码文件对象

C、同步的好处: 同步的出现解决了多线程的安全问题。
D、同步的弊端: 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
拓展
在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识。

  • java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
  • java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。
  • java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,类锁是用于类的静态方法或者一个类的class对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的.

举例:

 某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票。
 分析:
 	a: 三个窗口其实就是3个线程
  	b: 定义票的数量100张
  	c: 创建线程对象,启动线程. 每卖一张这个票数应该--
class CellRunable implements Runnable{
   static int piao=100;
   static Object obj=new Object();
   @Override
   public void run() {
      while (true) {
      //只要任意一个线程,进入了同步代码块了以后,就会锁上(这个线程就会持有这把锁),其他线程,获取不到这个锁就处于阻塞状态
      //java 中这种内置的锁,我们称之为互斥锁
        synchronized (obj){ //同步代码块 锁对像 ,可以传任意一个对象 ,注意多个对象要共享一把锁
        //进来之后 说白了就是一个单线程环境
           if (piao > 0) {
              try {
                  Thread.sleep(50);
              } catch (InterruptedException e) {
                  e.printStackTrace();
               }
       System.out.println(Thread.currentThread().getName() + "正在出售 " + (piao--) + " 张票");
            }
       }
    //出来之后,才会释放锁
    }
  }
}
public class MyTest {
  public static void main(String[] args) {
    //synchronized 效率低  耗费性能
    CellRunable cellRunable = new CellRunable();
    Thread th1 = new Thread(cellRunable);
    Thread th2 = new Thread(cellRunable);
    Thread th3 = new Thread(cellRunable);
    th1.setName("窗口1");
    th2.setName("窗口2");
    th3.setName("窗口3");
    th1.start();
    th2.start();
    th3.start();
  }
}
发布了24 篇原创文章 · 获赞 11 · 访问量 2050

猜你喜欢

转载自blog.csdn.net/weixin_43791069/article/details/86603416
今日推荐