java修炼之多线程学习心得
java是少数几种支持“多线程”的开发语言之一。大多数的程序语言只能遵循运行单独的一个程序块,无法同时运行多个不同的程序块。java的“多线程”恰可以弥补这个缺陷,它可以让同一进程中的不同线程一起运行起来,而不再像别的成语言只能进行单一的线程运行。如此一来就可以让程序运行起来更加的顺畅了,同时也可以达到多个任务同时被处理,更加充分利用计算机的CPU资源。
1. 进程与线程的区别
进程的特性:
(1)一个进程就是一个进行中的程序,而每个进程都有自己独立的内存空间,一组系统资源。在进程的概念中,每一个进程的内部数据和状态都是独立的。
(2)创建并执行进程对系统的运行资源开销都是很大的。
(3)进程是程序的一次进行过程,就是处于运行状态的程序,是系统运行的基本单位。
(4)一个进程可以有很多线程,每条线程并行执行不同的任务。
线程的特性:
(1)在java中,程序是通过流控制来执行程序流。程序中的单向流动的流控制我们称之为“线程”。
(2)线程是程序执行流的最小单元。
(3)同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。
(4)在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行。
2. 认识线程
线程的五种状态:
1、新建(new):就是继承了Thread线程类或者实现了Runnable接口的实现类被实例化出来的对象称为线程对象。
2、可运行:线程对象调用了start()方法(实现Runnable的实现类只能通过把对象以构成方法参数的形式间接调用start() 方法)之后的线程进入可运行等待队列等候CPU调度,此状态就为可运行态。
3、运行:在可运行等待队列的线程获得了CPU时间片(timeslice),执行程序代码。
4、阻塞:处于运行态的线程因为某种原因主动放弃CPU使用权,就是让出了CPU时间片(timeslice)。线程就会暂时停止运行。直到线程进入可运行态重新等待排队。(等待阻塞:运行的线程执行了wait() 方法之后,JVM会把该线程放入等待队列中)、(同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用着,则JVM就会把该线程锁池中等待再次进入可运行态)、(其他阻塞:处于运行态的线程执行了sleep() 方法或者join() 方法,或者发出了I/O请求时,JVM会把该线程设置为阻塞状态)
5、死亡:线程的run()、main() 方法执行结束,或者因异常 退出了run() 方法,则该线程结束生命周期。死亡的线程不可再次复生。
通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。因而近年来推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。
下面为java实现多线程的两种方式:
第一种:通过继承Thread类实现多线程
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
// TestThread t = new TestThread();
// t.setDaemon(true);
// t.start();//线程启动方法
// t.getName();//获取线程名称
// t.getPriority();//获取线程的优先级
// t.destroy();//线程销毁
// t.interrupt();//线程中断
// t.isAlive();//线程是否存活
// t.isDaemon();//线程置于后台
// t.join();//线程强制进行
TestThread t = new TestThread();
t.start();
for(int i=0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"测试多线程,run("+i+")在运行!");
}
}
class TestThread extends Thread {//继承多线程实现类
//run方法为多线程的执行入口
public void run() {
for(int i=0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"测试多线程,run("+i+")在运行!");
}
}
}
第二种:通过实现Runnable接口
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
// TestThread t = new TestThread();
// t.setDaemon(true);
// t.start();//线程启动方法
// t.getName();//获取线程名称
// t.getPriority();//获取线程的优先级
// t.destroy();//线程销毁
// t.interrupt();//线程中断
// t.isAlive();//线程是否存活
// t.isDaemon();//线程置于后台
// t.join();//线程强制进行
TestRunable t = new TestRunable ();
new Thread(t).start();//通过Runable的实现类Thread的start方法开启多线程
for(int i=0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"测试多线程,run("+i+")在运行!");
}
}
class TestRunable implements Runnable {//实现多线程接口
public void run() {
for(int i=0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"测试多线程,runnable()在运行!");
}
}
}
Thread多线程类是Runnable多线程接口的实现类,因为Runnable接口没有提供start()多线程启动方法,所以只能通过其实现类Thread的start启动多线程操作。由于java只能支持单继承,所以如果需要继承Thread的类还有别的父类的时候,Runnable就是提供了一个很好的选择。
3. 把线程置于后台运行
对java程序来说,只要还有一个线程处于运行状态,那么这个进程就会一直占用CPU的资源不会被撤下来。如果一个进程中的所有线程都在后台运行那么这个进程就会被结束掉,从CPU中撤下来。我们可以通过setDaemon(Boolean)(是否置于后台运行)方法设置线程在前后台的转换。注意这个方法只能在start()方法执行前设置才起作用。
下面的代码实现:
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
// TestThread t = new TestThread();
// t.setDaemon(true);
// t.start();//线程启动方法
// t.getName();//获取线程名称
// t.getPriority();//获取线程的优先级
// t.destroy();//线程销毁
// t.interrupt();//线程中断
// t.isAlive();//线程是否存活
// t.isDaemon();//线程置于后台
// t.join();//线程强制进行
TestThread t = new TestThread();
t.setDaemon(true);
t.start();
}
class TestThread extends Thread {//继承多线程实现类
//run方法为多线程的执行入口
public void run() {
for(int i=0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"测试多线程,run("+i+")在运行!");
}
}
}
4. 线程的强制运行
就是说进行了线程的强制运行方法join()的线程会被强制优先执行完再去执行别的线程。
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
// TestThread t = new TestThread();
// t.setDaemon(true);
// t.start();//线程启动方法
// t.getName();//获取线程名称
// t.getPriority();//获取线程的优先级
// t.destroy();//线程销毁
// t.interrupt();//线程中断
// t.isAlive();//线程是否存活
// t.isDaemon();//线程置于后台
// t.join();//线程强制进行
TestThread t = new TestThread();
t.start();
t.join();//强制性优先执行
for(int i=0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"测试多线程,run("+i+")在运行!");
}
}
class TestThread extends Thread {//继承多线程实现类
//run方法为多线程的执行入口
public void run() {
for(int i=0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"测试多线程,run("+i+")在运行!");
}
}
}
5. 线程的休眠
设置线程运行时的休眠时候方便别的线程运行
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
// TestThread t = new TestThread();
// t.setDaemon(true);
// t.start();//线程启动方法
// t.getName();//获取线程名称
// t.getPriority();//获取线程的优先级
// t.destroy();//线程销毁
// t.interrupt();//线程中断
// t.isAlive();//线程是否存活
// t.isDaemon();//线程置于后台
// t.join();//线程强制进行
TestThread t = new TestThread();
t.start();
t.join();//强制性优先执行
for(int i=0; i < 10; i++) {
Thread.sleep(100);//用来设置线程的休眠时间
System.out.println(Thread.currentThread().getName()+"测试多线程,run("+i+")在运行!");
}
}
class TestThread extends Thread {//继承多线程实现类
//run方法为多线程的执行入口
public void run() {
for(int i=0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"测试多线程,run("+i+")在运行!");
}
}
}
6. 线程的中断
中断正在运行当中的线程操作
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
// TestThread t = new TestThread();
// t.setDaemon(true);
// t.start();//线程启动方法
// t.getName();//获取线程名称
// t.getPriority();//获取线程的优先级
// t.destroy();//线程销毁
// t.interrupt();//线程中断
// t.isAlive();//线程是否存活
// t.isDaemon();//线程置于后台
// t.join();//线程强制进行
Thread t = Thread.currentThread();
System.out.println(t.getName()+"线程被中断了=" + t.isInterrupted());
t.interrupt();
System.out.println(t.getName()+"线程被中断了=" + t.isInterrupted());
System.out.println(t.getName()+"线程被中断了=" + t.isInterrupted());
System.out.println(t.getName()+"线程被中断了=" + t.isInterrupted());
try {
t.interrupt();
t.sleep(1000);
} catch (Exception e) {
System.out.println(t.getName()+"线程被中断了!");
}
}
}
7. 多线程的同步
同步线程机制: 保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。在多线程的程序中只要被 “synchronized” 关键字标记的方法或者代码块均为同步线程
同步的方法
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
// TestThread t = new TestThread();
// t.setDaemon(true);
// t.start();//线程启动方法
// t.getName();//获取线程名称
// t.getPriority();//获取线程的优先级
// t.destroy();//线程销毁
// t.interrupt();//线程中断
// t.isAlive();//线程是否存活
// t.isDaemon();//线程置于后台
// t.join();//线程强制进行
//线程1运行
TestThread t = new TestThread();
t.start();
//线程休眠
Thread.sleep(500);
//main主线程运行
t.loop();
}
public synchronized void loop() {
System.out.println(Thread.currentThread().getName()+"----->刚刚进入loop方法!");
for(int i=0; i < 10; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"测试方法!");
}
System.out.println(Thread.currentThread().getName()+"----->刚刚离开loop方法!");
}
public void run() {
loop();
}
}
同步的代码块
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
// TestThread t = new TestThread();
// t.setDaemon(true);
// t.start();//线程启动方法
// t.getName();//获取线程名称
// t.getPriority();//获取线程的优先级
// t.destroy();//线程销毁
// t.interrupt();//线程中断
// t.isAlive();//线程是否存活
// t.isDaemon();//线程置于后台
// t.join();//线程强制进行
//线程1运行
TestThread t = new TestThread();
t.start();
//线程休眠
Thread.sleep(500);
//main主线程运行
t.loop();
}
public synchronized void loop() {
synchronized(this) {
System.out.println(Thread.currentThread().getName()+"----->刚刚进入loop方法!");
for(int i=0; i < 10; i++) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"测试方法!");
}
System.out.println(Thread.currentThread().getName()+"----->刚刚离开loop方法!");
}
}
public void run() {
loop();
}
}
8. 线程的死锁
死锁的概念: A线程中有一个锁的触发是需要B线程的锁执行,而B线程的锁执行也必须要A线程的锁执行,就这样线程无限期地等待即为死锁。就是说如果一个正在运行中的A线程里面有一个锁的触发条件是必须要执行B线程的锁,但是要执行B线程中的锁恰好也需要A线程的锁执行,这样形成了交叉联系无法分离的状态。
产生死锁的四个条件:(缺一不可)
1、互斥条件:在线程获取系统资源的时候具有排他性的权利,就是说该轮到它拿资源别的线程不能阻止。
2、不被剥夺:线程对已经拿到手的系统资源,别的线程不能对其资源强行拿走。只能由其自己释放资源。
3、请求和保持的权利:就是说线程已经拿到了一部分系统的资源,但是它还是可以继续想系统申请资源的权利。
4、闭环:进程中一个线程的部分资源都是等待队列中后一个线程运行所必须的资源,而它又不释放资源,这样形成的无限期等待。
class A {
synchronized void funA(B b) {
String name = Thread.currentThread().getName();
System.out.println("进入了" + name + "线程中!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程:" + name + "需要调用B类中的last方法");
b.last();
}
synchronized void last() {
System.out.println("进入了A类的last方法!");
}
}
class B {
String name = Thread.currentThread().getName();
synchronized void funB(A a) {
System.out.println("进入了" + name + "线程中!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("线程:" + name + "需要A类中的last方法");
a.last();
}
synchronized void last() {
System.out.println("进入了B类的last方法!");
}
}
class TestRunable implements Runnable {//实现多线程接口
A a = new A();
B b = new B();
TestRunable() {
Thread.currentThread().setName("Thread_B");
new Thread(this).start();
a.funA(b);
System.out.println(b.name + "---线程执行完毕!");
}
public void run() {
Thread.currentThread().setName("Thread_B");
b.funB(a);
System.out.println(b.name + "---线程执行完毕!");
}
public static void main(String[] args) {
new TestRunable();
}
}
9. 线程间的通信
我可以通过一个问题来理解线程间的通信问题。把一个person对象数据的存储空间划分为两个部分:一个部分用来存储person的name属性
一部分存储person的sex属性。在这里我们设置两个线程,一个是生产者producter(对name和sex属性赋值操作),一个是消费者customer(对name和sex属性取值操作)。这个程序到这里如果按照多线程的执行会产生两种意外问题,需要我们考虑。
第一种:假设生产者线程向数据空间执行完person1的name属性数据存储,还没来得及执行sex属性数据的存储,CPU就被切换到消费者线程上了,这时person1的sex属性的数据并没有被赋值,消费者就会把上一个person的sex属性数据赋值给person1的sex属性。这样就会造成数据混乱。
第二种:生产者放入了若干次数据,消费者才开始取数据,或者是,消费者取完一个数据后,还没来得及等待生产者放入数据,就又重复执行了取数据的操作。
class Person {
String name = null;
String sex = null;
}
class Producter implements Runnable {
Person p = null;
Producter(Person p) {
this.p = p;
}
@Override
public void run() {
int i = 0;
while(true) {
if(i == 0) {
p.name = "王五";
p.sex = "女";
} else {
p.name = "赵六";
p.sex = "男";
}
i=(i+1)%2;
}
}
}
class Customer implements Runnable {
Person p = null;
Customer(Person p){
this.p = p;
}
@Override
public void run() {
for(int i=0;i<20; i++) {
System.out.println(p.name + "----" + p.sex);
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
// TestThread t = new TestThread();
// t.setDaemon(true);
// t.start();//线程启动方法
// t.getName();//获取线程名称
// t.getPriority();//获取线程的优先级
// t.destroy();//线程销毁
// t.interrupt();//线程中断
// t.isAlive();//线程是否存活
// t.isDaemon();//线程置于后台
// t.join();//线程强制进行
Person p = new Person();
new Thread(new Producter(p)).start();
new Thread(new Customer(p)).start();
}
}
从上面的输出结果可以看得到在多线程的操作中极其容易出现上面所述的结果,当生产者Producter和消费者Customer同时操纵一个Person对象的时候就会出现,生产者Producter还没有对Person完成操作,消费者Customer就已经把Person类中的内容取走了,所以就出现了原本应该是王五的sex属性值应该是女,而赵六的sex属性值应该是男但是现在已经出现了数据上的混乱。就是资源不同步的原因产生的。
可以在Person类中设置 set()/get() 方法来解决这类问题。
class Producter implements Runnable {//生产者
Person p = null;
Producter(Person p) {
this.p = p;
}
@Override
public void run() {
int i = 0;
while(true) {
if(i == 0) {
p.set("王五", "女");
} else {
p.set("赵六", "男");
}
i=(i+1)%2;
}
}
}
class Customer implements Runnable {//消费者
Person p = null;
Customer(Person p){
this.p = p;
}
@Override
public void run() {
for(int i=0;i<20; i++) {
p.get();
}
}
}
class Person {//被操纵的数据类
private String name = null;
private String sex = null;
synchronized void set(String name, String sex) {
this.name = name;
this.sex = sex;
}
synchronized void get() {
System.out.println(this.name + "----" + this.sex);
}
}
public class ThreadDemo {//测试类
public static void main(String[] args) throws InterruptedException {//程序入口
// TestThread t = new TestThread();
// t.setDaemon(true);
// t.start();//线程启动方法
// t.getName();//获取线程名称
// t.getPriority();//获取线程的优先级
// t.destroy();//线程销毁
// t.interrupt();//线程中断
// t.isAlive();//线程是否存活
// t.isDaemon();//线程置于后台
// t.join();//线程强制进行
Person p = new Person();
new Thread(new Producter(p)).start();
new Thread(new Customer(p)).start();
}
}
如图所示person中属性的数据没有在发生混乱了,但是这样又出现了新的问题,那就是Producter生产者存入的一次数据有可能会被Customer消费者连续去出多次,这就会造成数据的冗余。而我们应该想一下能不能有一种这样的机制,就是每当Producter生产者存入一次数据,Customer消费者就来取一次数据。这种约定式的生产者、消费者关系就是接下来一个章节要讲解的————线程间的通信
java 多线程API提供给我们三个方法:
wait():告诉当前线程放弃监控器并进入睡眠状态,直到其他线程进入同一个监控器并且调用notify() 通知它该醒了为止。
notify():唤醒同一对象监控器中调用了wait() 方法的第一个线程。这就像饭堂排队取餐,只有接到窗口的报自己的餐号通知才能取到饭。
notifyAll():唤醒同一个对象监听器中调用了wait() 方法的所有线程,具有最高优先级的线程会优先被唤醒并执行。
注意:这3个方法只有在被 synchronized 标识的方法或代码块中才有效,即无论线程调用一个对象的 wait() 还是 notify() 方法。该线程必须先得到该对象的锁标识。所以同一个对象的监控器下的notify() 方法只能唤醒执行了wait() 方法的线程。而是用多个对象监控器,就可以分为有多个notify() 和 wait() 的情况,同组里执行了wait() 的线程只能被同组个notify() 方法唤醒重新进入锁池中等待获取锁标记。
如果想要实现上面的设计需求,就需要要在Person类中设置一个判别标识flag(Boolean)初始值为false表示生产者Producter还未给Person类赋值,当生产者Producter线程调用时必须先判断flag标识是否为false(表示Person的数据已被Customer取走)并且把flag的值设置为true,而消费者Customer线程的调用时必须判断flag标识是否为true(表示Person的数据已被Producter赋值)并且把flag的值设置为false。有了这样的规则之后生产者Producter与消费者Customer直接调用Person就可以对接起来了,不会造成之前的一次数据被多次取用的现象。
class Person {
Boolean flag = false;
private String name = null;
private String sex = null;
public Boolean getFlag() {
return flag;
}
public void setFlag(Boolean flag) {
this.flag = flag;
}
synchronized void set(String name, String sex) {
if(this.getFlag()) {
try {
wait();//设置后面排队的线程要等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name = name;
this.sex = sex;
this.setFlag(true);
notify();//通知后面等待的线程可以运行了
}
synchronized void get() {
if(!this.getFlag()) {
try {
wait();//设置后面排队的线程要等待
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(this.name + "----" + this.sex);
this.setFlag(false);
notify();//通知后面等待的线程可以运行了
}
}
class Producter implements Runnable {
Person p = null;
Producter(Person p) {
this.p = p;
}
@Override
public void run() {
int i = 0;
while(true) {
if(i == 0) {
p.set("王五", "女");
} else {
p.set("赵六", "男");
}
i=(i+1)%2;
}
}
}
class Customer implements Runnable {
Person p = null;
Customer(Person p){
this.p = p;
}
@Override
public void run() {
while(true) {
p.get();
}
}
}
public class ThreadDemo {//测试类
public static void main(String[] args) throws InterruptedException {//程序入口
// TestThread t = new TestThread();
// t.setDaemon(true);
// t.start();//线程启动方法
// t.getName();//获取线程名称
// t.getPriority();//获取线程的优先级
// t.destroy();//线程销毁
// t.interrupt();//线程中断
// t.isAlive();//线程是否存活
// t.isDaemon();//线程置于后台
// t.join();//线程强制进行
Person p = new Person();
new Thread(new Producter(p)).start();
new Thread(new Customer(p)).start();
}
}
好了到这里我的java多线程学习就结束了,希望能对大家有帮助。