zoukankan      html  css  js  c++  java
  • 多线程

    1、线程的概念
    多线程,就类似与操作系统中的多进程。简单的讲,就是可 以同时并发执行多个任务,处理多件事情。这与我们经常所 谓的边唱边跳,边说边做事一个道理。
    线程是一个轻量级的进程,一个进程中可以分为多个线程。 比起进程,线程所耗费的系统资源更少,切换更加容易 

    /*
     * 进程是操作系统中的一个任务,一个程序启动运行,就会创建
     * 一个(或多个)进程。
     * 线程是轻量级的进程。进程会有自己独立的内存空间与资源。一个进程
     * 下会存在一个(或多个)线程。线程为进程的执行单元。线程本身不含有
     * 独立的资源,而是共享进程中的资源。
     * 
     * 多线程的优势:
     * 1 使用多线程可以提高CPU的利用率。
     * 2 使用多线程可以提供更好的动态性与交互性。
     */
    package day18;
    
    public class HelloWorld {
    
    	public static void main(String[] args) {
    		// 无限循环,用来查看java程序的进程。
    		// Java程序在运行时,就会存在一个进程。
    		// 一个进程至少要包含一个线程,Java线程就是
    		// 执行main方法的main线程(线程的名字叫做main)。
    		while (true)
    			;
    	}
    }
    

      2、Thread
    ThreadJava中的线程类,我们可以通过继承Thread类来实 现多线程操作。
    继承Thread类,重写run方法, run方法中的代码即为我们需 要线程执行的任务,然后调用对象的start方法即可启动线程。
    说明:
    不是调用run方法,而是调用start方法,否则无法实现多线 程。
    执行main方法就会创建一个线程。

    /*
     * Thread类就表示线程。可以用来创建线程。
     * 通过继承Thread类实现多线程。
     */
    package day18;
    
    public class ThreadTest {
    	public static void main(String[] args) {
    		// 创建线程
    		Mission1 m = new Mission1();
    		Mission2 m2 = new Mission2();
    		// 创建线程后,需要启动线程,线程才能够执行。
    		// 调用start方法来启动一个线程,是线程处于就绪
    		// 状态。线程处于就绪状态,不代表该线程会马上得到执行,
    		// 具体何时执行,要取决于操作系统的调度。线程处于就绪
    		// 状态,表示该线程有机会获得CPU的时间片,即有机会
    		// 得到执行。
    		m.start();
    		m2.start();
    	}
    }
    
    // run方法就是线程要执行的任务。
    // 我们重写Thread类的run方法。
    // 线程从run方法开始执行。
    class Mission1 extends Thread {
    	@Override
    	public void run() {
    		for (int i = 1; i <= 10; i++) {
    			System.out.println(i);
    			// 令当前线程休眠参数指定的时间(毫秒)
    			// 处于休眠的线程会放弃掉当前的时间片,
    			// 并且在整个休眠期间,不会获得CPU的时间片。
    			// 当线程苏醒时,线程会处于就绪状态。(不是运行状态)。
    			try {
    				Thread.sleep(200);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			// 获取线程的名字。
    			System.out.println(getName());
    		}
    	}
    }
    
    class Mission2 extends Thread {
    	@Override
    	public void run() {
    		for (int i = 11; i <= 20; i++) {
    			System.out.println(i);
    			try {
    				Thread.sleep(200);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    

      3、Runnable接口
    除了继承Thread类以外,还可以通过实现Runnable接口来实现 多线程。
    首先,我们要创建一个类,该类实现Runnable接口, Runnable接口中存在一个抽象方法run,因此,我们需要实 现(重写) run方法。
    然后创建该类的对象,将对象作为Thread的运行目标。 ( target)。 

    /*
     * 通过实现Runnable接口实现多线程
     */
    package day18;
    
    public class RunnableTest {
    	public static void main(String[] args) {
    		Mission m = new Mission();
    		// 使用实现Runnable接口的对象,作为
    		// 线程的目标执行体。
    		Thread t1 = new Thread(m);
    		Thread t2 = new Thread(m);
    		t1.start();
    		t2.start();
    	}
    }
    
    class Mission implements Runnable {
    	@Override
    	public void run() {
    		for (int i = 1; i <= 10; i++) {
    			System.out.println(i);
    			try {
    				Thread.sleep(200);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		// 无法直接调用getName(),因为当前类没有继承Thread,
    		// 不是Thread类的子类。
    		// getName();
    		// 获取当前的执行的线程。
    		Thread current = Thread.currentThread();
    		current.getName();
    	}
    }
    

      4、线程的生命周期
    线程的生命周期可以分为以下环节:
    新建-创建对象
    就绪-调用start
    运行-获得CPU资源
    阻塞(挂起) -失去CPU资源
    死亡-线程执行结束或抛出未捕获的异常

    5、常用方法

    public static Thread currentThread(); 获得当前执行的线程。
     public String getName() 获得线程名称。
     public final native boolean isAlive(); 判断线程是否存活
    (在start方法调用后,并且线程没有死亡)。
     public void join() 等待直到这个线程死亡。
     public static void sleep(long millis) 使当前线程睡眠(暂时
    停止执行) millis毫秒。如果当前程序存在其他等待的线程,
    则其他线程会获得执行机会。
    public static void yield();当前运行的线程有意让出CPU资源,
    由线程调度器重新选择线程调度。不过,这仅仅是一个提示
    而已,线程调度器可能会忽略。
    public void setDaemon(boolean on);设置线程是否为后台
    线程。
    public void setPriority(int newPriority)设置线程的优先级。
    优先级为1-10。优先级高的线程仅意味着可能获得更多执行
    的机会,不表示一定会一直执行,优先级低的线程仍然有机
    会执行。

    6、yield方法

    package day18;
    
    public class YieldTest {
    	public static void main(String[] args) {
    		Thread2 t = new Thread2();
    		Thread3 t2 = new Thread3();
    		t.start();
    		t2.start();
    	}
    }
    
    class Thread2 extends Thread {
    	@Override
    	public void run() {
    		for (int i = 0; i <= 5; i++) {
    			System.out.println(i);
    		}
    		// 当前线程有意让出CPU资源,让其他线程得到执行。
    		// 但是,操作系统可能会忽略线程的这种请求。
    		Thread.yield();
    		for (int i = 6; i <= 9; i++) {
    			System.out.println(i);
    		}
    	}
    }
    
    class Thread3 extends Thread {
    	@Override
    	public void run() {
    		for (int i = 100; i < 110; i++) {
    			System.out.println(i);
    		}
    	}
    }
    

    7、

    package day18;
    
    //执行main方法的线程名就叫main。
    public class ThreadMethod {
    
    	public static void main(String[] args) {
    		// 获取当前执行的线程。
    		Thread current = Thread.currentThread();
    		// 获取线程的名字。
    		// System.out.println(current.getName());
    		// 判断当前线程是否处于存活状态(调用start方法之后,
    		// 线程死亡之前),存活返回true,否则返回false。
    		// System.out.println(current.isAlive());
    
    		ThreadA ta = new ThreadA();
    		ta.start();
    		try {
    			// 如果在A线程中调用B线程对象的join方法(无参),则A线程
    			// 会一直等待B线程执行结束,A线程才会继续执行。
    			ta.join();
    			// 当前线程最多等待ta线程参数指定的时间。(毫秒)
    			// ta.join(500);
    			// 当前线程最多等待第一个参数的时间(毫秒)加上
    			// 第二个参数的时间(纳秒)。(时间的和)
    			// ta.join(500, 100);
    			// 令当前线程休眠参数指定的时间。
    			// Thread.sleep(500);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.println("main方法输出内容");
    		ThreadA ta2 = new ThreadA();
    		// 设置当前线程是否为后台线程,true为后台线程,否则
    		// 为前台线程。该方法需要在线程启动之前调用。
    		ta2.setDaemon(true);
    		// 设置线程的优先级。优先级高的线程不代表会一直执行,
    		// 只是执行几率大于优先级低的线程。虽然Java提供了
    		// 1-10这十个优先级,但是,操作系统未必会存在十个优先级
    		// 有Java相对应。
    		ta2.setPriority(Thread.NORM_PRIORITY);
    
    	}
    }
    
    class ThreadA extends Thread {
    	@Override
    	public void run() {
    		for (int i = 0; i < 10; i++) {
    			System.out.println(i);
    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    

    8、中断线程

    package day18;
    
    public class InterruptTest {
    	public static void main(String[] args) {
    		Thread4 t = new Thread4();
    		t.start();
    		try {
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		// 中断线程。
    		t.interrupt();
    	}
    }
    
    class Thread4 extends Thread {
    	@Override
    	public void run() {
    		try {
    			// 如果线程处于sleep,join等等待方法中,如果
    			// 在等待期间,线程被其他线程所中断,则会产生
    			// InterruptedException异常。如果没有处于
    			// 以上等待方法中,则线程被其他线程所终端,不会
    			// 产生InterruptedException异常。
    			Thread.sleep(10000);
    		} catch (InterruptedException e) {
    			System.out.println("产生了InterruptedException异常");
    		}
    	}
    }
    

    9、线程同步
    当多线程并发运行时,多线程间很可能操作共享成员变量,此 时,就需要对共享成员变量的操作进行同步,避免出现多线程 的并发修改而引起的意外错误。 

    当多线程并发修改公共变量时,我们就需要对公共变量的修改 部分进行同步,避免出现并发修改错误。 

    线程同步可以使用:
    同步块
    同步方法

    同步的代码在同一时刻,至多只会有一个线程执行。其使用线 程锁机制来保证。 

    /*
     * 当多线程对共享变量并发进行修改时,就可能会出现
     * 并发修改的不一致性。
     * 对共享变量进行并发修改的区域,我们称之为共享区域。
     * 对于共享区域,我们必须要实现上锁(线程的互斥访问)。
     * 
     * 对于共享变量,要完全放在同步区域当中,否则就可能会出现
     * 问题。(存在修改共享变量)
     */
    package day18;
    
    public class BuyTicket {
    	public static void main(String[] args) {
    		Ticket ticket = new Ticket();
    		Thread t1 = new Thread(ticket);
    		Thread t2 = new Thread(ticket);
    		Thread t3 = new Thread(ticket);
    		// 设置线程的名字
    		t1.setName("张三");
    		t2.setName("李四");
    		t3.setName("王五");
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    
    class Ticket implements Runnable {
    	private int num = 100;
    	// 任意对象都可以充当共享区域的锁。
    	private Object lock = new Object();
    	// 因为任意对象都可以充当锁的角色,因此,我们没有必要
    	// 单独创建一个对象锁,使用当前对象this来充当锁就
    	// 可以了。
    
    	@Override
    	public void run() {
    		// while (num > 0) {
    		// synchronized (lock) {
    		// String name = Thread.currentThread().getName();
    		// System.out.println(name + "抢到第" + num + "张票");
    		// num--;
    		// }
    		// }
    		// 虽然可以这样实现,但是这样做,完全退化成单线程。
    		// synchronized (lock) {
    		// while (num > 0) {
    		// String name = Thread.currentThread().getName();
    		// System.out.println(name + "抢到第" + num + "张票");
    		// num--;
    		// }
    		// }
    		while (true) {
    			synchronized (this) {
    				if (num > 0) {
    					String name = Thread.currentThread().getName();
    					System.out.println(name + "抢到第" + num + "张票");
    					num--;
    				} else {
    					break;
    				}
    			}
    			try {
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    
    	/*
    	 * 声明同步方法,使用synchronized修饰。同步方法的整个 方法体都是共享区域,都是互斥的进行访问的。
    	 * 对于实例方法,使用当前对象this充当锁。 对于静态方法,使用当前类型的Class对象充当锁。
    	 */
    	public synchronized void f() {
    
    	}
    
    	public synchronized void g() {
    
    	}
    
    	public void k() {
    		synchronized (this) {
    
    		}
    	}
    
    	public void n() {
    		synchronized (lock) {
    
    		}
    	}
    }
    

      

    package day18;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class BuyTicket2 {
    
    	public static void main(String[] args) {
    
    	}
    }
    /*
     * Lock与synchronized
     * Lock是JDK1.5新增的内容,更加贴近与面向对象化。
     * synchronized如果使用break,或者产生了一个未捕获的异常,
     * 同步区域的锁可以自动得到释放,但是Lock不能。为了确保Lock
     * 能够一定被解锁,将unlock方法写在finally语句块中。
     */
    
    class Ticket2 implements Runnable {
    	private int num = 100;
    	private Lock lock = new ReentrantLock();
    
    	@Override
    	public void run() {
    		while (true) {
    			try {
    				// 加锁
    				lock.lock();
    				if (num > 0) {
    					// 输出一大堆
    					num--;
    				} else {
    					break;
    				}
    			} finally {
    				// 解锁,在finally中调用,确保锁能够
    				// 被解开。
    				lock.unlock();
    			}
    		}
    	}
    }
    

    10、死锁
    当两个或多个线程同时拥有自己的资源,而相互等待获得对 方资源,导致程序永远陷入僵持状态,这就是死锁。
    当多线程并发访问共享数据时,使用同步操作可以避免多线 程并发修改带来的危害,但同时有可能会产生死锁。 

    /*
     * 死锁
     * 死锁就是两个线程分别拥有自己的资源,而想要获得对方的资源,
     * 从而处于无限的僵持与等待之中。
     */
    package day18;
    
    public class DeadLock {
    	public static void main(String[] args) {
    		Fighter f1 = new Fighter("张三");
    		Fighter f2 = new Fighter("李四");
    		Thread t1 = new Thread(() -> {
    			f1.hold(f2);
    		});
    		Thread t2 = new Thread(() -> {
    			f2.hold(f1);
    		});
    		t1.start();
    		t2.start();
    	}
    }
    
    class Fighter {
    	private String name;
    
    	public Fighter(String name) {
    		this.name = name;
    	}
    
    	public synchronized void hold(Fighter f) {
    		System.out.println(name + "抓住了" + f.name);
    		System.out.println(name + "等待" + f.name + "的放手");
    		// 等待着对方先放开自己
    		f.loose(this);
    		// 然后自己再放开对方
    		this.loose(f);
    	}
    
    	public synchronized void loose(Fighter f) {
    		System.out.println(name + "放开了" + f.name);
    	}
    }
    

    11、等待与唤醒
    在多线程通信时,在某些特定条件下,我们需要线程做出一 定的“让步”,否则就很容易造成双方(或多方)进行僵持 状态,进而形成死锁。 

    sleep方法虽然能使当前线程阻塞,但是sleep方法不会释放其  所占有的任何“锁”。而且,也不能保证线程苏醒后,条件就 一定会得到满足。 

    我们可以使用以下方法实现线程的阻塞,并且令线程暂时释放 “锁”资源。以下方法都是在Object类中声明的(这意味着什
    么?)。

    wait 令当前线程等待,直到另一个线程调用为该对象调用 notifynotifyAll方法。当前线程必要拥有该对象的锁。当调 用wait方法后,线程会释放掉其占有的锁,并处于等待队列中。 

    notify 唤醒等待该对象锁的一个线程,如果有多个线程处于 等待中,仅唤醒一个。具体哪一个,取决于底层的实现(这
    又意味着什么?)。

    notifyAll唤醒等待该对象锁的所有线程。
    说明: waitnotifynotifyAll方法调用时,当前线程一定要拥 有对象的锁。否则将会引发IllegalMonitorStateException异常。 

    package day18;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class Product {
    
    	public static void main(String[] args) {
    		Worker w = new Worker();
    		Thread t1 = new Thread(() -> {
    			for (int i = 0; i < 100; i++) {
    				w.product();
    			}
    		});
    		Thread t2 = new Thread(() -> {
    			for (int i = 0; i < 100; i++) {
    				w.consume();
    			}
    		});
    		t1.start();
    		t2.start();
    	}
    }
    
    class Worker {
    	private List<String> list = new ArrayList<>();
    
    	public synchronized void product() {
    		if (list.size() == 3) {
    			System.out.println("仓库已满,生产阻塞。");
    			try {
    				// 释放掉当前占用的锁资源,使自身处于阻塞队里当中。
    				// 调用该方法的线程必须要具有锁资源,否则就会产生异常
    				// (IllegalMonitorStateException)。
    				wait();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		} else {
    			list.add("");
    			// 通知(唤醒)之前处于阻塞队列的线程。
    			notifyAll();
    		}
    	}
    
    	public synchronized void consume() {
    		if (list.size() == 0) {
    			System.out.println("仓库已空,消费阻塞。");
    			try {
    				wait();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		} else {
    			list.remove(0);
    			notifyAll();
    		}
    	}
    }
    

      

    package day18;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Product2 {
    
    	public static void main(String[] args) {
    		Worker2 w = new Worker2();
    		Thread t1 = new Thread(() -> {
    			for (int i = 0; i < 100; i++) {
    				w.product();
    			}
    		});
    		Thread t2 = new Thread(() -> {
    			for (int i = 0; i < 100; i++) {
    				w.consume();
    			}
    		});
    		t1.start();
    		t2.start();
    	}
    }
    
    /*
     * Condition与Lock联合使用。
     */
    class Worker2 {
    	private List<String> list = new ArrayList<>();
    	private Lock lock = new ReentrantLock();
    	private Condition condition = lock.newCondition();
    
    	public void product() {
    		try {
    			lock.lock();
    			if (list.size() == 3) {
    				System.out.println("仓库已满,生产阻塞。");
    				try {
    					condition.await();
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    			} else {
    				list.add("");
    				// condition.signal();
    				condition.signalAll();
    			}
    		} finally {
    			lock.unlock();
    		}
    	}
    
    	public void consume() {
    		try {
    			lock.lock();
    			if (list.size() == 0) {
    				System.out.println("仓库已空,消费阻塞。");
    				try {
    					condition.await();
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    			} else {
    				list.remove(0);
    				condition.signalAll();
    			}
    		} finally {
    			lock.unlock();
    		}
    	}
    }
    

      

     

  • 相关阅读:
    Ubuntu下建立Android开发环境
    c#值类型和引用类型
    Jude Begin
    Eclipse C/C++ development environment creation
    C# var usage from MSDN
    SubSonic应用_Collection
    C#2.0中委托与匿名委托引
    sql语句的执行步骤——zhuan
    图˙谱˙马尔科夫过程·聚类结构 (转载,原始出处不详)
    Hadoop集群新增节点实现方案
  • 原文地址:https://www.cnblogs.com/liuwei6/p/6582828.html
Copyright © 2011-2022 走看看