线程的基本概念以及锁对象和线程安全类

1)基本概念区分:

    程序:是一段静态的代码,是我们解决问题思维方式在计算机中的描述语言,是应用程序执行的蓝本,是一个静态的概念,存放在外存上,还没有运行的软件叫程序。

    进程:是程序的一个运行例程,用来描述程序的动态执行过程。是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

    线程:是程序中相对独立的一个程序段的执行单元。是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。

    多线程编译:一个程序运行时,可以分成几个并发的子程序任务同时处理(“同时”执行是人的感觉,在线程之间实际上轮换执行)每个子任务都称为一个线程,彼此相互独立。

    2)Java线程的运行机制

    JVM(Java虚拟机)很多程序都依赖线程调度,执行代码的任务是由线程完成的,每一个线程都有一个独立的程序计数器和方法调用栈。

    程序计数器:是记录线程当前执行程序代码位置的寄存器,在线程执行的过程中,程序计数器指向下一条要执行的指令。

    方法调用栈:用来描述程序执行时一系列方法调用过程。栈中的每一个元素对应一个浅帧,每一个帧对应一个方法调用,帧中保存方法调用的参数、局部变量和程序执行过程中的临时参数。

        2.1)线程的执行:使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。

1、扩展java.lang.Thread类。此类中有个run()方法,应该注意其用法: public void run()
Thread类的常用方法
方法 方法说明
void run() 线程运行时所执行的代码都会在这个方法中,
       是Runnable接口声明中的唯一方法
void start() 是该线程开始执行,Java虚拟机调用run()方法
 start int activeCount( ) 返回当前线程的线程组中的活动线程数目
start  Thread currentThread( ) 返回对当前正在执行线程对象的引用
start  int enumerate(Thread[ ]  t ) 将当前线程的每一个活动线程复制到指定的数组中
String getName( ) 返回该线程的名称
int getPriority( ) 返回该线程的优先级
Tstatic void yield( )       暂停当前正在执行的线程,并执行其他线程
Thread Group getState( ) 返回该线程的状态
Thread Group getThreadGroup( ) 返回该线程所属的线程组
final boolean isAlive( ) 测试线程是否属于活动状态
void setDaemon(boolean on) 将该线程标记为守护线程或用户线程
void setName(String name) 改变线程名称,使之与参数name相同
void interrupt( ) 中断线程
void join( )
        等待该线程终止,它有多个重载方法

检验多线程安全问题的标准:

        1) 是否是多线程环境

        2) 是否有共享数据

        3) 是否有多条语句对共享数据进行操作

优化第三个前提条件,使用synchronized同步代码块的方式将3)包起来!

    格式:    理解为:门的开和关

           synchronized(锁对象(隐示锁)){

        多条语句对共享数据进行操作;

            }

    锁对象:可以是任意的Java,每一个子线程共有同一个锁(不能使用多个锁)

需求:某电影院出售某些电影的票(复联3,红高粱....),有三个窗口同时进行售票(100张票),请您设计一个程序,模拟电影院售票(两种方式:继承、 接口)

          [例一]继承
(主程序) package org.westos_01;public class SellTicketDemo {
public static void main(String[] args) {

SellTicket st1 = new SellTicket() ;//创建三个子线程,分别代码三个窗口

SellTicket st2 = new SellTicket() ;

SellTicket st3 = new SellTicket() ;

st1.setName("窗口1");//设置线程名称
        st2.setName("窗口2");
        st3.setName("窗口3");

        st1.start();//启动线程
        st2.start();
        st3.start();
        }
        }

(子程序)

package org.westos_01;//SellTicket线程
      public class SellTicket extends Thread {
//为了不让外界更改这个类中的数据 //private//定一个票数
//要让每一个线程都要共同使用同一个数据,应该被static修饰// private int tickets = 100 ;
private static int tickets = 100 ;
@Override
public void run() {
//为了模拟电影卖票(模拟一直有票)//死循环
//st1,st2,st3都有执行这个里面的方法//st1 100//st2 100//st3 100
while(true) {
if(tickets>0) {
System.out.println(getName()+"正在出售第"+(tickets--)+"张票");
}}}}

[例二]接口

(子程序)package org.westos_02;
       public class SellTicket implements Runnable {
private int tickets = 100 ;//定义100张票
@Override
public void run() {
while(true) {
if(tickets>0) {
System.out.println(Thread.currentThread().getName()
+"正在出售第"+(tickets--)+"张票");
}}}}

package org.westos_02;//第二种方式实现
       //给继续加一些内容(延迟操作实现)
      (主程序)  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();
}
}

//现实中网络售票有延时,故让程序每一次抢票的进程休息100毫秒

        public class SellTicket implements Runnable {
private int tickets = 100 ;//定义100张票
@Override
public void run() {
while(true) {
//t1先进来  t2
if(tickets>0) {//为了模拟更真实的场景(网络售票有延迟的),稍作休息

try {
//t1睡 t2睡
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
//100张票 但此时打印结果出现0、-1并且窗口2票数是73张、窗口1票数是73张、窗口3票数是73张出现一张票被卖多次的现象
问题:为什么会出现同一张票被卖多次?
答: 出现同票的原因:CPU的执行有一个特点(具有原子性操作:最简单最基本的操作)
         t1线程进来,睡完了(延时100ms),100张票
        原子性操作:记录以前的值
                接着tickets-- :票变成99张票
        在马上输出99张票之前,t2/t3进来,直接输出记录的以前那个tickets的值,故出现一票多卖现象。

        问题:为什么会出现0、-1张票

        答:(延迟操作+线程的执行随机性)

        2.2)在程序中synchronized(锁对象) {
多条语句对共享数据操作的代码;

                }
 注意:锁对象:一定要同一个锁(每个线程只能使用同一把锁),锁对象:任何的Java类(引用类型)

改进后的程序:package or_west_01;
        public class StDemo implements Runnable {
private int ticket=100;//要让每一个线程都要共同使用同一个数据,应该被static修饰
private Object obj=new Object();
@Override
public void run() {
synchronized(obj) {//在循环入喉处加入锁开关,可解决下面两个问题
while(true) {

if(ticket>0) {
try {
Thread.sleep(100);//现实中网络售票有延时,故让程序每一次抢票的进程休息100毫秒
//但此时打印结果出现0、-1并且窗口2票数是73张、窗口1票数是73张、窗口3票数是73张出现一张票被卖多次的现象
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"票数是"+(ticket--)+"张");
}}}}}




//如果一个方法一进来就是同步代码块,那么可不可以将同步放到方法来进行声明呢? 可以

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

//非静态的方法:同步方法(需要底层源码,一些方法会声明synchronized)的锁对象:this
//同步方法 :里面锁对象是谁? this
private synchronized  void sellTicket() {
if(tickets>0) {
try {
Thread.sleep(100); 
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+"正在出售第"+(tickets--)+"张票");
}
}
//静态的同步方法:和反射有关 (静态同步方法的锁对象:类名.class)

        2.3)接口:Lock锁(Jdk5.0以后,java提供了一个具体的锁: )
private Lock lock= new ReentrantLock(); //显示获取锁的前提,一定要创建Lock接口对象

    Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构 可以使用Lock锁进行具体的锁定操作类 提供了具体的实现类:ReentrantLock加锁并且去释放锁

         子 程序可改为:  package org.westos_07;
            import java.util.concurrent.locks.Lock;
            import java.util.concurrent.locks.ReentrantLock;
            public class SellTicket implements Runnable {
    // 定义票
    private int tickets = 100;
    // Object obj = new Object();
    private Lock lock= new ReentrantLock(); //显示获取锁的前提,一定要创建Lock接口对象
    @Override
    public void run() {
while (true) {
try { //try...finally
lock.lock(); // 获取锁    syncrhonized(obj)
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票");
}

} finally {//释放锁
if(lock!=null) {
lock.unlock();
}
}}}
 
2.4)线程安全的类:

StringBuffer sb = new StringBuffer() ;
Vector<String> v = new Vector<String>() ;
Hashtable<String, String> hm = new Hashtable<String,String>() ;

//Vector<String>它线程安全的类,还是不习惯使用这个集合,通过ArrayList集合:线程不安全的类
List<String> array = new ArrayList(); //线程不安全的类

//public static <T> List<T> synchronizedList(List<T> list)
//返回指定列表支持的同步(线程安全的)列表
List list = Collections.synchronizedList(array) ; //线程安全的方法


    2.5)解决了多线程安全问题,但是还是有些问题:
1)执行效率低
2)会产生死锁(两个或两个以上的线程,在执行的过程中出现互相等待的情况,就叫做死锁!)

死锁示例程序:

(主程序)public class DieLockDemo {
public static void main(String[] args) {

//创建线程了对象
DieLock dl1 = new DieLock(true) ;
DieLock dl2 = new DieLock(false) ;

//启动线程
dl1.start();
dl2.start();}}

(子程序)package org.westos_08;
                  public class DieLock extends Thread {
           private boolean flag ;//声明一个成语变量
           public DieLock(boolean flag) {
            this.flag = flag ;}
//重写run方法
@Override
public void run() {
if(flag) {
synchronized (MyLock.objA) {
System.out.println("if ObjA");
synchronized (MyLock.objB) {
System.out.println("if objB");
}
}
}else {
synchronized (MyLock.objB) {
System.out.println("else objB");
synchronized (MyLock.objA) {
System.out.println("else objA");
}
}
}
}}

在程序执行后打印情况出现以下三种:
   第一种情况:if ObjA
else objB
    第二种情况
else objB
if ObjA
     第三种情况:
理想状态
else objB
else objA
if ObjA
if objB

if ObjA
if objB
else objB
else objA








猜你喜欢

转载自blog.csdn.net/qq_41141896/article/details/80488319