多线程——创建线程、线程安全问题

馋奶兔 提交于 2020-02-28 07:22:41

程序、进程、线程

程序:是为完成特定任务、用某种语言编写的一组指令的集合。即一段静态的代码,静态对象。
进程:是程序产生的一次执行过程,或是正在运行的一个程序。是一个动态的过程:由它自身
	  的产生、存在和消亡的过程——生命周期。
	  >如:运行中的QQ,运行中的MP3播放器
	  >程序是静态的,进程是动态的
	  >进程作为资源分配的单位,系统在运行时会为每个进程分匹配不同的内存区域
线程:进程可进一步细化为线程,是一个程序内部的一条执行的路径。
	  >若一个进程同时并行执行多个线程,就是支持多线程的
	  >线程作为进程调度和执行的最小单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
	  >一个进程中的多个线程共享相同的内存单元/内存地址空间->他们从同一堆中分配对象,可以访问呢相同的
	   变量和对象。这就使得线程间通信更便捷、高效、。但是多个线程操作共享的系统资源可能会带来安全的隐患。

创建线程

 1、创建线程的方式
    -1:继承Thread类(Thread类实现了Runnable接口)
    -2:实现Runnable接口(推荐使用。类实现了接口,还可以继承其他类,接口多实现,继承是单继承)
    ----------------------------------------
    继承Thread类:
    public class ThreadA extends Thread{
    	//重写 run方法
		@Override
		public void run(){
		}
	}
	实现Runnable接口:
	public class ThreadA implements Runnable{
    	//重写 run方法
		@Override
		public void run(){
		}
	}

启动线程

1、调用线程的start()方法。注:调用start()方法才是真正的启动线程,而调用run()方法,相当于
   调用了一个普通的方法,不能启动线程。
   -------------------------------------------------------
   继承Thread的启动线程方式:
   ThreadA t = new ThreadA();
   t.start();
   
   实现Runnable接口的启动线程方式:
   /**
   *  Thread类中的方法      
   *  ThreadA t1= new Thread(Runnable target)
   */
   ThreadA t = new Thread(new ThreadA());
	t.start();

线程安全

  • 出现线程安全的原因

     1、多个线程执行的不确定性引起执行结果的不稳定
     2、如:多个线程对账本的共享,会造成操作的不完整性,会破坏数据
    

    如下图,账户原始余额为3000,A和B同时(并发)从账户中取出2000元,账户余额变成-1000,这种问题就是线程安全问题。
    账户取钱

  • 解决线程安全问题——线程同步

     方式一:同步代码块
     1、同步监视器,俗称:锁。任何一个类的对象都可以充当锁,要求多个线程必须共用同一把锁
     2、同步监视器可以是:当前类.class(Class clazz = 当前类.class(),类只加载一次,
     					所以这种方式创建的对象是唯一的)、其他类对象、this(当前类的对象)
     3、在继承Thread类创建多线程的方式中,慎用this(当前对象)充当同步监视器(确保)
     4、在实现Runnable接口创建多线程的方式中,我们可以考虑使用this作为同步监视器
     synchronized(同步监视器){
     	//需要同步的代码 (操作共享数据的代码,如:火车票的数量),同步的代码不能包含少了,也不能包含多了
     }		
     
     方式二:同步方法
     优点:解决了线程安全的问题
     缺点:操作同步代码时。只能一个线程参与,其他线程等待。相当于是一个单线程过程,效率低
     1、如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步方法
     2、总结:
     		(1)同步方法仍然涉及到同步监视器,只是不需要我们显示的声明
     		(2)非静态的同步方法,同步监视器是 this;
     			 静态的同步方法,同步监视器是:当前类本身(类.class)。
    

解决线程安全问题(实现Runnable)——同步代码块

public class ThreadA implements Runnable{

	    private int ticket = 100;
	    /**
	     * 正确方式
	     */
	    //Object obj = new Object();
	
	    @Override
	    public void run() {
	        //错误方式 创建了多个Object对象
	        //Object obj = new Object();
	        while (true){
	            //错误方式 创建了多个Object对象
	            //synchronized(new Object()) {
	            //synchronized(ThreadA .class) {
	            synchronized(this) { 
	            //共用一把锁
	            //synchronized(obj) {
	                if (ticket > 0) {
	                    try {
	                        Thread.sleep(100);
	                    } catch (InterruptedException e) {
	                        e.printStackTrace();
	                    }
	                    System.out.println(Thread.currentThread().getName() + ":买票,票号为" + ticket);
	                    ticket--;
	                } else {
	                    break;
	                }
	            }
	        }
	    }
	
	    public static void main(String[] args) {
	        ThreadA t = new ThreadA();
	
	        Thread t1 = new Thread(t);
	        Thread t2 = new Thread(t);
	        Thread t3 = new Thread(t);
	
	        t1.setName("窗口1");
	        t2.setName("窗口2");
	        t3.setName("窗口3");
	
	        t1.start();
	        t2.start();
	        t3.start();
	    }
	}

解决线程安全问题(实现Runnable)——同步方法

同步方法的同步监视器为this

public class ThreadC implements Runnable{

    private int ticket = 100;

    @Override
    public void run() {
        while (true){
            sellTicket();
        }
    }
    
	//同步方法,使用关键字synchronized修饰方法
    private synchronized void sellTicket(){
        if(ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ",买票,票号:" + ticket);
            ticket--;
        }
    }

    public static void main(String[] args) {
        ThreadC t = new ThreadC();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

解决线程安全问题(继承Thread)——同步代码块

public class ThreadB extends Thread{

    private int ticket = 100;

    //正确方式
    //private Object obj = new Object();


    @Override
    public void run() {
        while (true){
            synchronized (ThreadB.class){  //正确方式
//            synchronized (obje){ //正确方式
//            synchronized (this){  //该方式会有错误
                if(ticket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+",买票,票号:"+ticket);
                    ticket--;
                }else{
                    break;
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadB t = new ThreadB();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

解决线程安全问题(继承Thread)——同步方法

public class ThreadD extends Thread{

    private int ticket = 100;

    @Override
    public void run() {
        while (true){
            sellTicket();
        }
    }

    //private synchronized void sellTicket(){ //错误的解决方式
    private static synchronized void sellTicket(){ // 监视器为ThreadD.class
        if(ticket > 0){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ",买票,票号:" + ticket);
            ticket--;
        }
    }

    public static void main(String[] args) {
        ThreadD t = new ThreadD();

        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

解决线程安全问题方式三:Lock锁 — JDK5.0新增

class Window implements Runnable{
    private int ticket = 100;

    /**
     * 实例化锁
     * 参数 fair(boolean):
     *      true-公平的竞争锁
     *      false(默认)-随机竞争锁
     *  当使用继承的方式实现多线程时,由于创建了多个对象,就需要将lock设置为static转为类层级,保证锁的唯一
     */
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                //加锁 保证执行下面的代码是单线程的
                lock.lock();

                if(ticket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+",买票,票号:"+ticket);
                    ticket--;
                }else {
                    break;
                }
            } finally {
                //解锁
                lock.unlock();
            }
        }
    }
}


public class LockTest {

    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口1");
        t2.setName("窗口1");
        t3.setName("窗口1");

        t1.start();
        t2.start();
        t3.start();
    }
}

Synchronized与Lock的异同

▼相同:
二者都可以解决线程安全问题

▼不同:
	1、Synchronized会在执行完相应的同步代码以后会自动释放锁(同步监视器)。
	2、Lock需要我们手动添加锁,调用lock()方法,并且需要手动的释放锁,调用unlock()方法。
	3、使用Lock锁,JVM会花费较少的时间去调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。
	
▼两种方式优先使用顺序:
Lock→同步代码块(已经进入了方法体,分配了相应的资源)→同步方法(在方法体之外)
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!