JAVA 线程 之 初级线程安全

来源:互联网 发布:我的网络ip地址 编辑:程序博客网 时间:2024/05/29 13:52

第一part: 概述

首先介绍一些基本概念:程序、进程、线程的概念

程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process)是程序的一次执行过程,或是正在运行的一个程序。动态过程:有它自身的产生、存在和消亡的过程。
                          如:运行中的QQ,运行中的MP3播放器,程序是静态的,进程是动态的。
l线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径,若一个程序可同一时间执行多个线程,就是支持多线程的

为什么需要多线程:
1:程序需要同时执行两个或多个任务
2:程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等
3:需要一些后台运行的程序时

使用多线程的优势:
1:提高应用程序的响应。对图形化界面更有意义,可增强用户体验;
2:提高计算机系统CPU的利用率;
3:改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改;

线程的分类:
Java中的线程分为两类:一种是守护线程,一种是用户线程,它们在几乎每个方面都是相同的,唯一的区别是判断JVM何时离开。
守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
Java垃圾回收就是一个典型的守护线程。
若JVM中都是守护线程,当前JVM将退出

线程的生命周期:JDK中用Thread.State枚举表示了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态,new Thead对象
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件  调用start()方法
运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能   正在运行进程调用yield()方法
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态,sleep,  wait, suspend
死亡:线程完成了它的全部工作或线程被提前强制性地中止   执行完 run,或者出现异常


第二part用两种方式实现一个简单的售票功能(以下两种方式是存在线程安全的问题)
  
  1.继承Thread类
  
package com.thread;public class ThreadTest{public static void main(String[] args){Win win01 = new Win();win01.setName("窗口1");Win win02 = new Win();win02.setName("窗口2");Win win03 = new Win();win03.setName("窗口3");win01.start();win02.start();win03.start();}}class Win extends Thread{private static int ticket = 100;@Overridepublic void run(){while (true){if (ticket > 0){try{Thread.currentThread().sleep(10);// 此行代码会放大问题}catch (InterruptedException e){// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName() + "售票"+ ticket--);}else{break;}}}}

 2.实现Runnable接口

package com.thread;public class ThreadTest01{public static void main(String[] args){Win01 win = new Win01();Thread thread01 = new Thread(win);thread01.setName("窗口一");thread01.start();Thread thread02 = new Thread(win);thread02.setName("窗口二");thread02.start();Thread thread03 = new Thread(win);thread03.setName("窗口三");thread03.start();}}class Win01 implements Runnable{private int ticket = 100;@Overridepublic void run(){while (true){if (ticket > 0){try{Thread.currentThread().sleep(10);// 此行代码会放大问题}catch (InterruptedException e){// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName() + "售票"+ ticket--);}else{break;}}}}

两种方式比较:实现接口的方式优于继承类的方式;
原因:(1)实现接口的方式Java单继承的局限性 (2)如果多线程要操作同一份资源(数据),更适合用实现接口的方式,因为实现接口的方式只会产生一个实现了Runnable类的对象;
两种方式存在问题:都存在线程安全的问题,都出现了同一张票多次售出的情况,重票  错票 ;
线程安全存在的原因:
由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在了线程安全的问题;

第三Part JAVA如何解决线程安全的问题;
如果解决线程安全的问题,必须让一个线程操作共享数据完毕后,其他线程才有机会参与共享数据的操作。
JAVA如何实现线程的安全,线程的同步机制;
方法一:同步代码块;
synchronized (同步监视器){
          // 需要被同步的代码;即操作共享数据的代码
    }
1:共享数据:多个线程共同操作的数据  变量
2:同步监视器:由一个类的对象来充当,哪个线程获取此监视器,谁就执行大括号里被同步的代码。俗称:锁,要求所有的线程公用一把锁,这样才是安全的;

例一,实现了Runnable接口的多线程,处理线程安全的方式,如下:
package com.thread;public class ThreadTest{public static void main(String[] args){Win01 win = new Win01();Thread thread01 = new Thread(win);thread01.setName("窗口一");thread01.start();Thread thread02 = new Thread(win);thread02.setName("窗口二");thread02.start();Thread thread03 = new Thread(win);thread03.setName("窗口三");thread03.start();}}class Win01 implements Runnable{private int ticket = 100;@Overridepublic void run(){while(true){synchronized (this)//此处之所以写成this,是因为在实现runnable接口的类的对象在所有的线程中公用一个,此处也可以创建任意一个对象比如Object{if (ticket > 0){try{Thread.currentThread().sleep(10);// 此行代码会放大问题}catch (InterruptedException e){// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName() + "售票"+ ticket--);}else{break;}}}}}

例二,在继承了Thread的多线程中,

package com.thread;public class ThreadTest{public static void main(String[] args){Win win01 = new Win();win01.setName("窗口1");Win win02 = new Win();win02.setName("窗口2");Win win03 = new Win();win03.setName("窗口3");win01.start();win02.start();win03.start();}}class Win extends Thread{private static int ticket = 100;static Object object = new Object();//注意此处要用静态变量,才能保证是同一个对象,如果不是静态变量的话 是不能保证是同一个对象的 即 锁@Overridepublic void run(){while (true){synchronized (object)//注意此处不能用this,因为这里不同的Thread有不同的类对象,不是同一个对象{if (ticket > 0){try{Thread.currentThread().sleep(10);// 此行代码会放大问题}catch (InterruptedException e){// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName() + "售票"+ ticket--);}else{break;}}}}}

方法二:同步方法;
synchronized还可以放在方法声明中,表示整个方法
      为同步方法。
例如:
    public synchronized void show (String name){ 
            ….
   }

同步方法:将操作共享数据的方法声明为synchronized ,即此方法为同步方法,能保证当其中一个线程执行此方法时,其他线程在外等待直至此线程执行完此方法。

例一:实现了Runnable接口的多线程:
package com.thread;public class ThreadTest01{public static void main(String[] args){Win01 win = new Win01();Thread thread01 = new Thread(win);thread01.setName("窗口一");thread01.start();Thread thread02 = new Thread(win);thread02.setName("窗口二");thread02.start();Thread thread03 = new Thread(win);thread03.setName("窗口三");thread03.start();}}class Win01 implements Runnable{private int ticket = 100;@Overridepublic void run(){while (true){show();}}public synchronized void show()// 注意此处的锁还是this,因为在实现了Runnable接口的类中 多线程公用一个 该类,因此能解决线程安全的问题;{if (ticket > 0){try{Thread.currentThread().sleep(10);// 此行代码会放大问题}catch (InterruptedException e){// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName() + "售票"+ ticket--);}}}

例二:继承了Thread类的多线程;

package com.thread;public class ThreadTest{public static void main(String[] args){Win win01 = new Win();win01.setName("窗口1");Win win02 = new Win();win02.setName("窗口2");Win win03 = new Win();win03.setName("窗口3");win01.start();win02.start();win03.start();}}class Win extends Thread{private static int ticket = 100;@Overridepublic void run(){while (true){show();}}public synchronized void show()//注意此处的锁还是this,因此不能解决线程安全的问题;{if (ticket > 0){try{Thread.currentThread().sleep(10);// 此行代码会放大问题}catch (InterruptedException e){// TODO Auto-generated catch blocke.printStackTrace();}System.out.println(Thread.currentThread().getName() + "售票"+ ticket--);}}}

第四part
单例  懒汉模式存在线程安全;
对于一般的方法内,使用同步代码块,可以考虑用this充当锁,前提是实现runnable接口的多线程情况;
对于静态方法而言是没有对象的,用当前对象this不符合语法,建议使用当前类本身充当锁
代码如下:
class SingleTon{private SingleTon(){}private static SingleTon singleTon = null;public static SingleTon getSingleTon(){if(null == singleTon)//该行代码可以增加代码的执行效率{synchronized (SingleTon.class)//SingleTon.class返回的也是一个对象{if(null == singleTon){singleTon = new SingleTon();}}}return singleTon;}}
0 0
原创粉丝点击