黑马程序员技术交流社区
标题: 牧紫小岩_多线程 [打印本页]
作者: mvplee 时间: 2013-6-19 10:45
标题: 牧紫小岩_多线程
面试都过了,就是技术分不够,郁闷了,现在分享我的学习笔记,以及一些电子书,赚点技术分。希望版主高抬贵手。
多线程概述:单线程如同一个餐厅只雇用一个服务员,服务员必须做完一件事情后才能做下一件事情。多线程如同一个餐厅雇佣很多名服务员,他们可以同时做很多事情,并且相互之间不会干扰。
一个程序被加载进内存运行时,就是一个进程,多线程使同一个进程可以并发处理多个任务,线程是进程的执行单元。一个进程可以拥有多个进程,一个线程必须有一个父进程。
每个线程拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不拥有系统资源,多个线程共享父进程所拥有的全部资源。线程的执行时抢占式的,当前的线程在任何时候都可能被挂起,以便CUP运行另外的线程。
一个程序运行后至少有一个进程,一个进程可以包含多个程序,但至少包含一个线程。
线程的创建和启动:方法1:创建线程需要继承Thread类,并重写run(),run()代表线程需要完成的任务,run()是线程的执行体,启动线程通过start()。
- public class FirstThread extends Thread{
- private int i;
- @Override
- public void run(){
- for (; i < 100; i++) {
- System.out.println(this.getName()+"\t"+i);
- }
- }
- public static void 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()方法内是线程的执行体。
- public class SecondThread implements Runnable {
- private int i;
- @Override
- public void run() {
- for (; i < 100; i++) {
- System.out.println(Thread.currentThread().getName() + "\t" + i);
- }
- }
- public static void 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()。
- ublic class 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;
- }
- public static void 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()加入的线程执行完为止。
- public class JoinThread extends Thread{
- public JoinThread(String name) {
- super(name);
- }
- @Override
- public void run(){
- for(int i=0;i<100;i++){
- System.out.println(this.getName()+"\t"+i);
- }
- }
- public static void 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()前设置。
- public class DaemonThread extends Thread {
- @Override
- public void run() {
- for (int i = 0; i < 1000; i++) {
- System.out.println(this.getName() + "\t" + i);
- }
- }
- public static void 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():线程休眠,并进入阻塞状态。
- public class SleepThread {
- public static void 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的执行资格。
- public class YieldTest extends Thread {
- private String name;
- public YieldTest(String name) {
- super(name);
- }
- @Override
- public void run() {
- for (int i = 0; i < 50; i++) {
- System.out.println(this.getName() + "\t" + i);
- if (i == 20)
- Thread.yield();
- }
- }
- public static void 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();
- }
- }
复制代码
作者: mvplee 时间: 2013-6-19 10:48
改变线程优先级: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()没有异常。
线程同步:当使用多个线程访问同一个数据时就有可能出现线程安全问题。例如,银行取钱:定义账户类:
- public class Account {
- private Account account;//模拟账户
- private double drawAmount;//取出的钱
- public Account() {
- super();
- }
- public Account(String accountNo, double balance) {
- super();
- this.accountNo = accountNo;
- this.balance = balance;
- }
- @Override
- public int hashCode() {
- return accountNo.hashCode();
- }
- @Override
- public boolean equals(Object obj) {
- if (obj == null || obj.getClass() != Account.class)
- return false;
- if (this == obj)
- return true;
- Account a = (Account) obj;
- if (this.accountNo.equals(a.accountNo) && this.balance == a.balance)
- return true;
- return true;
- }
- public String getAccountNo() {
- return accountNo;
- }
- public void setAccountNo(String accountNo) {
- this.accountNo = accountNo;
- }
- public double getBalance() {
- return balance;
- }
- public void setBalance(double balance) {
- this.balance = balance;
- }
- }
复制代码创建取钱的线程类:实现简单的取钱逻辑,判断账户余额
- 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;
- }
- @Override
- public void 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() + "__取钱失败,余额不足!");
- }
- }
- }
复制代码测试:
- public class DrawTest {
- public static void 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
- public void 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类中添加一个取钱的方法:
- public synchronized void 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()
- + "__取钱失败,余额不足!");
- }
- }
复制代码synchronized关键字修饰方法时,方法就变成同步方法了,同步方法使用的对象监视器是this,调用该方法的对象。sleep()、yield()、suspend()不会释放监视器的锁。
Java1.5可以通过显示定义同步锁的方式来为共享资源加锁,使用Lock对象,每次只能有一个线程对Lock对象加锁。代码格式如下:
- public class X {
- private final Lock lock = new ReentrantLock();
- public void m() {
- lock.lock();
- try {
- } catch (Exception e) {
-
- } finally {
- lock.unlock();
- }
- }
- }
复制代码使用finally块保证锁一定会被释放。修改Account类,使用Lock对象为draw()加锁:
- public class Account {
- private String accountNo;
- private double balance;
- private Lock lock = new ReentrantLock();
- public Account() {
- super();
- }
- public Account(String accountNo, double balance) {
- super();
- this.accountNo = accountNo;
- this.balance = balance;
- }
- public void 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
- public int hashCode() {
- return accountNo.hashCode();
- }
- @Override
- public boolean equals(Object obj) {
- if (obj == null || obj.getClass() != Account.class)
- return false;
- if (this == obj)
- return true;
- Account a = (Account) obj;
- if (this.accountNo.equals(a.accountNo) && this.balance == a.balance)
- return true;
- return true;
- }
- public String getAccountNo() {
- return accountNo;
- }
- public void setAccountNo(String accountNo) {
- this.accountNo = accountNo;
- }
- public double getBalance() {
- return balance;
- }
- public void setBalance(double balance) {
- this.balance = balance;
- }
- }
复制代码
作者: mvplee 时间: 2013-6-19 10:50
一个线程可以对已经被加锁的ReentrantLock锁再次加锁,ReentrantLock对象维持一个计数器来追踪lock()方法的嵌套调用。
当两个线程相互等待对象释放同步监视器时就会发生死锁。
死锁实例1:
- public class A {
- public synchronized void 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();
- }
- public void mehod() {
- System.out.println("method() in class A");
- }
- }
- public class B {
- public synchronized void 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();
- }
- public void method() {
- System.out.println("method() in class B");
- }
- }
- public class DeadLock implements Runnable {
- A a = new A();
- B b = new B();
- public void init() {
- Thread.currentThread().setName("主线程");
- a.A2B(b);
- System.out.println("进入主线程之后");
- }
- @Override
- public void run() {
- Thread.currentThread().setName("副线程");
- b.B2A(a);
- System.out.println("进入副线程之后");
- }
- public static void main(String[] args) {
- DeadLock dead = new DeadLock();
- new Thread(dead).start();
- dead.init();
- }
- }
复制代码死多实例2:
- class Test implements Runnable {
- private boolean flag;
- public Test(boolean flag) {
- this.flag = flag;
- }
- @Override
- public void 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 {
- public static Object locka = new Object();
- public static Object lockb = new Object();
- }
- /**
- * 多线程的死锁
- * @author RockLee
- *
- */
- public class DeadLockDemo {
- public static void 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账户类:
- public class Account {
- private String accountNo;// 账户编号
- private double balace;// 帐户余额
- private boolean flag = false;// 定义存/取钱标记位
- public Account(String accountNo, double balace) {
- super();
- this.accountNo = accountNo;
- this.balace = balace;
- }
- /**
- * 定义存钱方法,flag=true时,表示已经存过钱了,账户里有钱,线程等待
- *
- * @param drawAmount
- */
- public synchronized void 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
- */
- public synchronized void 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() {
- return accountNo;
- }
- public void setAccountNo(String accountNo) {
- this.accountNo = accountNo;
- }
- public double getBalace() {
- return balace;
- }
- }
复制代码定义一个线程模拟取钱100次:
- 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;
- }
- @Override
- public void run() {
- // 模拟取钱100次
- for (int i = 0; i < 100; i++) {
- account.draw(drawAmount);
- }
- }
- }
复制代码定义一个线程模拟存钱100次:
- public class DepositThread extends Thread {
- private Account account;
- private double depositAmount;
- public DepositThread(String name, Account account, double depositAmount) {
- super(name);
- this.account = account;
- this.depositAmount = depositAmount;
- }
- @Override
- public void run() {
- // 模拟存钱100次
- for (int i = 0; i < 100; i++) {
- account.deposit(depositAmount);
- }
- }
- }
复制代码测试:
- public class DrwaTest {
- public static void 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();
- public class ThreadPoolTest {
- public static void main(String[] args) {
- ExecutorService pool = Executors.newFixedThreadPool(6);// 创建线程池对象,池中有6个线程
- // 创建并通过线程池启动一个线程
- pool.submit(new Runnable() {
- @Override
- public void run() {
- for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()
- + "--->" + i);
- }
- }
- });
- // 创建并通过线程池启动一个线程
- pool.submit(new Runnable() {
- @Override
- public void run() {
- for (int i = 0; i < 100; i++) { System.out.println(Thread.currentThread().getName()
- + "--->" + i);
- }
- }
- });
- pool.shutdown();// 关闭线程池
- }
- }
复制代码版主,小手一抖,
俺的技术分到手。
作者: 神之梦 时间: 2013-6-20 21:46
哥们,不错{:soso_e179:}
欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) |
黑马程序员IT技术论坛 X3.2 |