多线程学习
线程概念
操作系统调用的是我们的进程,而一个进程里面包括许多任务,我们就需要将每个任务单独开始,这时光有进程是不够的,所以在进程里面在划分了线程,而线程才是cpu最小的执行单元,java内置了支持多线程的包。
操作系统分时调用多个进程,按时间片轮流执行每个进程,java多线程就是在操作系统给java程序的一个时间片内,在若干个独立的可控制的线程之间切换,每个进程都有一个内存区域,多个线程可以共享这部分的内存区域。
线程的4个生命周期
新建
当一个Thread类或其子类创建了一个对象,新生的线程处于新建状态,该线程已经有了内存空间和其它资源。线程创建之后只有内存资源,jvm线程调度器中还没有该线程,它必须调用start()方法通知JVM,这样JVM才会将该线程添加到线程调度器。
运行
当jvm线程调度器中的线程分配到了时间片时,这个线程就可以继续执行run方法。在线程还没有执行前,这个线程是和主线程绑定的,因为需要主线程调用该线程的start()方法。在线程第一次被分配到了CPU资源的时候,线程就脱离了主线程,具有了自己的生命周期。
中断
1.JVM将CPU资源从当前线程切换给其它线程(被动)。
2.线程执行期间执行了sleep类方法,使当前线程进入休眠状态,一旦执行该方法就立马让出CPU的使用权,当sleep时间完后,该线程在进入线程调度队列,等待被分配CPU资源。
3.线程调用了wait()方法,线程进入等待状态,等待状态的线程不会主动进入线程调度队列,排队等待CPU资源,必须由其他的线程调用notify()方法通知它,这个线程才会进入线程调度队列,排队等待CPU资源,
4.线程执行期间,某个操作进入了堵塞操作,如执行读写引起堵塞,进入了堵塞状态的线程不能进入线程调度队列,等待堵塞原因消除后,线程才进入线程调度队列。
死亡
1.线程正常完成了所有的工作,即执行完了run方法。
2.线程被提前强行终止,即强制run方法结束。
所谓了线程死亡,即线程所拥有的资源被释放了。
用Thread的子类来创建线程
通过Thread类的子类创建线程需要子类重写run方法,run方法内的代码才是线程执行的实体,而父类的run方法体内没有任何代码。这种方式创建线程可以在线程种添加新的属性和方法,但是不允许继承其它的类。
import java.lang.Thread;
public class ThreadTest{
public static void main(String args[]){
Test thread1 = new Test("1",100);
Test thread2 = new Test("2",200);
thread1.start();
thread2.start();
}
}
class Test extends Thread{
int n;
Test(String threadName,int n){
setName(threadName); //Thread类的类方法,给线程添加一个名称。
this.n = n;
}
public void run(){
try{
for(int i =0;i<4;i++){
//getName()Thread类的类方法,获取线程的名字。
System.out.println("I am Thread:"+getName());
//线程的类方法,让线程休眠n毫秒。该方法会触发InterruptedException异常
sleep(n);
}
}catch(InterruptedException e){}
}
}
用Runnable接口来创建线程
这种方式创建线程,需要使用Thread(Runnable target)构造方法来创建,传入的参数是一个实现了接口Runnable接口的类创建的对象,这个对象称为目标对象,当轮到当前线程执行时,目标对象就会自动调用run方法。
import java.lang.Thread;
import java.lang.Runnable;
public class ThreadTest{
public static void main(String args[]){
Test target1 = new Test("1",100);
Test target2 = new Test("2",200);
Thread thread1 = new Thread(target1);
Thread thread2 = new Thread(target2);
thread1.start();
thread2.start();
}
}
class Test implements Runnable{
int n;
String name;
Test(String threadName,int n){
this.n = n;
this.name = threadName;
}
public String getName(){
return this.name;
}
public void run(){
try{
for(int i =0;i<4;i++){
//getName()Thread类的类方法,获取线程的名字。
System.out.println("I am Thread:"+getName());
//线程的类方法,让线程休眠n毫秒。该方法会触发InterruptedException异常
Thread.sleep(n);
}
}catch(InterruptedException e){}
}
}
线程的优先级
java中的线程调度器来管理队列中的线程,调度器将线程分为1-10个优先级,默认的线程的优先级是5,可以使用Thread类的方法setPriority方法设置线程的优先级,该方法接收一个int类型参数。getPriority()方法返回线程的优先级,线程优先级高的线程是先执行的。不建议使用该方式来控制线程的执行,应该让每个线程都有执行的机会。
线程种一些常用的方法。
//让线程强制运行,强制运行期间其它的线程无法运行,必须等到该线程执行完毕。
public final void join() throws InterruptedException
//中断某个线程的执行
public void interrupt()
//主动让出线程的执行权力
yield方法
//获取当前线程的名称
public final String getName()
//设置线程的名称
public final synchronized void setName(String name)
//获取线程的优先级
public final int getPriority()
//设置线程的优先级
public final void setPriority(int newPriority)
//判断当前线程是否是活着的
public final native boolean isAlive()
//让线程进入睡眠状态
sleep()
//挂起线程
Wait()
线程安全的问题
例如去银行取钱,银行内部肯定是:
1.判断你要取的钱是否小于你存的钱才能取钱成功,
2.取钱后将你的余额进行修改。
例如当两个人同时在不同银行对一个账户取钱(对应两个线程同时对共享资源进行操作),账户总共1000元,第一个人取了700元,但是在第2步还没执行(即余额还没有改时),第二个人再次对该账户取钱,因为此时的余额还没有改, 则这个人看到的余额还是1000,那么这个人还能取至少0-1000元,显然这是不合理的,这就出现了线程不安全的问题。
这个问题产生的原因就是java多线程环境下执行的不确定性,cpu可能随时在多个就绪的线程之间进行切换。线程安全问题实际就是多个线程对共享资源进行操作的问题。
import java.lang.Thread;
import java.lang.Runnable;
public class Xiancheng {
public static void main(String args[]){
Account account = new Account("laoyu",1000);
DrawMoney d1 = new DrawMoney(account, 600);
DrawMoney d2 = new DrawMoney(account, 600);
Thread thread1 = new Thread(d1,"A");
Thread thread2 = new Thread(d1,"B");
thread1.start();
thread2.start();
}
}
class DrawMoney implements Runnable{
private Account account;
private int x;
DrawMoney(Account account,int x){
this.account = account;
this.x = x;
}
public void draw(int money){
try{
if(money<this.account.getMoney()){
System.out.println(Thread.currentThread().getName()+"取了:"+money+"元");
Thread.sleep(1000);
this.account.changeMoney(money);
System.out.println(Thread.currentThread().getName()+"余额:"+this.account.getMoney()+"元");
}else{
System.out.println("您的余额不足");
}
}catch(InterruptedException e){}
}
public void run(){
this.draw(x);
}
}
class Account{
private String accountid;
private int money;
Account(String accountid,int money){
this.accountid = accountid;
this.money = money;
}
public String getAccountid(){
return this.accountid;
}
public int getMoney(){
return this.money;
}
public void changeMoney(int change){
this.money = this.money - change;
}
}
解决方法
同步方法
对共享资源进行操作的方法加上synchronized关键字修饰,被改关键字修饰的方法不管在任何时刻,当最先执行该方法的线程执行完该同步方法之前,其它的线程是不能执行该方法的。
public synchronized void draw(int money)
同步代码块
同步方法会将同步的范围扩大,这时候同步代码块会比较好,格式是synchronized(obj){};其中的obj一部是共享的资源或者this。
synchronized(this.account){
if(money<this.account.getMoney()){
System.out.println(Thread.currentThread().getName()+"取了:"+money+"元");
Thread.sleep(1000);
this.account.changeMoney(money);
System.out.println(Thread.currentThread().getName()+"余额:"+this.account.getMoney()+"元");
}else{
System.out.println("您的余额不足");
}
}
lock对象
调用同步方法前需要获得线程锁,并且在同步代码执行完毕后需要释放锁
import java.lang.Thread;
import java.lang.Runnable;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Lock;
public class Xiancheng {
public static void main(String args[]){
Account account = new Account("laoyu",1000);
DrawMoney d1 = new DrawMoney(account, 600);
DrawMoney d2 = new DrawMoney(account, 600);
Thread thread1 = new Thread(d1,"A");
Thread thread2 = new Thread(d1,"B");
thread1.start();
thread2.start();
}
}
class DrawMoney implements Runnable{
private Account account;
private int x;
private Lock lock = new ReentrantLock();
DrawMoney(Account account,int x){
this.account = account;
this.x = x;
}
public void draw(int money){
lock.lock();
try{
if(money<this.account.getMoney()){
System.out.println(Thread.currentThread().getName()+"取了:"+money+"元");
Thread.sleep(1000);
this.account.changeMoney(money);
System.out.println(Thread.currentThread().getName()+"余额:"+this.account.getMoney()+"元");
}else{
System.out.println("您的余额不足");
}
}catch(InterruptedException e){}
finally{
lock.unlock();
}
}
public void run(){
this.draw(x);
}
}
class Account{
private String accountid;
private int money;
Account(String accountid,int money){
this.accountid = accountid;
this.money = money;
}
public String getAccountid(){
return this.accountid;
}
public int getMoney(){
return this.money;
}
public void changeMoney(int change){
this.money = this.money - change;
}
}
wait()notify()notifyAll()实现线程之间通信。
wait()—当线程调用wait方法会进入等待阻塞状态,只有通过其他的线程调用notify方法或notifyAll方法才会唤醒这个线程,让这个线程变为就绪状态。例如:在一个线程执行同步方法时,其中某个成员变量需要其它线程的辅助才能继续执行,这时就可以调用wait方法,让其它的线程调用该同步方法。
notify随机让一个wait的线程恢复。
而notifyAll让所有的wait线程恢复。
wait方法会让当前线程处于等待状态,其它的线程可以调用该同步方法(释放了锁,正常只有等同步方法返回时,其它的线程才可以调用该同步方法)。
import java.lang.Thread;
import java.lang.Runnable;
public class Xiancheng{
public static void main(String args[]){
Runable r1 = new Runable();
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r1);
t1.start();
t2.start();
}
}
class Runable implements Runnable{
T t = new T();
public synchronized void run(){
t.i=t.i+1;
try{
while(t.i<=1){
System.out.println("我是第一个线程:"+t.i);
wait();
}
}catch(InterruptedException e){}
finally{
System.out.println("我是第2个线程:"+t.i);
notifyAll();
}
}
}
class T{
public int i=0;
}
挂起和恢复线程
在实现了Runnable接口而穿件的线程时,可以在没有堵塞的线程中通过目标对象调用期同步方法,同步方法中分别是wait或notifyAll方法,来挂起或恢复线程。
import java.lang.Thread;
import java.lang.Runnable;
import java.lang.Object;
public class Xiancheng{
public static void main(String args[]){
Target target = new Target();
Thread thread = new Thread(target);
thread.start();
while(target.getStop()==false){
System.out.print(1);
}
target.restart();
}
}
class Target implements Runnable{
private int number=0;
public boolean stop = false;
public void run(){
while(this.number<=8){
this.number++;
System.out.println("现在的number="+number);
if(this.number==3){
try{
this.stop=true;
System.out.println("线程被挂起了");
this.handUp();
System.out.println("线程恢复了");
}catch(InterruptedException e){};
}
}
}
boolean getStop(){
return this.stop;
}
synchronized void handUp() throws InterruptedException{
wait();
}
synchronized void restart(){
notifyAll();
}
}
线程联合
当A线程执行了B.join()方法,即A线程联合了B线程,那么A线程会一直等到B线程执行完毕后才会继续执行。
守护线程
void setDaemon(boolean on)方法可以将一个线程设置为守护线程(该方法需要在start方法之前调用),守护线程在用户线程执行完毕后就会全部关闭,
死锁的问题
在多线程执行的时候,多个线程对资源进行了占用,而多个线程之间由必须要对方控制的资源才能继续进行下去,多个线程都处于等待状态。例如一个线程A在控制着键盘的资源,另一个线程B控制的打印机的资源,而这时两者都需要对方的资源才能执行下去,这时候两个线程都无法继续进行下去,这时候就产生了死锁的问题。
死锁产生的4个必要条件
1.资源的排他性:任何时刻该资源都只有一个线程占据该资源。
2.不剥夺条件:线程在所获得的资源未使用完毕之前,不能被其它的线程剥夺。
3.请求保持条件:线程在拥有了某个资源又去申请另一个资源。
4.循环等待:存在一个线程拥有某个资源,而另一个线程又在申请该资源。