线程,同步

线程

多线程的好处:多个线程互不影响(因为在不同的栈空间)

Thread类

java.lang.Thread

构造方法:

  • public Thread():分配一个新的线程对象。

  • public Thread(String name) :分配一个指定名字的新的线程对象。

  • public Thread(Runnable target):分配一个带有指定目标新的线程对象。

  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

常用方法:

  • public String getName() :获取当前线程名称。

  • public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。

  • public void run() :此线程要执行的任务在此处定义代码。

  • public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。

  • public static Thread currentThread() :返回对当前正在执行的线程对象的引用。

    直接打印[Thread-0,5,main]

    分别代表:线程名,优先级,在哪个方法

创建多线程的第二种方式:

1.创建一个实现类来实现Runnable接口并重写run方法
package com.qin.study;
​
/**
 * Created by SunYuqin in 2018/8/8
 * Description:
 **/
​
public class RunnableImpl implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}
2.通过Thread中的构造创建线程
public class RunnableImplTest {
    public static void main(String[] args) {
        Runnable runnable = new RunnableImpl();
        Thread td = new Thread(runnable);
        td.start();
​
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
​
    }
}

采用java.lang.Runnable,只需要重写run方法

定义Runnable的实现类或者是匿名内部类,重写run方法,使用Thread的构造方法实例作为Thread的target对象来创建线程对象.

实现Runnable借口创建多线程程序的好处:

  • 避免了单继承的局限性:

    一个类只能继承一个类,类继承了Thread就不能继承其他类,而实现了Runnable借口还可以继承其他类

  • 增强了程序的扩展性,降低了程序的耦合性(解耦)

    实现Runnable接口的方式,增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

  • 适合多个相同的程序代码的线程去共享同一个资源

  • 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。

匿名内部类实现线程的创建

package com.qin.study;
​
/**
 * Created by SunYuqin in 2018/8/8
 * Description:
 **/
​
public class RunnableNoName {
    public static void main(String[] args) {
        //Thread类的匿名创建
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"--->"+i);
                }
            }
        }.start();
​
        //Runnable接口的形式匿名内部类
        new  Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"--->"+i);
                }
            }
        }).start();
    }
}

后来可以使用lamada表达式简化

线程安全

线程安全

多线程访问了共享的数据会产生安全问题

问题:电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。

在不考虑安全问题的代码实现

//Runnable的实现类
public class Ticket implements Runnable {
    private int ticket = 100;
    @Override
    public void run() {
        while (true) {
                if (ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //获取当前线程的名字
                    String name = Thread.currentThread().getName();
                    System.out.println(name+"正在卖"+ticket--);
                }
​
        }
    }
}
//Test类
​
public class TicketTest {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread thread1 = new Thread(ticket,"窗口1");
        Thread thread2 = new Thread(ticket,"窗口2");
        Thread thread3 = new Thread(ticket,"窗口3");
        thread1.start();
        thread2.start();
        thread3.start();
​
    }
}

会出现两个问题:

  1. 相同的票数,比如5这张票被卖了两回。

  2. 不存在的票,比如0票与-1票,是不存在的。

线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

线程同步

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。

同步技术的原理

使用一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视锁

3个线程一起抢夺cpu的执行权,谁抢到了谁执行run方法进行卖票

  • t0抢到了cpu的执行权,执行run方法,遇到synchronized代码块

    这时t0会检查synchronzed代码块是否有锁对象,发现有,就会获取到锁对象,进入到同步中执行,出了同步会把锁对象归还.

  • t1到了cpu的执行权,执行run方法,遇到synchronized代码块

    这时t0会检查synchronzed代码块是否有锁对象,发现没有,就会进入到阻塞状态,会一直等待t0线程归还锁对象...

同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁,就进不去同步

同步保证了只能有一个线程在同步中处理共享数据.保证了安全性

但是在频繁的判断锁,获取锁,释放锁,程序的效率会降低

有三种操作:

  1. 同步代码块

  2. 同步方法

  3. 锁机制

  • 1.同步代码块:

格式

synchronized(锁对象){

​ 可能会出现安全问题的代码(访问了)

  1. 通过代码块中的锁对象,可以使用任意的对象(引用类型),也可以使用"abc".因为是静态的,能保证同一个

  2. 但是必须保证多个线程使用的锁对象是同一个

  3. 在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

  4. 锁对象的作用:

    把同步代码块锁住,只让一个线程在同步代码块中运行

修改同步部分的代码使用同步代码块的方法:

public class Ticket implements Runnable {
    private int ticket = 100;
    Object obj = new Object();
    @Override
    public void run() {
        while (true) {
​
            synchronized (obj){
                if (ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //获取当前线程的名字
                    String name = Thread.currentThread().getName();
                    System.out.println(name+"正在卖"+ticket--);
                }
            }
​
​
        }
    }
}
​
  • 2.同步方法

同步方法也会把方法内部的代码锁住,只让一个线程执行,同步方法的锁对象是谁?

-->就是实现对象new RunnableImpl(),也就是this

  1. 把访问了共享数据的代码抽取出来,放在一个方法中

  2. 在方法上添加synchronzed修饰符

修改同步部分的代码使用同步方法的方法:

public class Ticket implements Runnable {
    private int ticket = 100;
    Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            ticketSell();
        }
    }
​
    public void ticketSell() {
            if (ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //获取当前线程的名字
                String name = Thread.currentThread().getName();
                System.out.println(name+"正在卖"+ticket--);
            }
    }
}

静态方法的话,要注意加上static

静态同步方法:

  1. 锁对象是谁?

    不能是this,this是创建对象之后产生的,而静态方案优先于对象,所以静态方法的锁对象是本类的class属性-->class文件对象(反射)

  2. 就是:Runnable.class

  • 3.lock锁机制

java.util.concurrent.locks.Lock接口机制提供了比synchronized代码块synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

Lock锁也称同步锁,加锁与释放锁方法化了,如下:public void lock():加同步锁。public void unlock() :释放同步锁。

使用步骤:

  1. 成员位置创建一个ReentrantLock对象

  2. 在可能会出现安全问题的代码前调用Lock接口的方法lock获取锁

  3. 在可能会出现安全问题的代码后调用ock接口的方法unlock释放锁

有一个写法,最好配合使用finally代码块:无论什么情况,最后都释放锁.

public class Ticket implements Runnable {
    private int ticket = 100;
    Lock lock = new ReentrantLock();
​
    @Override
    public void run() {
        while (true) {
​
            lock.lock();
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //获取当前线程的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在卖" + ticket--);
            }
​
            lock.unlock();
​
        }
    }
}
​


线程状态

线程状态概述:

线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行) 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
TimedWaiting(计时等待) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止) 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

几个方法的使用:

wait的作用:1.线程等待 2.释放锁

而sleep是没有释放锁的功能的

wait与notify只能是锁对象才可以调用

wait(long time)就相当于sleep(long time)

notify():如果有多个等待线程,随机唤醒一个

notifyAll():同时唤醒所有等待的线程

生产者,消费者使用wait和notify来观察线程的状态

代码实现

package com.qin.study;
​
/**
 * Created by SunYuqin in 2018/8/8
 * Description:
 * 消费者:买饭,wait,吃
 * 生产者:卖饭,notify,做饭
 **/
​
public class MealDemo {
    public static void main(String[] args) {
        Object obj = new Object();
        //消费者线程
        new Thread() {
            @Override
            public void run() {
                while (true) {
                    synchronized (obj) {
                        System.out.println("老板,来份饭 !!!");
​
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
​
                        System.out.println("开始吃饭,吃完了结账.");
                    }
​
                }
​
            }
        }.start();
        //生产者线程
        new Thread() {
            @Override
            public void run() {
                while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                    synchronized (obj) {
                        System.out.println("好嘞,开始做饭");
                        try {
                            Thread.sleep(2000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("饭做好了,您请用!");
                        obj.notify();
                    }
                }
            }
        }.start();
​
​
    }
}

注:两个线程是随机运行的.也可能会一直跑一个线程.

猜你喜欢

转载自blog.csdn.net/qq_35472880/article/details/81513257
今日推荐