《JAVA多线程编程核心技术》

13 篇文章 0 订阅

一、JAVA多线程技能

多线程使用异步,同步意味着一个任务想要执行必须等待上一个任务执行完才可以。线程被调度的时机是随机的。

使用多线程:

多线程的实现

1. 继承Thread类

需要重写run方法

首先Thread类的结构是

public class Thread implements Runnable

Thread类实现了Runnable接口

2. 实现Runnable接口

需要实现run()方法

Thread类的常用构造方法有

Thread()
Thread(Runnable target)
Thread(Runnable target, String name)
Thread(String name)

其中有两个可以传递Runnable接口,可以传入一个Runnable接口的对象。也可以传入Thread类的对象。

使用继承Thread类的接口的多线程的方法有一定的局限性,是因为JAVA不支持多继承,如果预创建的线程已经有一个父类了,那么此时就不能在继承Thread类,可以使用实现Runnable接口的方法。

 

实例变量和线程安全

共享数据-多个线程访问同一变量

像i--这种代码容易产生非线程安全问题,可以对重写的的run方法前面加上synchronized关键字,时多个线程在调用run方法前,前判断run方法有没有悲伤所,如果有锁,必须等持有锁的线程调用结束才可以执行run方法。

对于代码

package Java.Thread;

public class ManyThread {
	public static void main(String args[]) {
		
		MyThread T = new MyThread();
		Thread T1 = new Thread(T, "A");
		Thread T2 = new Thread(T, "B");
		Thread T3 = new Thread(T, "c");
		Thread T4 = new Thread(T, "A");
		T1.start();
		T2.start();
		T3.start();
		T4.start();
	}
	public static class MyThread extends Thread{
		private int count = 5;
		synchronized public void run() {
			count--;
			System.out.println(Thread.currentThread().getName()+" = "+count);
		}
	}
}
// 本例是代码中的变量共享,如果一开始新建两个MyThread线程,那么就是相当于两个对象分别操作俩个变量
// 但如果是只新建一个MyThread对象,把这个对象当做参数传到Thread中,就是相当于一个对象在操作共享变量

currentThread()方法返回代码正在被哪个线程调用的信息

isAlive() 判断当前线程是否处于活动状态,线程处于正在运行或者准备运行(start())都是活动状态

还有sleep getId等方法

 

停止线程

(远古时期)使用的是Thread.stop()方法,但这个方法不安全,已被弃用,现在主要是

1. 使用退出标志,使线程正常退出,也就是当run方法执行完后正常中止,比如一些循环的while,可以通过设置while中的标志进行线程的停止

2. 使用stop()强行终止,thread.stop(), 不推荐:

如果在调用thread.stop()后导致了该线程所持有的所有锁的突然释放(不可控制),那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。

3. 使用inturrupt方法中断线程,注意是Thread.currentThread().interrupt(),注意是currentThread()

(1)线程阻塞时(在线程sleep下)使用interrupt(),会进入catch语句抛出一个InterruptedException例外,并且清除停止状态值使之变为false

(2)线程未处于阻塞状态,使用isInterrupted()判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志就会置true,和使用自定义的标志来控制循环是一样的道理。 

将方法interrupt()和return结合使用也可以实现线程停止的效果,但最好还是使用抛出异常的方式

关于两个方法:

this.interrupted() 测试当前线程是否已经中断状态,执行后将中断标志再置位false

this.isInterrupted() 测试线程Thread对象是否已经中断状态,但不清楚状态标志。使用isInterrupt()时,可以直接thread.interrupt(),thread是一个线程实例(对象)

 

暂停线程

使用suspend()暂停 resume()恢复 

缺点

1. 独占:注意使用方法, 否则极易造成公共的同步对象的独占,是其它线程无法访问该公共同步对象

2. 不同步

 

yield方法

放弃当前CPU资源,将他让给其他的任务去占用CPU执行时间。但放弃时间不确定,有可能刚刚放弃又马上获得CPU时间片

线程优先级

setPriority()方法设置线程优先级,1~10个优先级  getPriority()是获取当前线程优先级

线程的优先级具有继承性,B继承A,则B的优先级和A的优先级相同

优先级具有规则性还有随机性,规则性是指高优先级的线程总是大部分先执行完,但不是全部都执行完,优先级与代码执行顺序无关;随机性是指:优先级高的线程不一定每一次都先执行完run方法中的任务,优先级与打印顺序无关。优先级高的运行的较快。

守护线程

Java中的两种线程是守护线程和用户线程。守护线程:当进程中不再有非守护线程时,守护线程自动销毁。垃圾回收线程是典型 的守护线程,当进程中没有非守护线程,则垃圾回收线程也就没有存在的意义了,随着JVM一同结束工作。

 

二 对象及变量的并发访问

synchronized同步方法

方法内的私有变量为线程安全:方法内部的私有变量不存在“非线程安全问题”

实例变量非线程安全:两个线程同时访问一个没有同步的方法,如果两个线程同时操作业务对象中的实例变量,则可能会出现“非线程安全”问题,可在方法前加上synchronized关键字,使方法变为同步方法

关键字synchronized取得的锁都是对象锁,多个线程访问多个对象就会创建多个锁

当A线程持有object对象的Lock锁时,B可以异步的方式调用object对象中的非synchronized类型的方法,但是如果想要调用object中的同步方法,则需要等待(这就是同步)。

A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法所在的对象的锁,其他线程必须等A线程执行完才可以调用X方法,而B线程如果调用了声明synchronized关键字的非X方法时,必须等A线程将X方法执行完,也就是释放对象锁之后才可以调用。

可重入锁:当一个线程得到一个对象锁时,再次请求此对象锁时是可以再次得到该对象锁的,自己可以获取自己的内部锁

另外 B线程继承自A线程,那么B可以通过重入锁的方式访问A的同步方法。

当一个线程执行的代码出现异常时,其持有的锁会自动释放

同步不可以继承,就是如果B继承自A,A类中方法同步,那么B类中的方法不同步,需要自己手动添加synchronized


synchronsized 同步语句块

脏读:在读取实例变量时,这个值已经被其他的线程更改过

使用同步方法的弊端:使用同步方法的线程可能会占用很长时间,造成后面线程长时间等待

同步代码块:synchronized(this), 和同步方法一样,一段时间内只能有一个线程来执行,其他线程必须等待当前线程执行完这个代码块才可以执行该代码块,在synchronized中的代码是同步,不在里面的是异步

当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中的所有其他synchronized(this)的同步代码块访问将会被阻塞,这说明synchronized使用的“对象监视器”是一个

总结synchronized同步的作用:不管是对代码块还是方法(1)对其他synchronized修饰的同步方法或者代码块(this)调用呈阻塞状态(2)同一时间只有一个线程可以执行synchronized同步方法或者(this)中的代码

对于synchronized(非this对象):在多个线程持有对象监视器为同一对象的前提下,同一时间只有一个线程可以执行同步代码块中的代码;当持有对象监视器为同一个对象的前提下,同一时间只有一个线程可以执行同步代码块中的方法

package SynCo;


/* 关于同步代码块的非this锁和this锁
* 当两个代码块都是this锁对象时比如线程Ta Tb,他们在访问两个synchronized(this)时,同一时间内只有一个线程可以使用当前同步代码块,因为同一时间内只有一个线程可以访问当前this锁的代码块,其他被阻塞,因此执行结果是同步的
* 当两个代码块是非this时,结果异步
* 当两个分别是一个代码块一个同步方法时,结果异步
* 当两个都是同步方法时,结果同步
*/


public class Service {
	void methodA() {
		Object object1 = new Object();
		synchronized(object1) {
		System.out.println("begin 这是方法A~~");
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("end   这是方法A~~");
		}
	}
	 void methodB() {
		 Object object2 = new Object();
		 synchronized(object2) {
		System.out.println("begin 这是方法B~~");
		System.out.println("end   这是方法B~~");
		}
	}
}
package SynCo;

public class ThreadA extends Thread{
	private Service service;
	public ThreadA(Service service) {
		super();
		this.service = service;
	}
	public void run() {
		service.methodA();
	}
}

package SynCo;

public class ThreadB extends Thread{
	private Service service;
	public ThreadB(Service service) {
		super();
		this.service = service;
	}
	public void run() {
		service.methodB();
	}
}

package SynCo;

public class Run {
	public static void main(String args[]) {
		Service service = new Service();
		ThreadA Ta = new ThreadA(service);
		Ta.start();
		ThreadB Tb = new ThreadB(service);
		Tb.start();
	}
}

锁非this对象具有一定的优点,如果一个类中有很多同步方法,这是虽然可以实现同步但会收到阻塞,所以影响运行效率;但如果使用同步代码块锁非this对象,则代码块中的程序和同步方法是异步的,不与其他锁this同步方法争抢this锁

同步代码块是锁定当前对象的(和同步方法一样)

package Java.Thread;

// 同步代码块 锁定当前对象
public class SynCode {
	synchronized public void M1() {
		System.out.println("这是第一个线程调用的方法~~");
	}
	synchronized public void M2() {
		for(int i = 0; i < 20; i++) {
			System.out.println("Thread i = " + i);
		}
	}
	
	static class MyThread1 extends Thread{
		static SynCode syn;
		public MyThread1(SynCode syn) {
			this.syn = syn;
		}
		synchronized public void run() {
			syn.M1();
		}
	}
	static class MyThread2 extends Thread{  // 注意这个static,不加会报错
		static SynCode syn;
		public MyThread2(SynCode syn) {
			this.syn = syn;
		}
		synchronized public void run() {
			syn.M2();
		}
	}
	public static void main(String args[]) {
		SynCode syn = new SynCode();
		MyThread1 m11 = new MyThread1(syn);
		MyThread2 m12 = new MyThread2(syn);
		
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		m12.start();
		m11.start();
	}
}

synchronized(非this对象x) :支持“任意对象”作为对象监视器来实现同步功能,对象监视器必须是同一个对象??(不是很懂)

(书中的代码执行结果:异步调用,结果交叉执行)

关键字synchronized应用在static静态方法上,那是对当前文件*.java文件对应的class进行加锁,class锁对类的所有对象实例起作用,当多个线程同时执行这两种锁的时候,是异步的。在staticsyn中,有演示代码,代码中A B是类锁,C是对象锁,AB是同一个锁是按顺序执行的即同步的,C是对象锁,不一样,所以和AB是异步执行的,要想保持同步,那么锁住的就要是同一个对象。

对象锁要想保持同步执行,那么锁住的必须是同一个对象

总结:1. 如果多线程同时访问同一类的类锁和对象锁,那么这两个方法的执行是异步的,原因是这是两种不同的锁

2. 类所对该类的所有对象都能起作用,而对象锁不能

 

同步synchronized方法无线等待时,比如在A线程调用的方法中由一个无限死循环,那么可以将A调用的方法或者线程中所有的方法改为同步代码块的方式,当一个是同步代码块一个是同步方法或者是两个都是同步代码块时问题可以得到解决

 

死锁:多线程的死锁是常考问题,之所以会发生死锁是因为不同的线程都在等待根本不可能被释放的锁,从而导致所有的任务无法继续完成,比如双方都在等待对方持有的锁。避免:1. 避免双方互相持有对方锁的情况,2. 设置加锁顺序,3. 死锁检测。

内置类与静态内置类:

实例化内置类需要:

Neizhi neizhi = Waiceng.new Neizhi();

静态内置类就是在类之前加了static,这时候不用想上面那么麻烦直接使用静态类名new就可以了。

 

关于锁对象的改变:将任何数据类型作为同步锁需要注意是否有多个线程同时持有锁对象,如果同时持有相同的锁对象那么线程就是同步的,如果分别获得锁对象,那么就是异步的。比如synchronized(lock),当lock=“123”或者“456”时这就是不同的锁。

volatile关键字

主要作用是使变量在多个线程之间可见。强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。。

eg:一个变量在多线继承Thread中有代码 private boolean isRunning=true;此变量存在于公共堆栈和线程私有堆栈中,在JVM中的-server模式下,为了程序运行的效率,线程一直在私有堆栈中取得isRunning 的值是true,后来在main主程序中置位false,虽然被执行但是更新的确实公共堆栈中的isRunning,那么程序一直读到错误的值,对我们的程序造成影响。私有堆栈和公共堆栈中的值不同步。那么volatile的主要作用就是当线程访问这个变量时,强制从公共堆栈中取值。

volatile增加了实例变量在多个线程之间的可见性,但是不支持原子性。对于多个线程访问同一个实例变量还是应该使用synchronized同步。

volatile和synchronized的比较:

1. volatile是线程同步的轻量级实现,只修饰变量,而synchronized可以修饰方法以及代码块

2. 多线程访问volatile不会发生阻塞,但是synchronized会发生阻塞

3. volatile可以保证数据之间的可见性但不保证原子性,synchronized可以保证原子性也可以间接保证可见性,因为他可以将私有内存和公共内存中的数据做同步

4. volatile解决的是变量在多个线程之间的可见性,synchronized解决的是多个线程之间访问资源的同步性

关于volatile不具备原子性的原因:

在使用volatile关键字时,线程在读取共享变量时可以获取最新的值,但需要注意的是,如果修改实例变量中的数据比如i++,i--这种操作,这样的操作并不是原子性操作,也就是非线程安全的,步骤1. 从内存中读取i     2. i+1,计算i的值    3. 将i写入内存,分为这三步,若在第二部时另外一个线程也修改i,那么就会出现脏读(理解:这个时候从公共堆栈读出的数据,中途又被人修改过了,不是我们所需要的最终数据)

关于i++的解决方法:

1. 使用synchronized关键字进行同步

2. 使用AtomicInteger原子类进行实现,比如代码

AtomicInteger count = new AtomicInteger(0);

那么当设计count的操作,比如count.incrementAndGet()时就会可以在没有锁的情况下做到线程安全,但是原子类在有逻辑性的情况下输出结果具有随机性因为有些时候方法和方法之间调用却不是原子的

synchronized代码块具有volatile同步的功能。具有将线程工作内存中的私有变量与公共内存中的变量同步的功能

 

三、线程间通信

线程之间的通信:轮询、通知等待机制、join()方法······

轮询是通过不断检测某一条件,判断是否达到了某个线程进行相应动作的时候

等待通知机制wait()/notify()

wait是object类的方法,调用wait之前,线程必须获得该对象的对象级别锁,即只能在同步方法或者同步代码块中调用wait(),执行wait方法后,当前线程释放锁。如果在调用wait之前没有持有适当的锁,则抛出IllegalMonitorStateException.

notify也需要在同步代码块或者同步方法中调用,即在调用之前也必须持有适当的锁,也会抛出IllegalMonitorException.该方法随机挑选一个wait状态的线程,对其发送notify,并使它等待获取该对象的对象锁。需要说明的是,在执行notify之后,当前线程不会马上释放该对象锁,要等待执行notify的线程执行完,也就是退出synchronized代码块后,当前线程才会释放锁(synchronized代码块中的A线程执行notify随机唤醒了B线程,B线程不会马上获得这个对象锁,要等待A执行完所在的synchronized代码块释放锁才可以),在执行同步代码块过程中遇到了异常而线程终止,锁也会被释放。

在执行同步代码块的过程中,执行了锁所属对象的wait后,这个线程会释放对象锁,而此线程会进入线程等待池中等待被唤醒。

关于wait的一个方法wait(long):等待某一时间内没有线程对锁进行唤醒,超过这个时间自动唤醒

生产者/消费者模式的实现:

package entity;

// 生产者
/**
 * 
 * @author SLY
 * 当value值是非空的时候,此时应该将值取出,所以生产者线程wait,
 * 当value值是空的时候, 此时应该setValue,之后notify唤醒消费者进程
 */
public class P {
	private String lock;

	public P(String lock) {
		super();
		this.lock = lock;
	}

	public void setValue() {
		try {
		synchronized(lock) {
			if(!ValueObject.value.equals("")) {   // 非空
					lock.wait();
			}
			String value = System.currentTimeMillis()+ "_" + System.nanoTime();
			System.out.println("set的值是" + value);
			ValueObject.value = value;
			lock.notify();
		} 
		}catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
package entity;

// 消费者
/**
 * 
 * @author SLY
 * 当值为空的时候,此时应当进入生产者线程,所以wait
 * 当值为非空的时候,getValue,然后将value置位空,转而向生产者线程notify
 *
 */
public class C {
	private String lock;
	public C(String lock) {
		super();
		this.lock = lock;
	}
	public void getValue() {
		synchronized(lock) {
			if(ValueObject.value.equals("")) {  // 值为空
				try {
					lock.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println("get的值是" + ValueObject.value);
			ValueObject.value = "";
			lock.notify();
		}
	}
}
package entity;

public class ValueObject {
	public static String value = "";
}

package entity;

public class ThreadP extends Thread{
	private P p;
	public ThreadP(P p) {
		super();
		this.p = p;
	}
	
	public void run() {
		while(true) {
		p.setValue();
		}
	}
}

package entity;

public class ThreadC extends Thread {
	private C r;
	public ThreadC(C r) {
		super();
		this.r = r;
	}
	
	public void run() {
		while(true) {
			r.getValue();
		}
	}
}


package entity;

public class Run {
	public static void main(String args[]) {
		String lock = new String("");
		P p = new P(lock);
		C r = new C(lock);
		// 两把锁
		ThreadP pThread = new ThreadP(p);
		ThreadC rThread = new ThreadC(r);
		
		pThread.start();
		rThread.start();
	}
}

 当在多消费者多生产者的情况下 容易出现假死状态,就是线程进入waiting等待状态,不再指定任何业务功能,在判断字符串是空或者非空的时候使用while循环,然后ThreadP和ThreadC需要初始化成两个线程数组;

虽然在代码中使用了wait/notify,但是不能保证notify欢迎的是异类,也许是同类,比如生产者唤醒生产者,消费者唤醒消费者,假死发生的主要原因可能是连续唤醒了同类,解决办法是将异类一起唤醒,将notify变为notifyall就可以将异类同类全部唤醒 。

通过管道进行线程间通信:字节流

管道流(pipeString)是一种特殊的流,用于在不同的线程之间传送数据,一个线程发送数据到输出管道,另一个线程从输入管道中读取数据

JDK中提供了四个类来实现线程之间的通信:

1)PipedInputStream 和 PipedOutputStream

2)PipedReader 和 PipedWriter

 

方法join的使用

join()的作用是等待线程对象销毁

一般用于主线程等待子线程执行的结果时使用。

方法join()的作用是使所属的线程对象X正常执行run方法中的任务,而使当前线程z进入无期限的阻塞,等待线程X销毁后再继续执行线程z后面的代码。方法join具有使线程排队运行的作用,join与synchronized的区别是:join在内部使用wait()方法进行等待,而synchronized 关键字使用的是“对象监视器”原理作为同步。

在使用join过程中,如果当前线程被中断,则当前线程出现异常,比如join和synchronized 相冲突

方法join内部是使用wait实现的,所以join和join(long)都具有释放锁的特点

 

ThreadLocal的使用

变量值的共享可以通过public static的方式,所有的线程都使用public static的形式。当我们想实现每个线程都有自己的共享变量的时候,JDK提出了ThreadLocal ,类ThreadLocal主要解决的是每个线程绑定自己的值。

关于set()和null

package test;

/**
 * 
 * @author SLY
 * 第一次调用get返回的值是null,通过调用set方法赋值
 * 类ThreadLocal解决的是变量在不同线程之间的隔离性,也就是不同线程拥有自己的值
 * 不同线程的值可以放入ThreadLocal中进行保存
 *
 */
public class Run {
	public static ThreadLocal t1 = new ThreadLocal();
	public static void main(String args[]) {
		if(t1.get() == null) {
			System.out.println("我的心里空空的~");
			t1.set("我的心里满满的~~");
		}
		System.out.println("现在我的脑子里是 " + t1.get());
	}
}

 

验证线程之间代码的隔离性

package test;

public class Tools {
	public static ThreadLocal t1 = new ThreadLocal();
}

package test;

public class ThreadA extends Thread {
	public void run() {

		try {
			for (int i = 0; i < 5; i++) {
				Tools.t1.set("ThreadA" + (i + 1));
				System.out.println("ThreadA getValue " + Tools.t1.get());
				Thread.sleep(200);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}

// 在一个类文件中 只能有一个public类
class ThreadB extends Thread {
	public void run() {
		try {
			for (int i = 0; i < 5; i++) {
				Tools.t1.set("ThreadB" + (i + 1));
				System.out.println("ThreadB getValue " + Tools.t1.get());
				Thread.sleep(200);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
}


package test;

public class RUnn {
	public static void main(String args[]) {
		try {
		ThreadA a = new ThreadA();
		a.start();
		ThreadB b = new ThreadB();
		b.start();
		for (int i = 0; i < 5; i++) {
			Tools.t1.set("main " + (i + 1));
			System.out.println("   main getValue" + Tools.t1.get());
			Thread.sleep(200);
		}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

第一次调用get()的初始值为null,想要改变初始值可以使用 重写initalValue的方式, Object的返回形式,直接return

package test;

public class ThreadLocakExt extends ThreadLocal {
	@Override
	protected Object initialValue() {
		return "我是初始值";
	}
}

 

InheritableThreadLocal的使用

使用该类可以在子线程中取得父线程继承下来的值,需要注意的是,如果子线程在取得值的同时,主线程将值进行修改,那么取得的还是旧值

可以使用childValue(Object parentValue)重写的方式修改继承来的值

 

四、lock的使用

主要是关于ReentrantLock类

可以和synchronized达到同样的效果,并且在扩展功能上呀更加强大 比如具有嗅探锁定 多路分支通知等功能

使用ReentrantLock实现同步

使用lock()方法获得锁,使用unlock()方式释放锁

package Reentrantlock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyService {
	private Lock lock = new ReentrantLock();

	public void methodA() {
		try {
			lock.lock();
			System.out.println("methodA begin ThreadName=" + Thread.currentThread().getName() + "time="
					+ System.currentTimeMillis());
			Thread.sleep(500);
			System.out.println("methodA end ThreadName=" + Thread.currentThread().getName() + "time="
					+ System.currentTimeMillis());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	
	public void methodB() {
		try {
			lock.lock();
			System.out.println("methodB begin ThreadName=" + Thread.currentThread().getName() + "time="
					+ System.currentTimeMillis());
			Thread.sleep(500);
			System.out.println("methodB end ThreadName=" + Thread.currentThread().getName() + "time="
					+ System.currentTimeMillis());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
}

package Reentrantlock;

public class ThreadA extends Thread{
	private MyService service;
	
	public ThreadA(MyService service) {
		super();
		this.service = service;
	}
	
	public void run() {
		service.methodA();
	}
}

package Reentrantlock;

public class ThreadAA  extends Thread{
	private MyService service;
	
	public ThreadAA(MyService service) {
		super();
		this.service = service;
	}
	
	public void run() {
		service.methodA();
	}
}

package Reentrantlock;

public class ThreadB  extends Thread{
	private MyService service;
	
	public ThreadB(MyService service) {
		super();
		this.service = service;
	}
	
	public void run() {
		service.methodB();
	}
}

package Reentrantlock;

public class ThreadBB  extends Thread{
	private MyService service;
	
	public ThreadBB(MyService service) {
		super();
		this.service = service;
	}
	
	public void run() {
		service.methodB();
	}
}

package Reentrantlock;

public class Run {
	public static void main(String args[]) {
		MyService service = new MyService();
		ThreadA a = new ThreadA(service);
		a.setName("A");
		a.start();
		ThreadAA aa = new ThreadAA(service);
		aa.setName("AA");
		aa.start();
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		ThreadB b = new ThreadB(service);
		b.setName("B");
		b.start();
		ThreadBB bb = new ThreadBB(service);
		bb.setName("BB");
		bb.start();
	}
}

此实验说明调用lock.lock()代码的线程就持有了“对象监视器”其他线程只有等待锁被释放unlock时再次争抢,效果和synchronized 效果相同 线程之间还是顺序执行的。

使用Condition实现等待/通知

ReentrantLock实现等待/通知功能 也是像synchronized结合wait/notify,但是其结合的是Condition对象。

Condition类在一个Lock对象中可以创建多个Condition实例,线程对象可以注册在指定的condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。而在notify中JVM是随机选择被通知线程的。

而synchronized就相当于整个Lock对象中只有一个单一的Condition对象,所有的线程都注册在它一个对象身上,线程开始notifyall时,需要通知所有的waiting线程,没有选择权,会出现相当大的效率问题。

使用condition实现等待/通知

package Condition;

/**
 * 在这个例程中,首先是在run方法中调用了MyService中的await方法,然后await会进入一个
 * Condition类的await方法相当于 Object中的wait,然后,在Run的主线程中sleep一段时间,这时候让主线程调用signal
 * 方法,那么就可以signal,在signal中还可以继续调用MyService中的await方法
 * 只有在MyService中调用了Condition中的signal()方法,才可以通知await的线程,使得await中的线程内容继续
 * 
 */
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

// 实现通知单个线程
public class MyService {
	private Lock lock = new ReentrantLock();
	public  Condition condition = lock.newCondition();  // 注意写法 是newCondition
	public  void await() {
		try {
			lock.lock();
			System.out.println("await的时间是" + System.currentTimeMillis());
			condition.await();
			System.out.println("接触封印~");
		} catch(InterruptedException e) {
			e.printStackTrace();
		} finally {
			lock.unlock();
		}
	}
	public void signal() throws InterruptedException {
		try {
			lock.lock();
			System.out.println("signal的时间是" + System.currentTimeMillis());
			condition.signal();
		} finally {
			lock.unlock();
		}
	}
}
package Condition;

public class ThreadA extends Thread {
	private MyService service;
	public ThreadA(MyService service) {
		super();
		this.service = service;
	}
	public void run() {
		service.await();
	}
}

package Condition;

public class Run {
	public static void main(String args[]) {
		MyService service = new MyService();
		ThreadA a = new ThreadA(service);
		a.start();
		try {
			Thread.sleep(200); // 等待时间是必须的,不然就是signal先开始运行
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			service.signal();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}


/**打印结果
*await的时间是1554984649007
*signal的时间是1554984649206
*接触封印~
**/

作用:signal=notify       signalAll=notifyAll     await=wait

使用多个condition实现通知部分线程

(全部的话直接在刚才程序的基础上,将signal改为signalAll()就可以)

通知部分线程时,我们需要实例化多个Condition类的对象,

package ManyCondition;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyService {
	private Lock lock = new ReentrantLock();
	public Condition conditionA = lock.newCondition();
	public Condition conditionB = lock.newCondition();
	public void awaitA() {
		try {
			lock.lock();
			System.out.println("begin awaitA的时间是" + System.currentTimeMillis() + "Thread Name:"+ Thread.currentThread().getName());
			conditionA.await();
			System.out.println("end  awaitA的时间是" + System.currentTimeMillis()+ "Thread Name:" + Thread.currentThread().getName());
		} catch(InterruptedException e) {
			e.printStackTrace();
		}
		finally {
			lock.unlock();
		}
	}
	
	public void awaitB() {
		try {
			lock.lock();
			System.out.println("begin awaitB的时间是" + System.currentTimeMillis() + "  Thread Name:"+ Thread.currentThread().getName());
			conditionB.await();
			System.out.println("end  awaitB的时间是" + System.currentTimeMillis()+ "  Thread Name:" + Thread.currentThread().getName());
		} catch(InterruptedException e) {
			e.printStackTrace();
		}
		finally {
			lock.unlock();
		}
	}
	
	public void signalAll_A() {
		lock.lock();
		System.out.println("signallAll_A的时间是" + System.currentTimeMillis() + "  Thread Name:" + Thread.currentThread().getName());
		conditionA.signalAll();
		lock.unlock();
	}
	
	public void signalAll_B() {
		lock.lock();
		System.out.println("signallAll_B的时间是" + System.currentTimeMillis() + "  Thread Name:" + Thread.currentThread().getName());
		conditionB.signalAll();
		lock.unlock();
	}

}

package ManyCondition;

public class ThreadA extends Thread{
	private MyService service = new MyService();
	public ThreadA(MyService service) {
		super();
		this.service = service;
	}
	
	public void run() {
		service.awaitA();
	}
}

package ManyCondition;

public class ThreadB extends Thread{
	private MyService service = new MyService();
	public ThreadB(MyService service) {
		super();
		this.service = service;
	}
	
	public void run() {
		service.awaitB();
	}
}

package ManyCondition;

public class Run {
	public static void main(String args[]) {
		MyService service = new MyService();
		ThreadA a = new ThreadA(service);
		a.setName("a");
		
		ThreadB b = new ThreadB(service);
		b.setName("b");
		b.start();
		a.start();
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		service.signalAll_A();
		 //service.signalAll_B();
		// 当上面这一行没有注释的时候,那么最后将B也会唤醒
	}
}

/**当前代码运行结果
 * begin awaitA的时间是1554988354851Thread Name:a
 * begin awaitB的时间是1554988354852  Thread Name:b
 * signallAll_A的时间是1554988355052  Thread Name:main
 * end  awaitA的时间是1554988355052Thread Name:a
 */


/**这是去掉service.signalAll_B的运行结果
* begin awaitB的时间是1554987438702  Thread Name:b
* begin awaitA的时间是1554987438703Thread Name:a
* signallAll_A的时间是1554987438903  Thread Name:main
* signallAll_B的时间是1554987438903  Thread Name:main
* end  awaitA的时间是1554987438903  Thread Name:b
* end  awaitB的时间是1554987438903Thread Name:a
**/

当使用ReenTrantLock 和 Condition的时候实现生产者/消费者模式:只需要在第一个代码中,将await放在while循环中,判断条件在生产者和消费者之间变化

公平锁和非公平锁:

公平锁表示线程获取锁的顺序是按照加锁的顺序来分配的FIFO先来先得的顺序,非公平锁随机获得锁。

关于一些方法

int getHoldCount()   查询当前线程保持此锁定的个数,也就是调用lock()的次数

int getQueueLength()   返回正等待获取此锁定线程的估计数

int getWaitQueueLength(Condition condition)   返回等待与此线程相关的给定条件Conditon的线程估计数,比如有5个线程,每个线程都执行了同一个condition对象的await()方法,则调用该方法返回的 值是 5 

boolean hasQueueThread(Thread thread)  查询指定线程是否正在等待获取此锁定

boolean hasQueueThreads()   查询是否有线程正在等待此锁定

Boolean hasWaiters(Condition condition)  查询是否有线程正在等待与此锁有关的condition条件

boolean isFair()  是否是公平锁

boolean isHeldByCurrentThread()   查询当前线程是否保持此锁定

boolean isLocked()  查询此锁是否由任意线程保持

void lockInterruptibly()  如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常

boolean tryLock()  仅在调用时锁定未被另一线程保持的情况下,才获取该锁定

boolean tryLock(long timeout, TimeUnit unit)  如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断则获取该锁定

awaitUnitterruptibly()  可以处理interrupt()的异常情况

使用ReentrantReadWriteLock类

类ReentrantLock类具有完全互斥排他的效果,即在同一时间内只有一个线程可以执行ReentrantLock.lock()后面的任务,虽然安全但是效率比较低。ReentrantReadWriteLock读写锁可以提高效率,读锁是共享锁 写锁是排他锁,读锁之间不互斥,读写和之间互斥,读锁之间互斥。读读是异步的,非互斥。

readLock()  : 使用  lock.readLock.lock() /  lock.readlock.unlock()

writeLock()   : 读锁

package ReentrantReadWriteLock;

import java.util.concurrent.locks.ReentrantReadWriteLock;

// 关于读写互斥
public class Service {
	private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); // 注意是锁都要声明为私有类型,防止可以外界可以获得此锁
	public void read() {
		lock.readLock().lock();
		System.out.println("获得读锁" + Thread.currentThread().getName() + "   Time  " + System.currentTimeMillis());
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		lock.readLock().unlock();
	}
	public void write() {
		lock.writeLock().lock();
		System.out.println("获得写锁" + Thread.currentThread().getName() + "   Time  " + System.currentTimeMillis());
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		lock.writeLock().unlock();
	}
}

package ReentrantReadWriteLock;

import Reentrantlock.MyService;

public class ThreadA extends Thread{
	private Service service;
	
	public ThreadA(Service service) {
		super();
		this.service = service;
	}
	
	public void run() {
		service.read();
	}
}

package ReentrantReadWriteLock;

import Reentrantlock.MyService;

public class ThreadB extends Thread{
	private Service service;
	
	public ThreadB(Service service) {
		super();
		this.service = service;
	}
	
	public void run() {
		service.write();
	}
}

package ReentrantReadWriteLock;

public class Run {
	public static void main(String args[]) {
		Service service = new Service();
		ThreadA a = new ThreadA(service);
		a.setName("a");
		a.start();
		ThreadB b = new ThreadB(service);
		b.setName("b");
		b.start();
	}
}

/* 结果
* 获得读锁a   Time  1554992187220
* 获得写锁b   Time  1554992190221
*/

 

五、定时器Timer

关于实现指定时间、指定周期来执行任务

Timer类的主要作用是设置计划任务,但封装计任务的类却是TimerTask类,该类是一个抽象类,

TimerTask是以队列的方式一个一个被顺序执行的,所执行的时间有可能和预期的时间不一致,那是因为前面的任务可能消耗了较长的时间,则后面的任务被延迟。

schedule(TimerTask task, Date time) 在指定的日期执行某一次任务。当我们的任务被设计为提前运行(时间点为之前的时间)的时候,其正常执行,不会出现错误,打印时间为当前时间。

package Run1;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class Run1 {
	private static Timer timer = new Timer();
    static public class MyTask extends TimerTask {
    	public void run() {
    		System.out.println("运行了! 时间是 :" + new Date());
    	}
	}
	public static void main(String[] args) {
		MyTask task = new MyTask();
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String dateString = "2019-04-12 18:55:00";
		Date DateRef = null;
		try {
			DateRef = sdf.parse(dateString);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("字符串时间:" + DateRef.toLocaleString() + " 当前时间: " + new Date().toLocaleString());
		timer.schedule(task, DateRef);
	}
}

/*
字符串时间:2019-4-12 18:55:00 当前时间: 2019-4-12 18:56:19
运行了! 时间是 :Fri Apr 12 18:56:19 CST 2019
*/

多个timerTask任务和延时运行,答应的时间仍然以运行时间为准,因为中间的线程占用20s时间

package Run1;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

// 多个timetask和延时 的情况
public class Run2 {
	static Timer timer = new Timer();
	static public class MyTask1 extends TimerTask{
		public void run() {
			System.out.println("111运行了:" + new Date());
			try {
				Thread.sleep(20000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	static public class MyTask2 extends TimerTask{
		public void run() {
			System.out.println("222运行了:" + new Date());
		}
	}
	
	public static void main(String[] args) {
		MyTask1 task1 = new MyTask1();
		MyTask2 task2 = new MyTask2();
		SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String date1 = "2019-04-12 21:10:00";
		String date2 = "2019-04-12 21:10:01";
		Date dateRef1 = null;
		Date dateRef2 = null;
		try {
			dateRef1 = sdf1.parse(date1);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		try {
			dateRef2 = sdf2.parse(date2);
		} catch (ParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("字符串1的时间的时间为" + dateRef1.toLocaleString() + "  当前时间:" + new Date());
		System.out.println("字符串2的时间的时间为" + dateRef2.toLocaleString() + "  当前时间:" + new Date());
		timer.schedule(task1, dateRef1);
		timer.schedule(task2, dateRef2);
	}
}

/*

字符串1的时间的时间为2019-4-12 21:10:00  当前时间:Fri Apr 12 21:09:43 CST 2019
字符串2的时间的时间为2019-4-12 21:10:01  当前时间:Fri Apr 12 21:09:43 CST 2019
111运行了:Fri Apr 12 21:10:00 CST 2019
222运行了:Fri Apr 12 21:10:20 CST 2019
*/

schedule(TimerTask task, Date firstTime, long period)  周期性的无线循环完成某一个任务,最后一个参数是隔所长时间循环一次

TimerTask 中有个cancle方法,这个方法用来将自身从任务中清除,而Timer类中的cancle()则是将任务队列中的任务全部清除,该方法卸载run()中。

方法:scheduleAtFixedRate(TimeTask task, Date firstTime, long period)

此方法和schedule方法都是按照顺序执行,故不需要考虑非线程安全问题

关于这两个方法的区别:

schedule: 延时:下次开始时间参考上次结束   非延时:下一次开始参考上一次开始时间  不具有追赶执行性

scheduleAtFixedRate: 延时与非延时:下次任务的执行时间参考上一次任务的结束时间。 具有追赶执行性

追赶执行:某个时间段在没有任务执行时,是否被补充性的执行了Task任务

 

六、单例模式和多线程

单例模式:确保一个类只有一个实例(也就是类的对象),并且提供一个全局的访问点(外部通过这个访问点来访问该类的唯一实例)。需要:1. 将类的构造函数声明为类的私有函数(private,那么在外部就无法实例化该类     2. 在类的里面声明一个公开的全局静态字段(全局访问点),外部对象通过此拿到拿到该类的唯一实例。 

public class SingleObject {
 
   //创建 SingleObject 的一个对象
   private static SingleObject instance = new SingleObject();
 
   //让构造函数为 private,这样该类就不会被实例化
   private SingleObject(){}
 
   //获取唯一可用的对象
   public static SingleObject getInstance(){
      return instance;
   }
 
   public void showMessage(){
      System.out.println("Hello World!");
   }
}

public class SingletonPatternDemo {
   public static void main(String[] args) {
 
      //不合法的构造函数
      //编译时错误:构造函数 SingleObject() 是不可见的
      //SingleObject object = new SingleObject();
 
      //获取唯一可用的对象
      SingleObject object = SingleObject.getInstance();
 
      //显示消息
      object.showMessage();
   }
}

优点:

  • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景:

  • 1、要求生产唯一序列号。
  • 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  • 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。

注意事项:getInstance() 方法中需要使用同步锁 synchronized (Singleton.class) 防止多线程同时进入造成 instance 被多次实例化。

立即加载/"饿汉模式"


public class MyObject {
	// 立即加载方式==饿汉模式
	private static MyObject myobject = new MyObject();  // 即时创建
	private MyObject() {}
	public static MyObject getInstance() {
		return myobject;
	}
}

public class MyThread extends Thread {
	public void run() {
		System.out.println(MyObject.getInstance().hashCode());
	}
}

public class Run {
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		MyThread t3 = new MyThread();
		t1.start();
		t2.start();
		t3.start();
	}
}

/**
*
* 1839450825
* 1839450825
* 1839450825
*
**/

 

延迟加载/“懒汉模式”

public class MyObject {
	// 延迟加载==懒汉模式
	private static MyObject myobject;
	private MyObject() {}
	public static MyObject getInstance() {
		if(myobject != null) {}
		else
		myobject = new MyObject();
		return myobject;
	}
}

/**
686587801
1026810021
379353320

一个线程可以,多线程就会发生线程安全问题,多个线程同时进入了get方法
**/

 

非线程安全问题的解决方案:

 可以通过加synchronized关键字实现

1. 直接对get方法添加synchronized关键字

2. 对方法内部的全部代码添加synchronized关键字

1同2 一样,效率很低,是同步运行的,下一个线程想取得对象,必须等上一个线程释放锁

3. 对关键的代码进行同步:注意使用双重加锁机制

package Danli;

public class MyObject {
	// 延迟加载==懒汉模式
	private static volatile MyObject myobject;
	private MyObject() {}
	public static MyObject getInstance() {
		if(myobject != null) {}
		else {
			synchronized(MyObject.class){  // 注意synchronized中写的是MyObject.class
				if(myobject == null)   // 判断两次,一次仍然有安全问题
				myobject = new MyObject();
			}
			
		}
		return myobject;
	}
}

public class MyThread extends Thread {
	public void run() {
		System.out.println(MyObject.getInstance().hashCode());
	}
}


public class Run {
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		MyThread t2 = new MyThread();
		MyThread t3 = new MyThread();
		t1.start();
		t2.start();
		t3.start();
	}
}


/**
**
1026810021
1026810021
1026810021


**/

此写法保证了,当多个进程进入第一个判断锁时,会被同步机制隔离,只有一个程序进入新建对象,再其他线程进入时,instance已经不为null,因此不会新建多个对象。这种方法就叫做双重检查锁,但是也有一个问题,就是java是实行无序写入的机制,最好再添加关键字volatile,防止指令重排。

关于new对象的步骤:

1.首先jvm要为这个对象实例分配内存空间
2.初始化这个对象
3.将myobject指向内存地址。。。。

所以如果先执行了 3 那么其地址不为空,2  再进行初始化,就会出现线程不安全问题

也可以使用静态内置类实现单例模式

 

第七章、拾遗增补

线程状态使用Thread.currentThread().getState()

JVM的根线程组就是System,再取其父线程组则出现空异常

类SimpleDateFormat 非线程安全: 这个类主要是负责日期的转换和格式化,但在多线程环境中,使用此类易造成数据转换及处理的不准确,因为此类并不是线程安全的 

方法setUncaughtExceptionHandler()的作用是对指定的线程对象设置默认的异常处理器。在Thread类中,还可以使用setDefaultUncaughtExceptionHandler()方法对所有线程设置异常处理器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值