Java多线程从入门到精通(一)

Java多线程简单入门

本篇文章主要讲述Java多线程的基础入门知识,之后会陆续分享更深入的技术!
一、两种创建线程的方法
1、MyThread继承Thread类,然后重写其中的run方法
示例如下:

public class MyThread extends Thread{
	@Override
	public void run() {
		while(true) {
			System.out.println("我是线程1");

		}
	}

//在main方法中创建该类对象,并且执行start方法。
		MyThread m1=new MyThread();
		m1.start();

结果展示如下:
在这里插入图片描述2、实现Runnable接口,重写其中的run方法
示例如下 :

public class MyThread2 implements Runnable{

	@Override
	public void run() {
		while(true) {
			System.out.println("我是线程2");

		}
	}

}

//在main方法中创建该类对象,并将其放到线程中,启动start方法 
		MyThread2 m2 =new MyThread2();
		Thread thread=new Thread(m2);
		thread.start();

结果展示如下:
在这里插入图片描述这个时候我们会疑问?这两种方式有什么区别吗?
从功能上来讲,二者没有什么区别,因为在底层的运行过程中,二者的执行是一样的。
note:当一个类已经继承了其他类,就不能再继承Thread类了

继承Thread类是不是更加方便呢?
从开发者的角度来讲,确实是比较方便。只不过,实现Runnable接口的线程类,在逻辑上更加面向对象。

note:在实际的开发测试或者验证某些线程逻辑关系的时候,往往使用匿名内部类这样的方法去测试,如下所示:

		Thread t =new Thread(new Runnable() {
			
			@Override
			public void run() {
				while(true) {
					System.out.println("111111111111111111111");
	
					
				}
				
			}
		}) ;
		t.start();

二、线程的生命周期:
线程的生命周期有五种状态:即新建、就绪、运行、阻塞、死亡
新建:线程类 被JVM加载到内存中
就绪:当被调用了线程类的start方法之后,进入就绪状态
运行:当被调用了start方法之后的线程,获得了cpu的时间片,进入运行状态。
阻塞:1、当线程中调用了具备阻塞能力的方法或代码
    比如:读取一个文件没有读取完
        while true
   2、当线程中调用了Thread.sleep(2000)
   3、当线程尝试获取同步监视器(lock)的时候,而没有获取到
死亡:1、run方法执行完成
   2、线程抛出异常
   3、直接调用线程的stop方法结束线程
   note:当一个线程死亡之后,不能对死亡的线程重新调用start方法重新启动

  • note:开启线程并不是为了提高性能,而是提高吞吐量(并发度)
    三、线程控制:
    1、join线程
    当一个线程需要等待另一个线程完毕再执行的话,可以使用Thread的join方法。
    假设线程A和线程B,在A执行时调用了B的join方法,A将被阻塞,一直等到B线程执行完毕后,A线程继续执行,就好像排队加塞
    2、线程休眠
    如果让线程休息一会,我们可以使用Thread的静态方法sleep(long millis)。这个方法的意思是:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
    sleep方法会让线程休息指定的毫秒数,在sleep的时候,不会放弃执行权力,等待休息时间到了,马上获取执行机会。也就是说,在这个线程sleep的时候,其他线程不会获得CPU的执行权力。
    (线程休眠可以理解为该线程在cpu上睡觉,睡醒后继续执行。)
    3、线程优先级
    每个线程执行时都具备一个优先级,优先级越高越容易获得执行机会,优先级越低获得的执行机会越少。默认情况下,如果不设置,线程都具备普通优先级。
    另外,并不一定是拥有优先级高的线程和有用优先级低的线程在竞争时,优先级高的就一定能获取执行机会。
    Thread类的setPriority(int newPriority)和getPriority()方法能够设置和查看线程的优先级。
    其中,setPriority方法的参数是一个int整数,范围是1-10,更经常的,我们使用Thread类的三个静态变量来设置:
    static int MAX_PRIORITY
    线程可以具有的最高优先级。 值是10
    static int MIN_PRIORITY
    线程可以具有的最低优先级。 值是1
    static int NORM_PRIORITY
    分配给线程的默认优先级。 值是5
    四、线程安全:
    线程安全的概念不容易定义,在《Java 并发编程实践》中,作者做出了如此定义:多个线程访问一个类对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方法代码不必作其他的协调,这个类的行为仍然是正确的,那么这个类是线程安全的。

用一个示例来演示什么是线程不安全

public class UnsafeThreadTest {

	V v = new V();
	public static void main(String[] args) {
		UnsafeThreadTest test = new UnsafeThreadTest();
		test.test();
	}
	/**
	 * 开两个线程,分别调用V对象的打印字符串的方法
	 */
	public void test(){
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true){
					v.printString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
				}
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true){
					v.printString("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
				}
			}
		}).start();
	}
	/**
	 * 这个类负责打印字符串
	 */
	class V {
		/**
		 * 为了能使方法运行速度减慢,我们一个字符一个字符的打印
		 * @param s
		 */
		public void printString(String s){
			for(int i = 0;i<s.length();i++){
				System.out.print(s.charAt(i));
			}
			System.out.println();
		}
	}
}

输出结果:

BBBBBBBBAABBBBBBA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBB
AAAAAAAAAABBB
BBBBBBBBBBBBBBB

这里我们发现打印结果混乱,A和B 交错出现。也就是说线程1调用V对象的printString方法时,线程2对象也可以调用printString对象。但是这个方法并不能说明线程不安全,但我们看到了一个线程调用一个方法的同时,如果不加以控制,另外一个线程也有可能进入这个方法中执行!
1、同步代码块
同步代码块,就是定义了一组原子性代码。所谓原子性代码,就是说在这一组代码块中,只允许一个线程进入。直到这个线程运行完毕,下一个线程才可以进入。同步代码块的格式如下:
synchronized (obj) {
}
这里的obj,我们可以理解为一把锁,叫做同步监视器。
2、同步方法:
synchronized关键字也可以放到方法上,这样整个方法就是同步的。同步监视器就是类对象本身this。
只需要将前面的代码换成如下即可:

public synchronized void printString(String s){
				for(int i = 0;i<s.length();i++){
					System.out.print(s.charAt(i));
				}
				System.out.println();
			}
		}

注意:synchronized关键字可以修饰方法、代码块,但是不能修饰构造方法和属性。

线程要进入同步代码块或同步方法中,必须先获得同步监视器的锁定。也就是说必须先拿到锁,然后进入方法。那么什么时候释放锁呢?
1.方法执行结束
2.在方法中遇到Exception,导致异常
3.程序中遇到了退出程序的代码,比如return
4.程序执行了同步监视器对象的wait()方法
五、wait notify
当线程A运行过程中遇到不满足的条件需要等待,等待另外的线程B去更改系统状态。当B更改系统状态后,唤醒等待线程A。等待线程A查看是否满足条件,如果满足则继续执行,不满足则继续等待。
举个例子。在ATM中,有两个线程。一个用来存款,一个用来提款。当提款的线程提款时,发现余额不足,那么它等待有线程过来存款。一旦有线程存款,那么马上唤醒这个提款的线程。提款的线程继续查看是否余额大于提款额度,如果大于,则提款,否则继续等待。
这个功能的实现可以借助于Object类的wait notify和notifyAll方法,注意,这三个方法不是Thread类的方法,而是Object类方法。
另外,要使用这些方法,必须获得同步监视器对象。

/**
 * 运行两个线程,然后这两个线程交替执行,A先执行,执行完毕,B执行 wait notify notifyAll 都是Object的方法
 * wait方法只能在 获取同步监视锁的方法中或代码块中才能调用 同步监视锁既是 synchronized
 */
public class Testwait {
	public static void main(String[] args) {
		final LoopTest lt = new LoopTest();
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				while (true)
					lt.f1();
			}
		}, "线程A").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				while (true)
					lt.f2();
			}
		}, "线程B").start();
	}
}
class LoopTest {
	boolean flag = false;
	public synchronized void f1() {
		while (flag) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} 
			System.out.println(Thread.currentThread().getName());
			flag = true;
			notifyAll();
	}

	public synchronized void f2() {
		while (!flag) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} 
			System.out.println(Thread.currentThread().getName());
			flag = false;
			notifyAll();
	}

}

执行结果:
线程A
线程B
线程A
线程B
线程A
线程B

在这段代码中,我们开启了两个线程,线程中的run方法调用了LoopTest类对象的f1方法。在f1方法中,首先必须获得同步监视器对象,也就是必须有synchronized才可以使用wait和notifyAll。第一个线程进入f1方法,发现flag为false,没有进入if代码块,将flag设置为true(这时候另外一个线程可能已经进入f1方法,发现flag为false,于是进入wait状态),同时唤醒正在wait状态的所有线程(这里使用的notifyAll)。这时候,进入wait状态的线程会被唤醒,继续检查是否满足条件,如果满足条件则运行,否则继续等待。

  • 7
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值