Thread是线程,要学习线程,必须先知道进程。
百度进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
我理解的进程:进程是电脑或者手机上运行的一个应用。
百度线程:线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
我理解的线程:一个应用程序中的执行路径,或者说是应用中的某一项任务。
二、主要内容
1、Thread类的定义有两种方式
继承Thread类
public class MyThread extends Thread {
@Override
public void run() {
super.run();
}
}
实现Runnable接口
public class MyRunnable implements Runnable {
@Override
public void run() {
}
}
2、自定义线程不共享数据与共享数据。
不共享数据
给线程定义一个成员变量并初始化,每次创建线程对象,该成员变量都会被初始化,在每个线程中,都是各自的成员变量。
public class MyThread extends Thread {
private int count = 5;
public MyThread(String name){
super();
this.setName(name);
}
@Override
public void run() {
super.run();
while(count>0){
count--;
System.out.println("由"+this.getName()+"该线程计算的count的结果是:"+count);
}
}
}
测试代码
public static void main(String[] args) {
MyThread t1 = new MyThread("A");
MyThread t2 = new MyThread("B");
MyThread t3 = new MyThread("C");
MyThread t4 = new MyThread("D");
MyThread t5 = new MyThread("E");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
测试结果
由A该线程计算的count的结果是:4
由B该线程计算的count的结果是:4
由B该线程计算的count的结果是:3
由B该线程计算的count的结果是:2
由B该线程计算的count的结果是:1
由A该线程计算的count的结果是:3
由B该线程计算的count的结果是:0
由A该线程计算的count的结果是:2
由A该线程计算的count的结果是:1
由A该线程计算的count的结果是:0
由C该线程计算的count的结果是:4
由C该线程计算的count的结果是:3
由C该线程计算的count的结果是:2
由E该线程计算的count的结果是:4
由C该线程计算的count的结果是:1
由D该线程计算的count的结果是:4
由D该线程计算的count的结果是:3
由D该线程计算的count的结果是:2
由E该线程计算的count的结果是:3
由D该线程计算的count的结果是:1
由C该线程计算的count的结果是:0
由D该线程计算的count的结果是:0
由E该线程计算的count的结果是:2
由E该线程计算的count的结果是:1
由E该线程计算的count的结果是:0
共享数据
给该成员变量加上static的修饰符,则创建的线程都会共用该成员变量,从而达到共享数据的效果 ,但在多线程时,会出现并发的问题(不同的线程在不同的时间访问到的数据是相同)。
public class MyThreadTwo extends Thread {
private static int count = 5;
public MyThreadTwo(String name){
super();
this.setName(name);
}
@Override
public void run() {
super.run();
count--;
System.out.println("由"+Thread.currentThread().getName()+"线程计算count的结果是:"+count);
}
}
测试结果
由B线程计算count的结果是:3
由C线程计算count的结果是:2
由A线程计算count的结果是:3
由E线程计算count的结果是:1
由D线程计算count的结果是:0
还有一种共享数据的方法,在创建线程对象时,使用下面的构造方法
MyThreadOne mThread = new MyThreadOne();
Thread t1 = new Thread(mThread,"A");
Thread t2 = new Thread(mThread,"B");
Thread t3 = new Thread(mThread,"C");
Thread t4 = new Thread(mThread,"D");
Thread t5 = new Thread(mThread,"E");
MyThreadOne的代码
public class MyThreadOne extends Thread{
private int count = 5;
@Override
public void run() {
super.run();
count--;
System.out.println("由"+Thread.currentThread().getName()+"线程计算count的结果是:"+count);
}
}
测试结果
由A线程计算count的结果是:4
由B线程计算count的结果是:2
由C线程计算count的结果是:2
由D线程计算count的结果是:0
由E线程计算count的结果是:1
同样会出现并发问题
解决多线程并发问题
使用synchronized关键字加锁
使用第一种方式共享数据,解决并发问题,需要使用同步代码块,这里要锁的对象必须是唯一的。
synchronized (MyThreadTwo.class) {
count--;
System.out.println("由" + Thread.currentThread().getName() + "线程计算count的结果是:" + count);
}
测试结果
由A线程计算count的结果是:4
由E线程计算count的结果是:3
由D线程计算count的结果是:2
由B线程计算count的结果是:1
由C线程计算count的结果是:0
使用第二种方式共享数,解决并发问题,可以在run方法前面加上synchronized关键字进行加锁操作。
@Override
synchronized public void run() {
super.run();
count--;
System.out.println("由"+Thread.currentThread().getName()+"线程计算count的结果是:"+count);
}
3、线程的几个常用的方法
3.1、静态方法currentThread()。
获取当前程序所在的线程对象。
3.2、isAlive()
判断线程是否是活跃状态。
活动状态:已经启动且尚未结束。
3.3、静态方法sleep(long time).
让当前运行的线程休眠time毫秒
3.4、getId()
获取线程的唯一标识。
4、停止线程
4.1、停止线程的几种方式
1、线程正常停止,即run()方法中的程序执行完毕。
2、使用stop()方法,强制线程停止,但不安全,不推荐使用。
使用stop()停止线程的弊端:
(1)、可能使一些释放资源的 工作得不到完成 。
(2)、对锁的对象解锁,导致数据得不到同步的处理。
3、使用interrupt()方法中断线程。
当使用该方法停止线程时,如果进入了停止状态,可以抛一个InterruptedException异常,达到停止线程的目的。
当线程在休眠状态时,调用该方法,会进入catch语句,并会清除停止状态。
当线程在运行状态时,调用该方法,是不会进入catch语句。
4.2、判断线程是否是停止状态
interrupted()线程的静态方法,测试当前线程是否已经中断,线程的中断状态由该方法清除。
isInterrupted(),测试线程是否已经中断,不清除中断状态。
8、守护线程
8.1、调用线程的serDecmon(boolean b)方法,可以设置该线程是不是守护线程。
8.2、当进行中不存在非守护线程时,守护线程也将销毁。
9、给方法加关键字synchronized同步
9.1、方法中的私有变量是不存在线程安全问题
9.2、多个线程访问同一对象的成员变量才会出现线程安全问题
9.3、在方法加锁,锁的是对象,所以不同线程访问不同对象的成员变量,仍然是异步。
9.4、A线程已经持有object对象的锁了,B线程可以调用object对象中的非同步方法。
9.5、当线程已经持有object对象的锁了,可以继续调用该对象中的其他同步方法。
9.6、当一个线程持有object对象的锁时,另外一线程想访问该对象的另外一个同步方法时,必须等待第一个线程释放锁之后,才能访问。
9.7、当线程已经持有object对象的锁时,再访问其他该对象的同步方法时,是不需要等待该对象释放锁的。
9.8、当一个线程执行代码发生异常时,它所持有的锁也会释放掉。
9.9、同步不具有继承性的,如果子类需要同步,必须自己加上synchronized关键字。
10、使用关键字synchronized同步代码块
10.1、同步方法的缺点:当方法有耗时操作时,其它线程必须等待当前线程执行完毕之后释放锁之后才能执行,比较浪费时间。
10.2、在synchronized(){}内的代码同步,其它的就不同步。
10.3、使用同一对象的锁,它们之间是同步的。
10.4、使用synchronized(this)时,锁对象是当前对象。
10.5、锁对象不同,代码块之间是异步的。
10.6、同步代码块在非同步方法中调用,并不能保证调用非同步方法的线程同步。
10.7、当关键字synchronized加在静态方法上的时候,锁对象是该类所对应的Class类。
10.8、使用synchronized(MyObject.class){}同步代码块,与使用关键字synchronized同步静态方法是一样的。
10.9、尽量避免使用String的对象加锁,因为String有个常量池功能,String str1="A",String str2 = "A"。str1与str2是同一对象。
10.10、互相等待对方释放锁,就有可能出现死锁情况。
11、关键字volatile
11.1、关键字volatile主要作用是使变量在多线程之间可见。没用关键字volatile修饰之前,变量是在私有堆栈与公有堆栈中,但是它们之间是不同步的;使用了关键字volatile修饰,程序会强制从公有堆栈中取值。
11.2、使用关键字volatile的缺点是:不支持原子性。
11.3、线程安全包含原子性和可见性。
11.4、使用原子类进行原则操作,AtomicInteger、AtomicLong...
12、线程间通信。
12.1、等待通知机制,wait()方法与notify()方法只能在同步方法或同步代码块中调用,wait()方法调用之后会立即释放锁,notify()方法调用之后不会立即释放锁 。
12.2、线程的生命周期,就绪状态、可运行状态、运行状态、暂停状态、销毁状态;就绪状态:线程创建之后进入就绪状态;可运行状态:线程调用了start()方法,但未抢到CPU资源进入可运行状态;运行状态:线程在可运行状态下,抢到了CPU资源进入运行状态;暂停状态:线程在运行状态下,调用了suspend()/sleep()/wait()方法进入暂停状态;销毁状态:当线程运行完run()方法中的代码后进入销毁状态。
12.3、锁对象有两个队列,就绪队列与阻塞队列;线程调用了wait()方法,进入阻塞队列,线程被唤醒后,进入就绪队列;就绪队列中存放了将要获得锁的线程,阻塞队列存放了被阻塞的线程。
12.4、唤醒所有线程notifyAll();等待N毫秒之后自动唤醒方法wait(long N),当然也可以在这之前调用notify()或notifyAll()方法唤醒。
12.5、wait()条件发生变化时,应该将if判断改为while判断,再判断一次,看条件是否发生变化。
12.6、多个生产者或者多个消费者,出现“假死”状态(不停的唤醒同类),将notity()方法改为notifyAll()。
12.7、可以通过管道实现线程间通信;
字节流:
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream();
通过pis.connect(pos)或者pos.connect(pis)连接通道;
字符流:
PipedReader pr = new PipedReader();
PipedWriter pw = new PipedWriter();
连接pr.connect(pw)或者pw.connect(pr);
将这两个流分别传入两个线程中,一个用来读,一个用来写,从而实现线程间通信。
13、线程加入join()
13.1、使线程正常执行run里面的代码,无限阻塞当前线程知道run方法执行完毕。
13.2、有个重载的join(long time)方法,阻塞时间有限制,如果到了阻塞时间且线程还未运行完,会执行后边的代码。
13.3、方法join(long time)与sleep(long time)的区别,join(long time)内部使用的是wait(long time),所以join(long time)方法释放锁,而sleep(long time)不释放锁。
14、类ThreadLocal
14.1、与线程绑定,存储绑定线程的私有值。
14.2、具有线程变量的隔离性。
14.3、第一次使用该类对象的get方法获取值返回都为null。
14.4、解决第一次get总为null问题,自定义一个类继承ThreadLocal,重写initialValue(),返回一个任意对象,则它就为默认获取到的对象。
15、类InheritableThreadLocal
15.1、可以在子线程中继承父线程中的值。
15.2、继承父线程中的值且修改。继承同14.4,修改重写childValue()方法,返回parentValue+要添加的值。
16、类Lock
16.1、使用方式:
创建对象 Lock lock = new ReentrantLock();
上锁 lock.lock();
同步代码块;
释放锁 lock.unlock();
16.2、类Condition,等待与唤醒
使用Condition让线程等待
创建Lock对象 Lock lock = new ReentrantLock();
创建Condition对象 Condition condition = new Condition();
上锁 lock.lock();
同步代码块;
等待 condition.await();
同步代码块;
释放锁 lock.unlock();
使用Condition唤醒正在等待的线程。将上边的代码中的
等待 condition.await(); 替换为
唤醒 condition.signal();
与Object的方法进行对比
Object类的wait()方法相当于Condition类的await()方法;
Object类的wait(long t)方法相当于Condition类的await(long t)方法;
Object类的notify()方法相当于Condition类的signal()方法;
Object类的notifyAll()方法相当于Condition类的signalAll()方法;
16.3、公平锁与非公平锁
构造方法 ReentrantLock(boolean fair),参数就是表示是否是公平锁。
公平锁:谁先运行谁先获得锁
非公平锁:随机获得锁
16.4、ReentrantLock几个常用的方法
getHoldCount():查询当前线程持有该锁的个数;
getQueueLength():获取等待该锁的个数;
getWaitQueueLenght():获取等待唤醒线程的个数;
hasQueueThread():查询指定线程是否等待获取该锁;
hasQueueThreads():查询是否有线程等待获取该锁;
hasWaiters():查询是否有线程等待唤醒;
isFair():判断是不是公平锁;
isHeldByThread():查询当前线程是否持有此锁;
isLocked():查询此锁是否被任意线程锁持有;
lockInterruptibly():如果当前线程未被中断,则获取该锁,如果中断则抛出异常。
tryLock():在没有线程持有该锁的情况下,获得该锁。
tryLock(long timeout,TimeUnit tu):如果在给定时间内没有被其它线程持有该锁且没有被中断,则获取该锁;参1:时长、参2:单位(枚举值)。
16.5、Condition常用的几个方法
awaitUninterruptibly():在不管什么情况下,让线程在接收到唤醒之前一直处于等待状态。
awaitUntial(Date date):在时间到来之时,自动唤醒,也可以在时间到来之前,主动唤醒。
16.6、类ReenrantReadWriteLock(读写锁)
读写锁有两个,一个是读锁也可叫共享锁,一个是写锁也叫排他锁。
读锁的使用:
创建对象 ReenrantReadWriteLock lock = ReenrantReadWriteLock();
获取锁 lock.readLock.lock();
要锁的代码块
释放读锁 lock.readLock.unlock();
读锁与读锁之间是不互斥的。
写锁的使用:
创建对象 ReenrantReadWriteLock lock = ReenrantReadWriteLock();
获取锁 lock.writeLock.lock();
要锁的代码块
释放读锁 lock.writeLock.unlock();
写锁与写锁之间是互斥的。
读锁与写锁之间是互斥的。