Java 多线程

一. 基本名词概念

1. 进程:一个运行的程序

2. 线程:是进程中的单个顺序控制流,是一条执行路径

(1)单线程:一个进程只有一条执行路径(记事本中的点开设置)

(2)多线程:一个进程有多条执行路径(扫雷中的计时器和游戏)

二. Java实现多线程

1. 继承Thread类的方式实现多线程

(1)定义类MyThread继承Thread类

(2)在MyThread中重写run()方法

(3)创建MyThread对象,启动线程

2. 设置和获取线程名称

(1)如果不设置线程名称默认名为"Thread-0, Thread-1..."

(2)setName("xx")设置名称,getName()获取名称

(3)设置名称也可以重写构造函数,调用父类Thread的带参构造函数,传入name

(4)获取当前正在执行的线程对象的引用:静态方法Thread.currentThread()

public class MyThread extends Thread{
    public MyThread() {
        super();
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {//快捷键Ctrl+O
        for(int i=0;i<1000;i++){
            System.out.println(getName()+":"+i);
        }
    }
}
public class MyThreadDemo {
    public static void main(String[] args) {
        MyThread my1=new MyThread("hh1");
        MyThread my2=new MyThread("hh2");
        //run()封装线程执行的代码
        //my1.setName("Hello1");
        //my2.setName("Hello2");
        my1.start();//启动线程,由JVM调用run()方法
        my2.start();
        System.out.println(Thread.currentThread().getName());
    }
}

3. 线程调度

线程调度有两种模型:

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

• 抢占式调度模型:优先让优先级高的线程使用CPU,如果优先级相同则随机选择一个

Java使用的是抢占式调度模型

Thread类中提供的方法:

public final void setPriority(int newPriority)//更改此线程的优先级
public final int getPriority()//返回此线程的优先级。
MIN_PRIORITY=1
MAX_PRIORITY=10
NORM_PRIORITY=5//默认

4. 线程控制

public static void sleep(long millis)//当前正在执行的线程休眠(暂停执行)为指定的毫秒数
public final void join()//等待这个线程死亡,等价于join(0)
public final void setDaemon(boolean on)//标志着该线程是守护线程,如果运行的线程都只剩下守护线程是,Java虚拟机退出
//Java中除了用户进程就是守护进程,守护进程是后台服务进程,比如垃圾回收进程

5. 线程生命周期

6. 实现Runnable接口的方式实现多线程

(1)定义一个类MyRunnable实现Runnable接口

(2)在Runnable类中重写run()方法

(3)创建MyRunnable对象

(4)创建Thread类的对象,把MyRunnable对象作为构造方法的参数

(5)启动线程

好处:

• 避免了Java单继承的局限性,MyRunnable可以再继承其他父类

• 适合多个相同的程序的代码去处理同一个资源的情况,较好的体现面向对象思想

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);//不能直接getName()
        }
    }
}
public class MyRunnableDemo {
    public static void main(String[] args) {
        MyRunnable my=new MyRunnable();
        Thread t1=new Thread(my);
        Thread t2=new Thread(my,"hh");//带设置名的构造函数
        t1.start();
        t2.start();
    }
}

三. 线程同步

1. 卖票案例

电影院出售1000张票,三个窗口同时卖票,每次出票时间10ms

非同步出现的问题:

• 相同的票出现了多次(三个线程都打印"第100张票正在出售")

• 出现了负数的票

问题原因:

• 线程执行的随机性导致的

2. 安全问题的解决

解决方式:把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

(1)同步代码块:

synchronized (任意对象){
  多条语句操作共享数据的代码
}

synchronized相当于给代码加锁,任意对象就可以看成一把锁

同步的好处和弊端:

• 好处:解决了多线程的数据安全问题

• 弊端:线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中降低了程序运行效率

public class SellTicket implements Runnable{
    private int ticket=1000;
    private Object obj=new Object();//三个线程要用同一把锁,所以放一个obj变量在这
    @Override
    public void run() {
        while(true){
            synchronized (obj){//同步代码块
                //t1进来后,就会把这段代码锁起来
                if(ticket>0){
                    try {
                        Thread.sleep(10);//快捷键Ctrl+Alt+T
                        //即使t1在休息,t2抢到CPU执行权,也无法执行这段代码
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
}
public class SellTicketDemo {
    public static void main(String[] args) {
        SellTicket st=new SellTicket();
        Thread t1=new Thread(st,"窗口1");
        Thread t2=new Thread(st,"窗口2");
        Thread t3=new Thread(st,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

(2)同步方法:

同步方法:就是把synchronized关键字加到方法上

修饰符 synchronized 返回值类型 方法名(参数){}

同步方法的锁对象:this

同步静态方法:就是把synchronized关键字加到静态方法上

修饰符 static synchronized 返回值类型 方法名(参数){}

同步静态方法的锁对象:类名.class

public class SellTicket implements Runnable{
//    private int ticket=1000;
    private static int ticket=1000;
    private Object obj=new Object();//三个线程要用同一把锁,所以放一个obj变量在这
    private int x=0;
    @Override
    public void run() {
        while(true){
            if(x%2==0){
//                synchronized (obj){
//                synchronized (this){//同步方法的锁对象:this
                synchronized (SellTicket.class){//同步静态方法的锁对象:该类的字节码文件对象
                    if(ticket>0){
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
                        ticket--;
                    }
                }
            }else{
                sellTicket();
            }
            x++;
        }
    }

    private static synchronized void sellTicket() {
        if(ticket>0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
            ticket--;
        }
    }
}

3. 几个线程安全的类

(1)StringBuffer

• 线程安全,可变的字符序列,源码中的方法都加了synchronized

• 如果不需要同步,应使用StringBuilder替代

(2)Vector

• 改进了List接口,使其成为Java Collections Framework的成员

• 如果不需要线程安全,应使用ArrayList替代

(3)Hashtable

• 实现了一个哈希表,任何非null值可作为键或值

• 如果不需要同步,建议使用Hashmap替代(Hashmap允许键或值均可为null,而Hashtable均不可)

(4)工具类转为线程安全

举例:

List<String> synList = Collections.synchronizedList(new ArrayList<>());

4. Lock锁

为了体现更为清晰而广泛的加锁释放锁过程,JDK5之后提供了一个新的锁对象Lock

void lock() //获得锁
void unlock() //释放锁

Lock是接口不能直接实例化,可以采用它的实现类ReentrantLock来实例化

public class SellTicket implements Runnable{
    private int ticket=1000;
    private Lock lock =new ReentrantLock();
    @Override
    public void run() {
        while (true){
            try {//常用try-finally块,即使代码出问题也会释放锁
                lock.lock();
                if(ticket>0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
                    ticket--;
                }
            } finally {
                lock.unlock();
            }
        }
    }
}

 5. volatile关键字

(1)volatile是一种轻量级的同步机制,相比于synchronized和Lock(synchronized通常称为重量级锁),volatile更轻量级;

(2)volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性,所以不能用于非原子操作(赋值是原子操作);

(3)volatile用于修饰类变量或成员变量,被修饰的变量在缓存中的修改会被立即写入主存(一般变量只是先写入线程私有的本地内存);

(4)volatile在操作系统底层是内存屏障的原语,用于解决可见性和有序性问题;

(5)应用场景:单例模式

public class TestInstance{
    private volatile static TestInstance instance;
    
    public static TestInstance getInstance(){        //1
        if(instance == null){                        //2
            synchronized(TestInstance.class){        //3
                if(instance == null){                //4
                    instance = new TestInstance();   //5
                }
            }
        }
        return instance;                             //6
    }
}

如果不用volatile,第五行代码在多线程情况下会出现问题,因为instance = new TestInstance(); 事实上有三个执行步骤

a. memory = allocate() //分配内存

b. ctorInstanc(memory) //初始化对象
 
c. instance = memory //设置instance指向刚分配的地址

由于可能存在指令重排,如果一个进程1先执行了ac,另一个线程b判断instance == null就会认为是false,产生错误。

三. 生产者消费者模型

Java在Object类提供了一些方法,体现生产和消费过程中的等待和唤醒:

void wait() //使当前线程等待,直到另一个线程调用此对象的notify()或notifyAll()方法
void notify() //唤醒一个在这个对象的监视器上等待的单个线程
void notifyAll() //唤醒正在等待此对象监视器上的所有线程

• 这些方法需要在同步的条件下,一般配合synchronized使用

• 如果等待的线程有多个,notify方法只会唤醒其中一个,唤醒哪一个取决于操作系统对于多线程的管理

• notify/notifyAll() 的执行只是唤醒沉睡的线程,而不会立即释放锁,锁的释放要看代码块的具体执行情况

生产者消费者代码示例:

public class Box {
    //成员变量,表示第x件商品
    private int good;
    //表示箱内是否有商品
    private  boolean state=false;

    public synchronized void put(int good) {//注意要加synchronized关键字,否则wait()抛异常
        if(state){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.good = good;
        System.out.println("生产者将第"+this.good+"件商品放入箱中");
        state=true;
        notifyAll();
    }

    public synchronized void get() {
        if(!state){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("消费者拿到第"+good+"件商品");
        state=false;
        notifyAll();
    }
}
public class Producer implements Runnable{
    private Box b;

    public Producer(Box b){
        this.b=b;
    }
    @Override
    public void run() {
        for(int i=1;i<=5;i++){
            b.put(i);
        }
    }
}
public class Customer implements Runnable{
    private Box b;
    public Customer(Box b){
        this.b=b;
    }
    @Override
    public void run() {
        while(true){
            b.get();
        }
    }
}
public class BoxDemo {
    public static void main(String[] args) {
        Box b=new Box();//存储箱类
        Producer p=new Producer(b);//生产者对象
        Customer c=new Customer(b);//消费者对象
        Thread t1=new Thread(p);//生产者线程
        Thread t2=new Thread(c);//消费者线程
        t1.start();//启动线程
        t2.start();
    }
}

输出:

原文地址:https://www.cnblogs.com/Kinghao0319/p/13394596.html