1.线程和进程
什么是线程和进程
进程就是一个应用程序,线程是一个进程中的执行场景/执行单元。
一个进程可以有多个线程。
对于java来说,在DOC命令下输入java HelloWorld并按下回车时,会启动一个JVM,这就是一个进程,JVM会启动一个主线程调用main方法,还会调用一个回收垃圾的线程,此时java程序中至少有两个线程并发。
进程A和进程B的资源不共享,但同一个进程中的线程C和线程D有共享的资源,也有各自的资源。
在内存中,不同的线程共用一份方法区和堆内存,而不同的线程会开辟不同的栈空间,事实上在内存中栈内存不仅仅有一份。
假设程序中有10个线程,就会有10个栈内存空间,每个进程之间互不干扰,每个栈内存之间互不干扰,比如main方法结束了,但程序还有可能没有结束,因为main方法结束只是意味着主栈空了,其他的栈还在压栈和弹栈,其他的进程还未结束。这就是并发机制,java中之所以有并发机制,就是为了提高程序的处理效率。
关于CPU
实际上单核CPU的设备并不能做到真正的多线程,我们之所以认为很多应用程序在同时进行,是因为CPU处理的速度极快,多个进程之间频繁切换。
2.创建线程
创建线程的第一种方式:继承Threah类
public class demo {public static void main(String[] args) {myThread t1 = new myThread();// 直接调用run方法,等价于执行一个对象中的普通方法,不会创建一个新的线程t1.run();// 调用start方法,作用是创建一个新的栈内存,然后该方法结束// 调用start方法之后,会在另一个线程中自动调用线程对象的run方法// 而且run方法会在支栈的底部,就像main方法在主栈的底部一样。t1.start();// 注意:一定是执行了start方法之后才会执行main方法中之后的代码、,因为start方法在主栈中//...}}class myThread extends Thread{@Overridepublic void run() {// 在这里写新的线程需要执行的代码}}
创建线程的第二种方法:实现Runnable方法
public class demo {public static void main(String[] args) {myThread t = new myThread();// 注意,线程的创建一定是由Thread类实例化产生的Thread t1 = new Thread(t);}}// 这还不是一个线程类,实例化它的对象也不能算是创建一个线程class myThread implements Runnable{@Overridepublic void run() {// 在这里写新的线程需要执行的代码}}
同时我们可以使用匿名内部类的方式用第二种方式创建线程
Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {//...}});
要注意:我们更应该使用第二种方法创建线程,因为我们定义的实现接口的类可能还需要继承其他的类。
3.线程的五个状态
1.新建状态
2.死亡状态
3.就绪状态
4.运行状态
5.阻塞状态

4.线程中的几个方法
1.setName方法,修改线程的名字。
2.getName方法,获取线程的名字。
3.currentThread方法,获取当前的线程,这是一个静态的方法,返回值是Thread类型的。
public class demo {public static void main(String[] args) {myThread t = new myThread();// setName方法t.setName("线程x");t.start();}}class myThread extends Thread{@Overridepublic void run() {for(int i=0 ; i<100 ; i++){// currentThread方法和getName方法System.out.println(Thread.currentThread().getName()+"--->"+i);}}}
currentThread方法的一个说明
public class demo {public static void main(String[] args) {// 多态Thread t = new myThread();// 看看用对象名调用currentThread是什么结果String s = t.currentThread().getName();// 输出main// 意味着currentThread方法和对象没有关系System.out.println(s);}}class myThread extends Thread{@Overridepublic void run() {for(int i=0 ; i<100 ; i++){// currentThread方法和getName方法System.out.println(Thread.currentThread().getName()+"--->"+i);}}}
4.sleep方法,用于让正在进行的线程进入阻塞状态,放弃占有的cpu时间片,让给其他线程使用。这是一个静态方法,参数是毫秒数。
public class demo {public static void main(String[] args) {Thread t = new myThread();t.start();}}class myThread extends Thread{@Overridepublic void run() {for(int i=0 ; i<5 ; i++){// currentThread方法和getName方法System.out.println(Thread.currentThread().getName()+"--->"+i);// 注意sleep要处理异常,在而且只能用try catch,不能throws// 因为Thread类没有抛出异常,子类不能比父类有更多更广的异常try {// 睡眠1秒Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
注意,在main函数中如果写sleep线程,就算使用对象调用的形式,也不能让支线程休眠,只能让main函数休眠,因为sleep是静态方法。
5.interrupt方法,这个方法用于中断一个线程的睡眠,这是一个非静态方法。可以main函数里中断另一个线程的休眠。
注意:interrupt方法不是让线程进入运行状态,而是让线程进入就绪状态,被JVM调配。
public class demo {public static void main(String[] args) {Thread t = new myThread();t.start();//...// 希望在前面的代码结束之后让t线程醒过来,把cpu时间片让给tt.interrupt();// 这种中断睡眠的方法依赖了java的异常处理机制// 实际上会执行catch中的代码}}class myThread extends Thread{@Overridepublic void run() {for(int i=0 ; i<5 ; i++){try {// 睡眠1秒System.out.println("t线程正在执行");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}
中断线程的一种方法:用一个属性就行了
public class demo {public static void main(String[] args) {myThread t = new myThread();t.start();try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}// 我想终止线程只需要把对象中的flat改成flase就可以了t.flat = false;}}class myThread extends Thread {// 在我的类中定义一个boolean类型的属性boolean flat = true;@Overridepublic void run() {for (int i = 0; i < 10; i++) {// 注意这个程序中判断要在for里,把判断放在for外没有意义,程序照常执行。// 如果flat为真时,便让run方法继续执行if (flat) {try {// 睡眠1秒System.out.println("t线程正在执行");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}} else {System.out.println("线程终止!");return;}}}}
5.关于线程调度问题
在开发中有两种线程调度模型
1.抢占式调度模型:哪个线程的有限度高,抢到的CPU时间片的概率就高一些/多一些,java采用的就是抢占式调度模型。
2.均分式调度模型:平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样,平均分配,一切平等,有一些编程语言的线程调度模型采用的就是这种方法。
java中提供了关于线程调度的一些方法
void setPriority (int newPriority),设置线程的优先级
int getPriority(),获取线程的优先级
最低优先级1,默认优先级5,最高优先级10
优先级比较高的获取CPU时间篇可能会多一些,但不完全是。
statuc void yield(),一个静态方法,让位方法,暂停当前正在执行的线程对象,并执行其他线程,yield方法的执行会让当前线程从“运行状态”回到“就绪状态”。
6.线程安全
前程安全是并发开发中的重点,因为我们的项目都运行在服务器中,关于线程对象的创建,线程的启动等,都已经实现完了。
重要的是,我们编写的程序要放在一个多线程的环境下,我们要关注的是一些数据在多线程并发的环境下是否是安全的。
当有多线程并发的环境,有共享数据的存在,而且共享数据有修改的行为。
怎样解决线程安全问题
使用线程同步机制,实际上就是让线程排队执行,不并发,就可以解决线程安全问题了。
线程排队会牺牲一部分效率,但此时会保证线程是安全的,数据的安全永远是前提。
异步编程机制:t1线程和t2线程各自执行各自的,实际上就是多线程并发。
同步变成机制:两个线程之间发生了等待关系,这就是同步编程模型。
重要案例一:银行取钱模型
// Account类package Account;public class Account {private String ac;private double money;public Account(){}public Account (String ac,double money){this.ac = ac;this.money = money;}public String getAc() {return ac;}public double getMoney() {return money;}public void setAc(String ac) {this.ac = ac;}public void setMoney(double money) {this.money = money;}//取钱public void withdraw(double money){// 当前账户有多少前double befor = this.money;// 取之后剩余多少前double after = befor - money;try {// 模拟网络延时,一定会出问题Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 修改setMoneythis.setMoney(after);}}
// myThread类package Account;public class myThread extends Thread{private Account acc;public myThread(Account acc){this.acc = acc;}@Overridepublic void run() {acc.withdraw(2500);// 打印信息System.out.println(Thread.currentThread().getName()+"取款2500,剩余"+acc.getMoney());}}
// 测试类package Account;public class test {public static void main(String[] args) {Account acc = new Account("账户1",5000);myThread t1 = new myThread(acc);myThread t2 = new myThread(acc);t1.setName("t1");t2.setName("t2");t1.start();t2.start();}}
输出结果
t1取款2500,剩余2500.0
t2取款2500,剩余2500.0
java中使用锁机制保证了线程安全问题
public void withdraw(double money){// 加上synchronized关键字synchronized (this){// 当前账户有多少前double befor = this.money;// 取之后剩余多少前double after = befor - money;try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// 修改setMoneythis.setMoney(after);}}
synchronized后面小括号中传入的“数据”是相当关键,这个数据必须是多线程共享的数据,才能达到多线程排队的效果。
在我们的程序中括号里是this对象,而这个this对象是主类中的Account类的实例对象,只有一个,虽然我们创建了两个线程,当这两个线程共用一个Account对象,而一个Account对象只有一把锁,t1/t2占用了这把锁,就会让之后的线程排队。
实际上不一定必须要是this
可以是字符串”abc”,因为字符串在常量池中只有一个,但缺陷就是如果再实例化一个Account对象,那么两个对象就只有一把锁了。
Object obj = new Object(); // 如果在类中实例化一个对象,把这个对象放在小括号里也是可以的// 因为内存中的堆只有一份,就算实例化了两个Account对象,他们只会指向堆中的一个obj地址,公用的。
关于锁池
关于变量的安全问题
实例变量:在堆中,堆在内存中只有一个,不同的线程操作堆时,就是并发操作,会存在安全问题。
静态变量:在方法区中,方法区中的静态变量也只有一个,和实例变量一样会存在安全问题。
局部变量:在栈中,永远都不会存在线程安全问题,因为局部变量不共享,一个线程一个栈,局部变量在栈中,所以局部变量永远都不会被共享。
public void withdraw(double money){int i = 100;i = 101;}
t1会修改t1栈中的i,t2会修改t2栈中的i,不存在线程安全问题。
在方法上也可以使用synchronized
public synchronized void withdraw(double money)
优点:代码简洁
缺点:此时锁的一定是this对象,不能是其他对象,这种方式不灵活。
举个例子
关于StringBuffer类和StringBuilder类,如果我们有一个局部变量中要用到StringBuffer类,不妨使用StringBuilder类,因为StringBuilder类,因为StringBuilder类虽然存在线程不安全问题,但是我们操作的局部变量一定不存在线程安全问题。
注:synchronized出现在静态方法上是找类锁。
死锁现象
死锁现象是由于对synchronized的使用不当,造成程序的死锁,这种错误不会出现异常,也不会出现错误,程序会一直僵持在那里,这种错误最难调试。
public class demo1 {public static void main(String[] args) {Object o1 = new Object();Object o2 = new Object();// t1,t2共用o1和o2对象myThread1 t1 = new myThread1(o1,o2);myThread1 t2 = new myThread1(o1,o2);t1.start();t2.start();}}class myThread1 extends Thread{Object o1;Object o2;public myThread1 (Object o1,Object o2){this.o1 = o1;this.o2 = o2;}@Overridepublic void run() {synchronized (o1){try {sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (o2){}}}}class myThread2 extends Thread{Object o1;Object o2;public myThread2 (Object o1,Object o2){this.o1 = o1;this.o2 = o2;}@Overridepublic void run() {synchronized (o2){try {sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (o1){}}}}
涉及到了synchronized的嵌套就可能导致死锁现象,在开发中最好不要使用锁的嵌套使用。
在实际的开发中应该怎样解决线程安全问题
实际上一上来就使用synchronized选择线程的同步不是一个最好的选择,synchronized会使程序的执行效率变低,用户体验差,在不得已的情况下在选择线程同步机制。
第一种方案,尽量使用局部变量代替成员变量,在多线程开发中成员变量都在堆中,而堆只有一个,一定会涉及到数据的共享,使用局部变量,一个局部变量一个栈,使得数据不共享,不存在线程不安全问题,比如说在run方法中定义变量。
第二种方案,如果要使用实例变量/成员变量,可以考虑创建多个对象,不同对象的实例变量的内存不共享。比如说创造多个Account对象。
第三种方案,只能选择synchronized,使用线程同步机制。
7.守护线程
我们以上定义的线程都叫用户线程,实际上java语言中还有一类线程是守护线程,也叫做后台线程,具有代表性的线程就是垃圾回收线程,当用户线程/主线程(主线程main方法就是一个用户线程)结束时,守护线程自动结束。一般来说守护线程是一个死循环。
public class demo1 {public static void main(String[] args) {myThread t1 = new myThread();t1.setName("守护线程");// 我希望我的主线程的for循环结束之后,守护线程也结束,用到setDaemon方法t1.setDaemon(true);t1.start();for (int i=1 ; i<10 ; i++){try {Thread.sleep(1000);System.out.println(Thread.currentThread().getName()+"--->"+i);} catch (InterruptedException e) {e.printStackTrace();}}}}class myThread extends Thread{@Overridepublic void run() {int i = 0;// 守护线程是个死循环while (true){try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}i++;System.out.println(Thread.currentThread().getName()+"--->"+i);}}}
8.生产者消费者模型
首先引入Object类中的两个有关线程的方法,wait方法和notify方法。这两个方法是java中任何一个对象都有的方法,他们不是通过线程调用的,而是通过普通方法调用的。
wait方法,表示让正在对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。会释放掉该线程之前占有的对象的锁。
notify方法,唤醒正在对象上等待的线程,还有一个notifyAll方法,唤醒在对象上处于等待的所有线程。只是通知,不会释放对象上之前占有的锁。
关于生产者消费者模型
生产者消费者模型模拟了一个这样的需求,
我们用list集合,假设在集合中只能存储1个元素,有1个元素就表示仓库满了,0个元素表示仓库空了,必须要做到这样的一个效果:生产1个消费1个。
// 主类package ProducerandConsumer;import java.util.LinkedList;import java.util.List;public class test {public static void main(String[] args) {// list模拟了一个仓库List list = new LinkedList();// 模拟了两个线程Producer pro = new Producer(list);Consumer con = new Consumer(list);pro.setName("生产");con.setName("消费");pro.start();con.start();}}
// 模拟了一个消费者类package ProducerandConsumer;import java.util.List;public class Consumer extends Thread {List list;public Consumer(List list) {this.list = list;}@Overridepublic void run() {// 让消费者一直消费while (true) {// 当消费的时候锁住listsynchronized (list) {if (list.size() == 0) {// 不能消费,等待生产线程// 此时释放了锁等生产者生产// 消费者线程停止try {list.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 程序执行到这里,说明可以消费,开始消费Object o = list.remove(0);System.out.println(Thread.currentThread().getName() + "--->" + o);// 通知生产者可以生产了,唤醒了list对象上的线程list.notify();}}}}
// 生产者类package ProducerandConsumer;import java.util.List;public class Producer extends Thread {List list;public Producer(List list) {this.list = list;}@Overridepublic void run() {while (true) {synchronized (list) {if (list.size() > 0) {// 不能生产,等待消费try {list.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 开始生产Object o = new Object();list.add(o);System.out.println(Thread.currentThread().getName() + "--->" + o);list.notify();}}}}
