多线程概述:
单线程如同一个餐厅只雇用一个服务员,服务员必须做完一件事情后才能做下一件事情。多线程如同一个餐厅雇佣很多名服务员,他们可以同时做很多事情,并且相互之间不会干扰。
一个程序被加载进内存运行时,就是一个进程,多线程使同一个进程可以并发处理多个任务,线程是进程的执行单元。一个进程可以拥有多个进程,一个线程必须有一个父进程。
每个线程拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不拥有系统资源,多个线程共享父进程所拥有的全部资源。线程的执行时抢占式的,当前的线程在任何时候都可能被挂起,以便CUP运行另外的线程。
一个程序运行后至少有一个进程,一个进程可以包含多个程序,但至少包含一个线程。
线程的创建和启动:
方法1:创建线程需要继承Thread类,并重写run(),run()代表线程需要完成的任务,run()是线程的执行体,启动线程通过start()。
publicclass FirstThread extends Thread{ privateinti; @Override publicvoid run(){ for (; i < 100; i++) { System.out.println(this.getName()+"\t"+i); } } publicstaticvoid main(String[] args) { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+" "+i); if(i==20){ new FirstThread().start(); new FirstThread().start(); } } } } |
在main()中当循环变量i=20时,启动两个线程。属性i在两个线程中的打印结果是不连续的,证明了多线程之间运行互不干扰,因为new了两个线程对象。以上程序启动时有3个线程,main()也是一个线程,成为主线程。
方法2:实现Runnable接口并实现run(),fun()方法内是线程的执行体。
publicclass SecondThread implements Runnable { privateinti; @Override publicvoid run() { for (; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } } publicstaticvoid main(String[] args) { SecondThread st = new SecondThread(); for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()+" "+i); if(i==20){ new Thread(st,"线程-1").start(); new Thread(st,"线程-2").start(); } } } } |
通过实现Runnable接口在启动多线程时也需要使用Thread类并调用start()。这种方式创建的多个线程可以共享线程类中的属性,因为只创建一个Runnable接口的子类对象,这一个对象被Thread类引用。
方法3:通过实现Callable接口,并且实现call()。
ublicclass ThirdThread implements Callable<Integer> { @Override public Integer call() throws Exception { int i=0; for (; i <100; i++) { System.out.println(Thread.currentThread().getName()+"\t"+i); } return i; } publicstaticvoid main(String[] args) throws Exception { ThirdThread tt = new ThirdThread(); FutureTask<Integer> task = new FutureTask<Integer>(tt); for(int i=0;i<100;i++){ System.out.println(Thread.currentThread().getName()); if(i==20){ new Thread(task,"有返回值的线程").start(); } } System.out.println(task.get()); } } |
call()可以作为线程的执行体,call()比run()强大,可以有返回值,可以抛异常,Java5新增的接口。
线程的生命周期:
新建(New)à就绪(Runable)à运行(Running)à阻塞(Blocked)à死亡(Dead)
线程控制:
join():调用线程将被阻塞,直到被join()加入的线程执行完为止。
publicclass JoinThread extends Thread{ public JoinThread(String name) { super(name); } @Override publicvoid run(){ for(int i=0;i<100;i++){ System.out.println(this.getName()+"\t"+i); } } publicstaticvoid main(String[] args) throws Exception { new JoinThread("新线程").start(); for(int i=0;i<100;i++){ if(i==20){ JoinThread jt = new JoinThread("被Join的线程"); jt.start(); jt.join(); } System.out.println(Thread.currentThread().getName()+"\t"+i); } } } |
main线程调用了jt线程的join(),mian线程必须等jt线程执行结束后才会继续执行。
setDaemon():后台线程,如果所有前台线程都死亡,JVM会通知后台线程死亡,main线程默认是后台线程,设置一个线程为后台线程,必须在线程start()前设置。
publicclass DaemonThread extends Thread { @Override publicvoid run() { for (int i = 0; i < 1000; i++) { System.out.println(this.getName() + "\t" + i); } } publicstaticvoid main(String[] args) { DaemonThread dt = new DaemonThread(); dt.setDaemon(true);// 设置线程为后台线程 dt.start(); for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + " " + i); } // 程序运行到此处,前台线程(main线程)结束,后台dt线程也随之结束 } } |
sleep():线程休眠,并进入阻塞状态。
publicclass SleepThread { publicstaticvoid main(String[] args) throws Exception { for (int i = 0; i < 10; i++) { System.out.println("Current time : "+new Date()); Thread.sleep(1000); } } } |
线程休眠后进入阻塞状态,并且不会获得CPU执行资格。
yield():让当前正在执行的线程暂停,但不会阻塞该线程,只是将线程转入就绪状态,暂停后只有优先级与当前线程相同,或者更高的处于就绪状态的线程财货获得CPU的执行资格。
publicclass YieldTest extends Thread { private String name; public YieldTest(String name) { super(name); } @Override publicvoid run() { for (int i = 0; i < 50; i++) { System.out.println(this.getName() + "\t" + i); if (i == 20) Thread.yield(); } } publicstaticvoid main(String[] args) { YieldTest y1 = new YieldTest("优先级高"); y1.setPriority(Thread.MAX_PRIORITY); YieldTest y2 = new YieldTest("优先级低"); y2.setPriority(Thread.MIN_PRIORITY); y1.start(); y2.start(); } } |
改变线程优先级:MAX_PRIOPERTY=10,MIN_PRIOPERTY=0,NORM_PRIOPERTY=5,由于修改优先级需要操作系统底层的支持,Java虽然提供了10个等级的优先级,但还是建议使用常量的方法配置优先级。
sleep()与yield()区别:
1、 sleep()暂停后,会给其它线程执行资格,不会考虑优先级,yield()只会给优先级相同或
更高的线程执行资格。
2、sleep()会暂停当前线程,并且进入阻塞状态,yield()不会,只是强制让线程进入就绪状态,一个线程可能调用yield()后,又立即获得了CPU的执行资格。
3、sleep()抛InterruptedException(),yield()没有异常。
线程同步:
当使用多个线程访问同一个数据时就有可能出现线程安全问题。例如,银行取钱:
定义账户类:
publicclass Account { private Account account;//模拟账户 privatedoubledrawAmount;//取出的钱 public Account() { super(); } public Account(String accountNo, double balance) { super(); this.accountNo = accountNo; this.balance = balance; } @Override publicint hashCode() { returnaccountNo.hashCode(); } @Override publicboolean equals(Object obj) { if (obj == null || obj.getClass() != Account.class) returnfalse; if (this == obj) returntrue; Account a = (Account) obj; if (this.accountNo.equals(a.accountNo) && this.balance == a.balance) returntrue; returntrue; } public String getAccountNo() { returnaccountNo; } publicvoid setAccountNo(String accountNo) { this.accountNo = accountNo; } publicdouble getBalance() { returnbalance; } publicvoid setBalance(double balance) { this.balance = balance; } } |
创建取钱的线程类:实现简单的取钱逻辑,判断账户余额
publicclass DrawThread extends Thread { private Account account; privatedoubledrawAmount; public DrawThread(String name, Account account, double drawAmount) { super(name); this.account = account; this.drawAmount = drawAmount; } @Override publicvoid run() { // 账户里有余额 if (account.getBalance() >= this.drawAmount) { System.out.println(this.getName() + "__取钱成功,吐出钞票:" + drawAmount); account.setBalance(account.getBalance() - drawAmount);// 修改账户余额 System.out.println("余额:" + account.getBalance()); } else { // 账户余额不足 System.out.println(this.getName() + "__取钱失败,余额不足!"); } } } |
测试:
publicclass DrawTest { publicstaticvoid main(String[] args) { Account acct = new Account(); acct.setBalance(1000); new DrawThread("甲",acct,800).start(); new DrawThread("乙",acct,800).start(); } } |
输出结果: 乙__取钱成功,吐出钞票:800.0 甲__取钱成功,吐出钞票:800.0 余额:200.0 余额:-600.0 |
账户里只有1000K,却取出了1600K,多线程操作同一资源时出现了线程安全。原因时run()不具备安全性,程序中两个线程并发的修改了Account对象,这就问题。
Java中为了解决线程安全问题,加入了同步监视器,任何时刻只能有一个线程可以获得同步监视器的锁,当同步代码执行完后,该线程将会释放对该同步监视器的锁给其它线程使用。
修改run()加入同步代码块:
@Override publicvoid run() { synchronized (account) { // 账户里有余额 if (account.getBalance() >= this.drawAmount) { System.out .println(this.getName() + "__取钱成功,吐出钞票:" + drawAmount); account.setBalance(account.getBalance() - drawAmount);// 修改账户余额 System.out.println("余额:" + account.getBalance()); } else { // 账户余额不足 System.out.println(this.getName() + "__取钱失败,余额不足!"); } } } |
甲__取钱成功,吐出钞票:800.0 余额:200.0 乙__取钱失败,余额不足! |
synchronized中需要传入一个需要同步的监视器对象,使用account作为同步的监视器对象,同步后,在操作Account对象时逻辑为:“加锁à操作à释放锁”。任何线程在操作共享资源时首先要加锁,加锁期间其它线程无法修改共享资源,修改完成后释放共享资源的锁。通过这种方法保证并发线程访问、修改共享资源时只能有一个线程对其操作。
使用同步方法:在Account类中添加一个取钱的方法:
publicsynchronizedvoid draw(double drawAmount) { // 账户里有余额 if (getBalance() >= drawAmount) { System.out.println(Thread.currentThread().getName() + "__取钱成功,吐出钞票:" + drawAmount); balance -= drawAmount;// 修改账户余额 System.out.println("余额:" + balance); } else { // 账户余额不足 System.out.println(Thread.currentThread().getName() + "__取钱失败,余额不足!"); } } |
修改run():
@Override publicvoid run() { account.draw(drawAmount); } |
synchronized关键字修饰方法时,方法就变成同步方法了,同步方法使用的对象监视器是this,调用该方法的对象。sleep()、yield()、suspend()不会释放监视器的锁。
Java1.5可以通过显示定义同步锁的方式来为共享资源加锁,使用Lock对象,每次只能有一个线程对Lock对象加锁。代码格式如下:
publicclass X { privatefinal Lock lock = new ReentrantLock(); publicvoid m() { lock.lock(); try {
} catch (Exception e) {
} finally { lock.unlock(); } } } |
使用finally块保证锁一定会被释放。修改Account类,使用Lock对象为draw()加锁:
publicclass Account { private String accountNo; privatedoublebalance; private Lock lock = new ReentrantLock(); public Account() { super(); } public Account(String accountNo, double balance) { super(); this.accountNo = accountNo; this.balance = balance; } publicvoid draw(double drawAmount) { lock.lock();// 显示加锁 try { // 账户里有余额 if (balance >= drawAmount) { System.out.println(Thread.currentThread().getName() + "__取钱成功,吐出钞票:" + drawAmount); balance -= drawAmount;// 修改账户余额 System.out.println("余额:" + balance); } else { // 账户余额不足 System.out.println(Thread.currentThread().getName() + "__取钱失败,余额不足!"); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock();// 显示释放锁 } } @Override publicint hashCode() { returnaccountNo.hashCode(); } @Override publicboolean equals(Object obj) { if (obj == null || obj.getClass() != Account.class) returnfalse; if (this == obj) returntrue; Account a = (Account) obj; if (this.accountNo.equals(a.accountNo) && this.balance == a.balance) returntrue; returntrue; } public String getAccountNo() { returnaccountNo; } publicvoid setAccountNo(String accountNo) { this.accountNo = accountNo; } publicdouble getBalance() { returnbalance; } publicvoid setBalance(double balance) { this.balance = balance; } } |
一个线程可以对已经被加锁的ReentrantLock锁再次加锁,ReentrantLock对象维持一个计数器来追踪lock()方法的嵌套调用。
当两个线程相互等待对象释放同步监视器时就会发生死锁。
死锁实例1:
publicclass A { publicsynchronizedvoid A2B(B b) { System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method() in class A"); try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } System.out.println("当前线程:" + Thread.currentThread().getName() + "准备进入method() in class B"); b.method(); } publicvoid mehod() { System.out.println("method() in class A"); } } |
publicclass B { publicsynchronizedvoid B2A(A a) { System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method() in class B"); try { Thread.sleep(2000); } catch (Exception e) { e.printStackTrace(); } System.out.println("当前线程:" + Thread.currentThread().getName() + "准备进入method() in class A"); a.mehod(); } publicvoid method() { System.out.println("method() in class B"); } } |
publicclass DeadLock implements Runnable { A a = new A(); B b = new B(); publicvoid init() { Thread.currentThread().setName("主线程"); a.A2B(b); System.out.println("进入主线程之后"); } @Override publicvoid run() { Thread.currentThread().setName("副线程"); b.B2A(a); System.out.println("进入副线程之后"); } publicstaticvoid main(String[] args) { DeadLock dead = new DeadLock(); new Thread(dead).start(); dead.init(); } } |
死多实例2:
class Test implements Runnable { privatebooleanflag; public Test(boolean flag) { this.flag = flag; } @Override publicvoid run() { if (flag) { synchronized (MyLock.locka) { System.out.println("if locka"); synchronized (MyLock.lockb) { System.out.println("if lockb"); } } } else { synchronized (MyLock.locka) { System.out.println("if lockb"); synchronized (MyLock.lockb) { System.out.println("if locka"); } } } } } class MyLock { publicstatic Object locka = new Object(); publicstatic Object lockb = new Object(); } /** * 多线程的死锁 * @author RockLee * */ publicclass DeadLockDemo { publicstaticvoid main(String[] args) { Test t1 = new Test(true); Test t2 = new Test(false); new Thread(t1).start(); new Thread(t2).start(); } } |
线程间通信:
Object类中有三个方法用于实现线程间通信,wait()、notify()、notifyAll(),这三个方法必须有同步锁的监视器对象调用,synchronized方法使用this调用,synchronized块使用同步监视器对象调用。
wait():当前调用线程等待,直到其它线程使用同步监视器对象调用notify()、notifyAll()后才能唤醒。
notify():唤醒当前同步锁上的单个线程。
notifyAll():唤醒等待当前同步锁上的所有线程。
使用两个线程模拟存钱者和取钱者,不断重复存/取操作,但要求必须存一次,取一次。
定义Account账户类:
publicclass Account { private String accountNo;// 账户编号 privatedoublebalace;// 帐户余额 privatebooleanflag = false;// 定义存/取钱标记位 public Account(String accountNo, double balace) { super(); this.accountNo = accountNo; this.balace = balace; } /** * 定义存钱方法,flag=true时,表示已经存过钱了,账户里有钱,线程等待 * * @param drawAmount */ publicsynchronizedvoid deposit(double depositAmount) { if (flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { System.out.println(Thread.currentThread().getName() + "存款:" + depositAmount); balace += depositAmount; System.out.println("账户余额:" + balace); flag = true;// 存入钱后改变标记位 this.notifyAll();// 唤醒等待线程 } } /** * 取钱方法,flag=false时,表示已经取过钱了,等待 * * @param drawAmount */ publicsynchronizedvoid draw(double drawAmount) { if (!flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { System.out.println(Thread.currentThread().getName() + "取钱:" + drawAmount); balace -= drawAmount; System.out.println("账户余额:" + balace); flag = false; this.notifyAll(); } } public String getAccountNo() { returnaccountNo; } publicvoid setAccountNo(String accountNo) { this.accountNo = accountNo; } publicdouble getBalace() { returnbalace; } } |
定义一个线程模拟取钱100次:
publicclass DrawThread extends Thread { private Account account; privatedoubledrawAmount; public DrawThread(String name,Account account, double drawAmount) { super(name); this.account = account; this.drawAmount = drawAmount; } @Override publicvoid run() { // 模拟取钱100次 for (int i = 0; i < 100; i++) { account.draw(drawAmount); } } } |
定义一个线程模拟存钱100次:
publicclass DepositThread extends Thread { private Account account; privatedoubledepositAmount; public DepositThread(String name, Account account, double depositAmount) { super(name); this.account = account; this.depositAmount = depositAmount; } @Override publicvoid run() { // 模拟存钱100次 for (int i = 0; i < 100; i++) { account.deposit(depositAmount); } } } |
测试:
publicclass DrwaTest { publicstaticvoid main(String[] args) { Account account = new Account("110", 0); new DrawThread("取钱", account, 800).start(); new DepositThread("存钱甲", account, 800).start(); } } |
存钱甲存款:800.0 账户余额:800.0 取钱取钱:800.0 账户余额:0.0 |
线程池:
系统启动一个线程的成本比较高,尤其需要创建大量生存周期短暂的线程时,就不得不考虑性能,使用线程池可以很好的提高性能。线程池在系统创建启动时即创建了大量空闲线程,程序将线程运行的对象传给线程池,线程池会启动一个线程来执行run(),当run()结束后线程池中的线程不会死亡,将再次返回线程池成为空闲状态,等待执行下一个线程运行的对象。
创建线程池:
1、 Excutors工厂类创建ExecutorService对象,ExecutorService代表线程池类对象;
2、 创建线程类对象
3、 ExecutorService调用submit()执行线程类run();
publicclass ThreadPoolTest { publicstaticvoid main(String[] args) { ExecutorService pool = Executors.newFixedThreadPool(6);// 创建线程池对象,池中有6个线程 // 创建并通过线程池启动一个线程 pool.submit(new Runnable() { @Override publicvoid run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "--->" + i); } } }); // 创建并通过线程池启动一个线程 pool.submit(new Runnable() { @Override publicvoid run() { for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName() + "--->" + i); } } }); pool.shutdown();// 关闭线程池 } } |