java基础之线程

本文详细介绍了Java中线程的基本概念与创建方式,包括继承Thread类与实现Runnable接口两种方法。此外还探讨了线程的安全问题及解决方案,如使用synchronized关键字和Lock对象实现同步,以及如何避免死锁等高级主题。

一线程的概念
进程:是一个正在执行中的程序,每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
线程:就是进程中的一个独立的控制单元;线程在控制着进程的执行。
Java虚拟机启动的时候会有一个进程java.exe.该进程中至少一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中,该线程称之为主线程,每个进程中至少有一个线程。


二线程的创建
第一种方式:创建线程继承Thread类,
步骤:
1,定义类继承Thread。
2,复写Thread类中的run方法。
    目的:定义自己的任务让线程驱动。
3,调用线程的start方法,该方法两个作用:启动线程,调用run方法。
如下代码:
class MyThread extends Thread
{
    public void run()
    {
        for(int i=0; i<100; i++)
            System.out.println("Thread1 run----"+x);
    }
}
class MyThreadDemo
{
    public static void main(String[] args)
    {
        //for(int x=0; x<4000; x++)
        //System.out.println("Hello World!");

        MyThread t = new MyThread();//创建好一个线程。
        //t.start();//开启线程并执行该线程的run方法。
        t.run();//仅仅是对象调用方法。而线程创建了,并没有运行。

       
        for(int i=0; i<60; i++)
            System.out.println("Hello World!--"+i);
        }
}
  实现Runnable接口,实现接口里的run方法
步骤:
1,设计类实现Runnable接口实现该接口里的run方法;
2,创建该类的是两年作为参数传如Thread类的构造方法;
如下代码:
class MyThread implements Runnable{
        public void run(){
                  //这里定义自己的任务
            }
}
然后在创建启动线程
new(new MyThread()).start();

采用继承Thread类方式:
(1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
(2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。
采用实现Runnable接口方式:
(1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。


 


对象如同锁。持有锁的线程可以在同步中执行。
没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。


 

最后总结一下单例设计模式:

懒汉设计模式如下:

class Single
{
    private static Single s = null;
    private Single(){}


   public static  Single getInstance()
    {
         if(s==null)

           //--标记A

        s = new Single() ;    
                   
            }
        }
        return s;
    }

采懒汉设计模式时候存在安全隐患,当线程执行到A处的时候执行权被剥夺了停了了下来此时另一个线程就可能执行到此处这样就可能创建多个Single对象引发错误;这种设计必须同步处理如下代码:

  public static  Single getInstance()
    {
        if(s==null)
        {
            synchronized(Single.class)
            {
                if(s==null)
                   
                    s = new Single();
            }
        }
        return s;
    }
}
另外一种设计模式是饿汉式这种设计安全性比较高
class Single
{
    private static final Single s = new Single();
    private Single(){}
    public static Single getInstance()
    {
        return s;
    }
}

四  线程的安全问题的讨论与总结

同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源

加锁对象是Object的实例;也可以通过加锁函数来实现同步此时的加锁对象是调用该函数的this,静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class,如下代码:

 多线程编程时候,常常出现错误的情况,这是由于系统线程调度有一定的随机性,所以我们必须对临界资源进行同步,下面是一个经典的例子银行取钱问题

取钱的步骤:

1,用户输入帐号密码系统判断用户的账户密码是否匹配。

2,用户输入取款金额。

3,系统判断账户余额是否大于取款金额。

4,如果大于就成功否则就失败。

我们按上面的流程去编写取款程序,我们用两条线程来模拟取钱操作:

public class Account
{
    //封装账户编号、账户余额两个属性
    private String accountNo;
    private double balance;
    public Account(){}
    //构造器
    public Account(String accountNo , double balance)
    {
        this.accountNo = accountNo;
        this.balance = balance;
    }
    public void setAccountNo(String accountNo)
    {
        this.accountNo = accountNo;
    }
    public String getAccountNo()
    {
         return this.accountNo;
    }

    public void setBalance(double balance)
    {
         this.balance = balance;
    }
    public double getBalance()
    {
         return this.balance;
    }


    //下面两个方法根据accountNo来计算Account的hashCode和判断equals
    public int hashCode()
    {
        return accountNo.hashCode();
    }
    public boolean equals(Object obj)
    {
        if (obj != null
            && obj.getClass() == Account.class)
        {
            Account target = (Account)obj;
            return target.getAccountNo().equals(accountNo);
        }
        return false;
    }
}
接下来提供一个取钱的线程类,执行账户,取钱数量进行操作,逻辑是余额不足时候无法提取现金,当余额足够时候吐出钞票,余额减少。


public class DrawThread extends Thread
{
    //模拟用户账户
    private Account account;
    //当前取钱线程所希望取的钱数
    private double drawAmount;

    public DrawThread(String name , Account account ,
        double drawAmount)
    {
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }

    //当多条线程修改同一个共享数据时,将涉及到数据安全问题。
    public void run()
    {
        //账户余额大于取钱数目
        if (account.getBalance() >= drawAmount)
        {
            //吐出钞票
            System.out.println(getName() +
                "取钱成功!吐出钞票:" + drawAmount);
           
            //修改余额
            account.setBalance(account.getBalance() - drawAmount);
            System.out.println("\t余额为: " + account.getBalance());
        }
        else
        {
            System.out.println(getName() + "取钱失败!余额不足!");
        }
    }
}
现在启动2个线程该账户里取钱:
public class TestDraw
{
    public static void main(String[] args)
    {
        //创建一个账户
        Account acct = new Account("1234567" , 1000);
        //模拟两个线程对同一个账户取钱
        new DrawThread("甲" , acct , 800).start();
        new DrawThread("乙" , acct , 800).start();
    }
}
去掉上面的注释部分代码在多次运行后会发现最后余额会会出现负值的情况,这就是多线程并发访问共享资源是的错误
解决办法一 同步代码块:
synchronized(obj){} obj就是同步监视器,该代码的含义是线程访问同步代码前必须获得同步监视器的锁定。
Java程序中允许使用任何对象来做同步监视器,但是为了阻止多线程对同一资源的访问,必须使用的是同一个监视器。下面我们考虑用account作为同步监视器代码修改如下:


public class DrawThread extends Thread
{
    //模拟用户账户
    private Account account;
    //当前取钱线程所希望取的钱数
    private double drawAmount;

    public DrawThread(String name , Account account ,
        double drawAmount)
    {
        super(name);
        this.account = account;
        this.drawAmount = drawAmount;
    }

    //当多条线程修改同一个共享数据时,将涉及到数据安全问题。
    public void run()
    {
        //使用account作为同步监视器,任何线程进入下面同步代码块之前,
        //必须先获得对account账户的锁定——其他线程无法获得锁,也就无法修改它
        //这种做法符合:加锁-->修改完成-->释放锁 逻辑
        synchronized (account)
        {
            //账户余额大于取钱数目
            if (account.getBalance() >= drawAmount)
            {
                //吐出钞票
                System.out.println(getName() +
                    "取钱成功!吐出钞票:" + drawAmount);
                try
                {
                    Thread.sleep(1);           
                }
                catch (InterruptedException ex)
                {
                    ex.printStackTrace();
                }
                //修改余额
                account.setBalance(account.getBalance() - drawAmount);
                System.out.println("\t余额为: " + account.getBalance());
            }
            else
            {
                System.out.println(getName() + "取钱失败!余额不足!");
            }
        }
    }
}
上面的程序使用synchronized实现了同步,两个线程必须使用同步锁所以保证了共享资源的安全性
除此之外还可以使用同步方法,使用同步方法的锁是this对象,静态同步方法使用的锁是该方法对应类的Class对象这里不再演示程序。
java1.5以后加入了锁对象更加方便了控制多线程同步问题通常使用Lock对象的代码如下:
class x{
    //定义锁
    private final RenntrantLock lock=new RenntrantLock();
     //需要同步的方法
    public void m(){
           look.lock(); //加锁
          try{
                   //需要保证线程安全的代码
              }
             finally{
                       lock.unlockU()//保证最后能释放锁
                    }
    }
}
五 死锁
   但多个线程互相等待对方释放资源时候就会发生死锁如下代码:



class A
{
    public synchronized void foo( B b )
    {
        System.out.println("当前线程名: " +
            Thread.currentThread().getName() + " 进入了A实例的foo方法" );
        try
        {
            Thread.sleep(200);
        }
        catch (InterruptedException ex)
        {
            ex.printStackTrace();
        }
        System.out.println("当前线程名: " +
        Thread.currentThread().getName() + " 企图调用B实例的last方法");
        b.last();
    }

    public synchronized void last()
    {
        System.out.println("进入了A类的last方法内部");
    }
}

class B
{
    public synchronized void bar( A a )
    {
        System.out.println("当前线程名: "
            + Thread.currentThread().getName() + " 进入了B实例的bar方法" );
        try
        {
            Thread.sleep(200);
        }
        catch (InterruptedException ex)
        {
            ex.printStackTrace();
        }
        System.out.println("当前线程名: "
        + Thread.currentThread().getName() + " 企图调用A实例的last方法");
        a.last();
    }

    public synchronized void last()
    {
        System.out.println("进入了B类的last方法内部");
    }
}

public class DeadLock implements Runnable
{
    A a = new A();
    B b = new B();

    public void init()
    {
        Thread.currentThread().setName("主线程");
        //调用a对象的foo方法
        a.foo(b);
        System.out.println("进入了主线程之后");
    }
    public void run()
    {
        Thread.currentThread().setName("副线程");
        //调用b对象的bar方法
        b.bar(a);
        System.out.println("进入了副线程之后");
    }
    public static void main(String[] args)
    {
        DeadLock dl = new DeadLock();
        //以dl为target启动新线程
        new Thread(dl).start();
        //执行init方法作为新线程
        dl.init();
    }
}
Java虚拟机没有提供死锁的解决办法,所以我们写程序要避免死锁的发生。


六 线程的协同通信的问题
 线程的 通信其实就是多个线程多个线程操作同一资源但是操作的动作不同
如下代码:
class Res
{
    private String name;
    private String sex;
    private boolean flag = false;

    public synchronized void set(String name,String sex)
    {
        if(flag)
            try{this.wait();}catch(Exception e){}
        this.name = name;
       
        this.sex = sex;
        flag = true;
        this.notify();
    }
    public synchronized void out()
    {
        if(!flag)
            try{this.wait();}catch(Exception e){}
        System.out.println(name+"........"+sex);
        flag = false;
        this.notify();
    }
}

class Input implements Runnable
{
    private Res r ;
    Input(Res r)
    {
        this.r = r;
    }
    public void run()
    {
        int x = 0;
        while(true)
        {
            if(x==0)               
                r.set("小明","男");               
            else   
                r.set("小燕","女");               
            x = (x+1)%2;
        }
    }
}

class Output implements Runnable
{
    private Res r ;
   
    Output(Res r)
    {
        this.r = r;
    }
    public void run()
    {
        while(true)
        {
            r.out();
        }
    }
}


class  InputOutputDemo2
{
    public static void main(String[] args)
    {
        Res r = new Res();

        new Thread(new Input(r)).start();
        new Thread(new Output(r)).start();
       
    }
}
这里解释下几个方法
wait():导致当前线程等待,直到其他线程调用该同步监视器的notify方法或者notifyAll方法来唤醒该线程。
notify()唤醒此同步监视器上的等待的单个线程,如果有多个线程在同步器上等待就唤醒其中一个。
notifyAll():唤醒在次同步监视器上等待的所有线程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值