Java基础之多线程

来源:互联网 发布:php class 编辑:程序博客网 时间:2024/05/29 04:47

一、概念

进程:是一个正在执行中的程序,每一个进程都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元
线程:就是进程中一个独立的控制单元,线程控制这进程的执行,一个进程中至少有一个线程
         如:javaVM启动的时候会有一个进程叫java.exe该进程中至少有一个线程在负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程。其实更严谨的来说,javaVM启动的时候不止一个线程,还有一个垃圾回收的线程
多线程:一个进程中有多个线程同时运行

线程运行状态:


创建:start()

运行:具备执行资格,同时具备执行权;

冻结:sleep(time),wait()—notify();释放执行资格;

临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;

消亡:stop()


二、多线程的创建方式

第一种方式:自定义一个线程并继承Thread类。

步骤:

       1、定义类继承Thread。
       2、复写Thread类中的run方法。      
             目的:将自定义代码存储在run方法。让线程运行。
       3、调用线程的start方法,      
             该方法两个作用:启动线程,调用该线程的run方法。

为什么要覆盖run方法?
      Thread类用于描述线程该类定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法也就是说Thread类中的run方法,用于存储线程要运行的代码。

主要方法:

获取线程名称:getName()
设置线程名称:setName()或者构造方法
返回对当前正在执行的线程对象的引用:currentThread()

代码示例:

class SubThread extends Thread {public SubThread(String name) {super(name);}public void run() {for (int i = 0; i < 100; i++) {System.out.println(this.getName()+"--thread");//Thread.currentThread()==thisSystem.out.println(Thread.currentThread().getName()+"thread");}}}public class ThreadDemo {public static void main(String[] args) {SubThread st = new SubThread("one");//创建一个线程,并命名onest.start();//开启一个线程for (int i = 0; i < 100; i++) {System.out.println("hello");}}}

第二种方式:实现Runnable接口

步骤:
      1、定义类实现Runnable接口
      2、覆盖Runnable接口中的run方法。
           将线程要运行的代码存放在该run方法中。
      3、通过Thread类建立线程对象。
      4、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
           为什么要将Runnable接口的子类对象传递给Thread的构造函数。
           因为,自定义的run方法所属的对象是Runnable接口的子类对象。
           所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。
      5、调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。

实现方式和继承方式有什么区别?
        实现方式:避免了但继承的局限性 在定义线程时 建议使用此方式
两种方式的区别:
        继承Thread:线程代码存放在thread子类run方法中
        实现Runnable:线程代码存放在接口子类run方法中

示例代码:卖票小程序

class Ticket implements Runnable{//extends Thread{private int tic=100;public void run(){while(true){   if(tic>0){  System.out.println(Thread.currentThread().getName()+"ticket"+tic--);}}}}public class TicketDemo {public static void main(String[] args) {Ticket t=new Ticket();Thread t1=new Thread(t);Thread t2=new Thread(t);Thread t3=new Thread(t);Thread t4=new Thread(t);t1.start();t2.start();t3.start();t4.start();}}

分析:在上述卖票程序中,如果我们按照继承Thread的方式来创建线程 那么假如有四个机器卖票,则我们需要创建四个Ticket对象,每一个对象对应一个线程

Ticket t1=new Ticket();Ticket t2=new Ticket();Ticket t3=new Ticket();Ticket t4=new Ticket();t1.start();t2.start();t3.start();t4.start();

这样一来,每一个对象就会对应100张票,就会卖出400张票,这显然是不行的。而通过实现Runnable接口我们只需要创建一个Ticket对象,让它开启四个线程。这样四个线程就会共享这100张票,符合我们的需求

三、线程同步

上述例子存在问题:当0线程进入if循环之后还没有执行tic--,cpu将其执行权切换到了1线程,可能1线程进到循环之后也没有执行tic--,其执行权又被cpu切换到了线程2。我们可以在tic--该行代码前加上Thread.sleep(10),来模拟这一现象 ,结果会打印出-1,-2号票 这就是程序存在的安全隐患。

原因在于:当多条语句在操作线程共享数据时候,一个线程对多条语句值执行了一部分还没有至执行完,另一个线程进来执行,导致共享数据错误
解决办法:对多条操作共享数据的语句,只能让一个线程执行完,在执行过程中其他线程不能进来执行,Java对于多线程的安全问题提供了专业的处理办法。就是同步代码块:

synchronized(对象){需要被同步的代码}
对象如同锁,想持有所锁的线程可以同步进行
没有锁的进程即使持有cpu的执行权也进不去,因为没有获取锁
同步的前提:
1、必须有两个或两个以上的线程
2、必须是多个线程使用同一个锁
好处:解决了多线程的安全问题
弊端:多个线程需要判断锁,比较消耗资源

上述代码可修改为:
class Ticket implements Runnable{//extends Thread{private int tic=100;Object obj=new Object();public void run(){while(true){ synchronized (obj) {if(tic>0){try {Thread.sleep(10);//出现-1、-2号票 有安全问题} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"ticket"+tic--);}}}}}

同步函数:让函数具备同步性;
同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是this。
所以同步函数使用的锁是this。

通过程序来验证:
一个线程在同步代码块中
一个线程在同步函数中
两个线程都在执行买票动作,如果在在同步代码块中使用obj锁就会出现0号票,如果使用this就不会出现

class Ticket2 implements Runnable {// extends Thread{private int tic = 100;Object obj = new Object();boolean flag = true;public void run() {if (flag) {while (true) {synchronized (this) {if (tic > 0) {try {Thread.sleep(10);// 出现-1、-2号票 有安全问题} catch (Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "obj::" + tic--);}}}} elsewhile (true)show();}public synchronized void show() {if (tic > 0) {try {Thread.sleep(10);} catch (Exception e) {}System.out.println(Thread.currentThread().getName() + "....show.... : " + tic--);}}}public class ThisLockDemo {public static void main(String[] args) {Ticket2 t = new Ticket2();Thread t1 = new Thread(t);Thread t2 = new Thread(t);t1.start();try {Thread.sleep(10);} catch (Exception e) {}t.flag = false;t2.start();}}

注意:如果同步函数被static修饰后,使用的锁是该类对应的字节码文件对象,就是类名.class,因为静态随着类的加载而加载

死锁:同步当中嵌套着同步。而锁却不同

class DeadTest implements Runnable {private boolean flag;DeadTest(boolean flag) {this.flag = flag;}@Overridepublic void run() {if (flag) {synchronized (MyLock.lacka) {System.out.println("if...locka");synchronized (MyLock.lockb) {System.out.println("if....lockb");}}} else {synchronized (MyLock.lockb) {System.out.println("else...lockb");synchronized (MyLock.lacka) {System.out.println("else....locka");}}}}}class MyLock {public static Object lacka = new Object();public static Object lockb = new Object();}public class LockDemo {public static void main(String[] args) {Thread t1 = new Thread(new DeadTest(true));Thread t2 = new Thread(new DeadTest(false));t1.start();t2.start();}}

四、线程间通信

 线程间通讯:其实就是多个线程在操作同一个资源,但是操作的动作不同。


示例代码:
class Res {String name;String sex;}class Input implements Runnable {private Res r;Input(Res r) {this.r = r;}@Overridepublic void run() {int x = 0;while (true) {synchronized (r) {if (x == 0) {r.name = "mike";r.sex = "man";} else {r.name = "丽丽";r.sex = "女女女";}x = (x + 1) % 2;}}}}class Output implements Runnable {private Res r;Output(Res r) {this.r = r;}@Overridepublic void run() {while (true) {synchronized (r) {System.out.println(r.name + ":::" + r.sex);}}}}public class InputOutput {public static void main(String[] args) {Res r = new Res();Input i = new Input(r);Output o = new Output(r);Thread t1 = new Thread(i);Thread t2 = new Thread(o);t1.start();t2.start();}}

上述代码打印结果会出现连续的存储或者连续的取出,因此我们要加入线程的等待唤醒机制 让结果是存储一个,取出一个。上述代码可修改为

class Res {String name;String sex;boolean flag = false;}class Input implements Runnable {private Res r;Input(Res r) {this.r = r;}@Overridepublic void run() {int x = 0;while (true) {synchronized (r) {try {r.wait();} catch (Exception e) {}if (x == 0) {r.name = "mike";r.sex = "man";} else {r.name = "丽丽";r.sex = "女女女";}x = (x + 1) % 2;r.flag = true;r.notify();}}}}class Output implements Runnable {private Res r;Output(Res r) {this.r = r;}@Overridepublic void run() {while (true) {synchronized (r) {if (!r.flag)try {r.wait();} catch (Exception e) {}System.out.println(r.name + ":::" + r.sex);r.flag = false;r.notify();}}}}
wait:线程处于等待状态。放弃了执行权,放弃了锁。
notify():唤醒线程池中 的第一个等待线程
notifyAll():唤醒线程池中的所有等待线程。

都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。

为什么这些操作线程的方法要定义Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。
不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。

生产者与消费者

class Resource{private String name;private int count=0;private boolean flag=false;public synchronized void set(String name){while(flag)try {this.wait();}catch(Exception e) {}this.name=name+"..."+count++;System.out.println(Thread.currentThread().getName()+"生产者"+this.name);flag=true;this.notifyAll();}public synchronized void get(){while(!flag)try {this.wait();}catch(Exception e) {}System.out.println(Thread.currentThread().getName()+"消费者"+this.name);flag=false;this.notifyAll();}}class Producer implements Runnable{private Resource res;Producer(Resource res){this.res=res;}@Overridepublic void run() {while(true){res.set("商品");}}}class Consumer implements Runnable{private Resource res;Consumer(Resource res){this.res=res;}@Overridepublic void run() {while(true){res.get();}}}public class ProducerConsumer {public static void main(String[] args) {Resource res = new Resource();Producer pro = new Producer(res);Consumer con = new Consumer(res);Thread t1 = new Thread(pro);Thread t2 = new Thread(con);Thread t3 = new Thread(pro);Thread t4 = new Thread(con);t1.start();t2.start();t3.start();t4.start();}}

对于多个生产者和消费者。
要使用while循环判断标记,让被唤醒的线程再一次判断标记。不能使用if循环
要使用notifyAll(唤醒所有线程),是为了唤醒对方线程。notify,可能出现只唤醒本方线程的情况。导致程序中的所有线程都等待。

Jdk1.5对多线程的操作进行了升级

 将同步Synchronized替换成显示Lock操作。
 将Object中的wait,notify notifyAll,替换了Condition对象。
 该对象可以通过Lock锁 进行获取。
 下面示例中,实现了本方只唤醒对方操作。

class Resource {private String name;private int count = 0;private boolean flag = false;private Lock lock = new ReentrantLock();private Condition condition_pro = lock.newCondition();private Condition condition_con = lock.newCondition();public void set(String name) throws InterruptedException {lock.lock();try {while (flag)condition_pro.await();this.name = name + "..." + count++;System.out.println(Thread.currentThread().getName() + "生产者" + this.name);flag = true;condition_con.signal();} finally {lock.unlock();}}public void get() throws InterruptedException {lock.lock();try {while (!flag)condition_con.await();System.out.println(Thread.currentThread().getName() + "消费者" + this.name);flag = false;condition_pro.signal();} finally {lock.unlock();}}}class Producer implements Runnable {private Resource res;Producer(Resource res) {this.res = res;}@Overridepublic void run() {while (true) {try {res.set("商品");} catch (InterruptedException e) {e.printStackTrace();}}}}class Consumer implements Runnable {private Resource res;Consumer(Resource res) {this.res = res;}@Overridepublic void run() {while (true) {try {res.get();} catch (InterruptedException e) {e.printStackTrace();}}}}public class ProducerConsumer {public static void main(String[] args) {Resource res = new Resource();Producer pro = new Producer(res);Consumer con = new Consumer(res);Thread t1 = new Thread(pro);Thread t2 = new Thread(con);Thread t3 = new Thread(pro);Thread t4 = new Thread(con);t1.start();t2.start();t3.start();t4.start();}}

五、线程的其他操作

1、停止线程

stop方法已经过时。
如何停止线程?
只有一种,run方法结束。
开启多线程运行,运行代码通常是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
特殊情况:
当线程处于了冻结状态。
就不会读取到标记。那么线程就不会结束。
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。
强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();强制让冻结状态中的线程恢复到运行状态,而不是停止线程。它会获得一个中断异常。

2、守护线程
setDaemon(boolean b):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
3、加入线程
join():当A线程执行到了B线程的.join()方法时,A就会等待(冻结)。等B线程都执行完,A才会执行(恢复到运行状态)。join可以用来临时加入线程执行。
4、更改线程优先级
setPriority(int newPriority)
public static final int MIN_PRIORITY
线程可以具有的最低优先级。(1)
public static final int NORM_PRIORITY
分配给线程的默认优先级。(5)
public static final int MAX_PRIORITY
线程可以具有的最高优先级。(10)


原创粉丝点击