Java 笔记 之 多线程

什么是线程?

要解释线程,就必须要明白什么是进程。

什么是进程呢?

进程是指运行中的应用程序,每个进程都有自己独立的地址空间(内存空间),目前的操作系统都是支持多进程的。用户每启动一个进程,操作系统就会给该进程分配一个独立的内存空间。

什么是线程呢?

线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程本身不拥有系统资源,但是它可以和同属一个进程的其他线程共享进程所拥有的所有资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。

线程和进程的区别?

线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。

线程有哪几种状态?

线程从创建、运行到结束共有五种状态:新建状态、就绪状态、运行状态、阻塞状态以及死亡状态。

这里写图片描述

  1. 新建状态(New):

    当用new操作符创建一个线程时,例如new Thread(r),线程还没有开始运行,此时线程处在新建状态。当一个线程处于新建状态时,程序还没有开始运行线程中的代码

  2. 就绪状态(Runnable):

    一个新创建的线程并不自动开始运行,要执行线程,就必须要调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。让start()方法返回后,线程就处于就绪状态。

    处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。

  3. 运行状态(Running):

    当线程获得CPU时间后,才进入运行状态,真正开始执行run()方法。

  4. 阻塞状态(Blocked):

    所谓的阻塞状态是正在运行的线程还么有云心结束,暂时让出CPU,这是其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。
    线程运行过程中,可能由于各种原因进入阻塞状态:
    a.线程通过调用sleep方法进入到睡眠状态;
    b.线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
    c.线程试图得到一个锁,而该锁正在被其他线程持有;
    d.线程正在等待某个触发条件;
    ……

  5. 死亡状态(Dead):
    为了确定线程当前是否存活着(要么是可运行,要么是被阻塞了),需要使用isAlive方法.如果是可运行或者被阻塞,这个方法返回的是true;如果线程依旧是new状态且不是可运行的,或者是献策好难过死亡了,则返回false

    有两个原因会导致线程死亡:
    1️⃣ run方法正常退出而自然死亡;
    2️⃣ 一个未捕获的异常终止了run方法而使线程猝死

如何在Java中实现线程?

在java中实现线程有两种方式:
1. 继承Thread类,重写run()方法:

package demo;

class MyThread extends Thread{    
    public void run(){    
         //单条线程运行一百次
        for (int i = 0; i < 100; i++){    
            System.out.println("MyThread.run()..."+i);    
        }   
    }    
}

public class Test1 {

    public static void main(String[] args) {
        MyThread a = new MyThread();
        a.start();
        for (int j = 0; j < 100; j++) {
            System.out.println("test1.main()----" + j);
        }
    }
}

2.实现Runnable接口,重写run()方法

package demo2;

class MyThread2 implements Runnable {
    public void run() {
        /* 每条线程运行50次,共200次 */
        for (int i = 0; i < 50; i++) {
            System.out.println("MyThread.run()..." + i);
        } 
    }
}

public class Test2 {

    public static void main(String[] args) {
        MyThread2 a = new MyThread2();
        Thread t1 = new Thread(a);
        Thread t2 = new Thread(a);
        Thread t3 = new Thread(a);
        Thread t4 = new Thread(a);
        Thread t5 = new Thread(a);


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

    }
}

3.还有一种匿名内部类的方式:

package demo3;

public class Test3 {
    public static void main(String[] args) {
        // 继承Thread类实现多线程
        new Thread() {
            public void run() {
                for (int i = 0; i < 100; i++) {                      System.out.println(Thread.currentThread().getName() + "--" + i);
                }
            }
        }.start();
        // 实现Runnable接口实现多线程
        new Thread(new Runnable() {

            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "--" + i);
                }
            }
        }) {
        }.start();
    }
}

因为java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时候在用继承Thread类方法来创建线程显然是不可能的.java设计者们提供了另一个方式创建线程,就是通过实现Runnable接口来穿件线程.

Thread类中的start()方法和run()方法有什么区别?

start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样.当你调用run()方法的时候,只会在原来的线程中调用,没有新的线程启动,start()方法才会启动新线程.

线程安全问题

什么是线程安全?
想要深入理解线程安全,必须要了解两个主要的点:java内存模型和java的线程同步机制.
简单说,线程安全就是代码在多线程下执行和在单线程下执行永远都能获得一样的结果
线程安全级别:
1.不可变
像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新创建一个,因此这些不可变对象不需要任何同步手段就可以直接在多线程环境下使用
2.绝对线程安全
不管运行时环境如何,调用者都不需要额外的同步措施。例如CopyOnWriteArrayList、CopyOnWriteArraySet
3.相对线程安全
也就是我们通常意义上所说的线程安全,像Vector这种,add、remove方法都是原子操作,不会被打断,但是也仅限于此。如果有个线程遍历某个vector、有一个线程同时在add这个vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast机制
4.线程非安全
ArrayList、LinkedList、HashMap等都是线程非安全的类

怎么解决线程安全问题呢?

通常我们使用同步(关键字为synchronized)来解决这种由于多线程同时操作共享数据带来的线程安全问题。

同步可以理解为:我们将多条操作共享数据的语句代码包成一个整体,让某个线程执行时,其他线程不能执行。

同步方案包括三种方式,它们对应的锁对象是不一样的。另外我们可以通过加锁来代码块,解决安全问题。

注意:
同步可以解决问题的根本原因就在于锁对象上,因此要避免线程安全问题,多个线程必须使用同一个锁对象,否则,不能解决问题。

解决方案:
1、同步代码块

synchronized(对象) {
                //需要被同步的代码;
            }

            //这里的锁对象可以是任意对象。

2、同步方法
就是在方法上加synchronizaed关键字,这时的锁对象是this。
3、静态同步方法
格式:将同步加在静态方法上。此时的锁对象为当前类的字节码文件对象。
4、加锁Lock
将需要一次执行完代码块用lock()和unlock()包裹,来保证线程安全。

Java中线程安全的类
1、Vector与ArrayList
Vector属于线程安全级别的,但大多数情况下都不适用Vector,因为线程安全需要更大的系统开销。
2、HashTable与HashMap
HashTable中的方法是同步的,而HashMap中的方法是在缺省情况下是非同步的。在多线程并发的环境下,可以直接使用hashtable,但是要使用HashMap的话就要自己增加同步处理了。HashTable是线程安全的,即HashTable的方法都提供了同步机制;HashMap不是线程安全的,即不提供同步机制;HashTable不允许插入空值,HashMap允许。
3、StringBuilder与StringBuffer
StringBuilder和StringBuffer的方法是一模一样的,StringBuffer线程安全,StringBuilder线程不安全,但是性能更高。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值