线程
线程:
线程:一个线程可以看做是一个顺序流,它支持一个进程的运行,开启一个进程,有可能有一个或者多个线程运行启动
进程:在操作系统中每开启一个程序,都可以看做是一个进程
多线程:在同一个任务程序中可以有多个线程支持它运行
多进程:在操作系统中可以同时开启多个任务,一个任务代表一个进程
Java线程模型:
执行一个线程类 有java虚拟机虚拟出一个Cpu来,来处理线程类所做的操作,虚拟出来的cpu就可以对线程中数据或者代码进行运行,将对应的数据传递到线程对象中去,每次cpu执行的线程都是一个单一线程。
线程的创建有两种方法:
² 继承Thread类
此类实现了Runnable接口
Thread类的构造器:
package com.ibm.thread; public class FirstThread extends Thread { public void run(){ //在run方法中输出1到10并且加上线程的名称 for(int i = 1;i<=10;i++){ System.out.println(this.getName()+":"+i); System.out.println(Thread.currentThread().getName()+":"+i); } } public static void main(String[] args) { Thread tt = new FirstThread(); tt.start(); } //1、启动一个线程调用的是start方法 //2、线程的命名默认为Thread-num //3、通过继承创建线程类的对象是将子类对象赋给了父类的引用 //4、Thread.currentThread()是取到当前线程 在当前线程中可以用this关键字代替 //5、run方法所做的操作就是线程体 //6、一个线程在创建之后处于可运行状态,当调用start方法之后,那么线程就从可运行状态到运行状态 ,当执行完run中的内容,那么线程就结束。 //7、不可以直接调用run方法来执行线程体里面的内容 } |
*实现Runnable接口
创建线程需要实现Runnable接口,那么就应该将接口中未实现的方法实现过来,此接口中只有一个未实现的Run方法
package com.ibm.runnable; public class FirstRunnableimplements Runnable{ //继承Runnable接口,并且添加未实现的方法 @Override public void run() { //在线程体中输出1到10 for(int i=1;i<=10;i++){ System.out.println(Thread.currentThread().getName()+":"+i); } } //创建线程类的对象,启动线程 public static void main(String[] args) { //创建一个接口的实现类的对象 FirstRunnable fr = new FirstRunnable(); //将接口的实现类的对象赋给接口的引用并传递到Thread的构造器中去 Thread tt = new Thread(fr,"线程一"); tt.start(); } //1、创建线程需要实现Runnable接口 //2、需要实现接口中的run方法 //3、创建线程类的对象调方法 //4、Thread类中构造器传递的字符串就是线程的名字 //继承Thread类与实现Runnable的优缺点? //继承thread的优点:1、创建线程类对象时编写简单、可以直接使用this关键字 //缺点:继承thread类之后,那么此类就不能继承其他类 //实现Runnable接口的优点:1、可以继承其他类 //2、将cpu和代码数据分开形成清晰的模型 //实现多线程的时候一般情况下都是实现runnable接口,可以在方法中直接传递接口的引用, //每一个实现此接口的对象都可以传递到此方法中来 //如果是继承提thread类,那么就必须创建thread类的对象,传递的必须是thread类的对象,灵活性不高 //3、都要实现runnable接口,实现run方法 //缺点:不能使用this关键字 //创建线程类对象的不同? //继承thread类创建对象 thread 对象名 = new 子类构造(); 对象名.start(); //实现runnable接口 创建对象 实现类 实现类的对象名 = new 实现类的构造器(); //Thread thread类对象名 = new Thread(实现类的对象名) //thread类对象名.start(); } |
结束线程:怎么判断一个线程结束了
1.线程中线程体的操作执行完成,在执行过程中不出任何异常则执行完成
2.线程执行过程中线程结束或者线程出现错误则线程停止
package com.ibm.runnable; public class OutThread implements Runnable{ @Override public void run() { for(int i =1;i<=50;i++){ System.out.println(i); //当i输出到20的时候,我让程序退出 if(i==20){ //当i=20时程序退出,线程结束 System.exit(0); } } } public static void main(String[] args) { OutThread ot = new OutThread(); Thread tt = new Thread(ot); tt.start(); } } |
当i=20时,让程序退出,那么开启的输出1到20的线程就退出了,不会输出20之后的i的值
后台线程:
如果我们同时启动多个线程,那么可以让其中的某一个线程或者多个线程设置为后台线程,那么主线程结束,后台线程也跟着结束,后台线程依附于主线程运行
package com.ibm.daemon; public class DaeThread01 extends Thread{ public void run(){ //输出1到10000 for(int i=1;i<=1000;i++){ try { this.sleep(10);//让线程 睡眠10毫秒 //每循环一次让线程延迟10毫秒输出 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(this.getName()+i); } } } | package com.ibm.daemon; public class DaeThread02 { public static void main(String[] args) { Thread tt = new DaeThread01(); //将tt线程设置为后台线程默认守护线程为false //设置守护线程必须在线程启动之前 tt.setDaemon(true); tt.start(); //主线程结束,守护线程跟着主线程一起结束 for(int i=0;i<5;i++){ System.out.println(Thread.currentThread().getName()); } } } | |
线程的join方法
当一个线程正在执行的时候,那么在 某个条件下,可以让另一个线程B加入进来执行,但是线程A就需要停止,然后执行线程B,当线程B执行完之后,在接着执行A里面的内容
package com.ibm.join; public class JoinDemo01 extends Thread{ public void run(){ String[] str = {"a","b","c"}; for(int i=0;i<str.length;i++){ System.out.println(this.getName()+":"+str[i]); } } } | package com.ibm.join; public class JoinTest { public static void main(String[] args)throws InterruptedException { Thread tt = new JoinDemo01(); for(int i=0;i<100;i++){ Thread.sleep(1000); if(i==10){ //加入线程 tt.join(); tt.start(); } System.out.println(i); } } } | |
线程的isAlive方法:
package com.ibm.control; public class IsAliveDemo extendsThread{ publicvoidrun(){ for(int i=0;i<10;i++){ if(i==5){ System.out.println(this.getName()+"状态:"+this.isAlive()); } if(i==10){ System.out.println(this.getName()+"状态:"+this.isAlive()); } } } public static void main(String[] args) { Thread tt=new IsAliveDemo(); System.out.println("线程启动前:"+tt.isAlive()); tt.start(); System.out.println("线程启动后:"+tt.isAlive()); } //isAlive方法是用来判断一个线程是否是处于运行状态,在线程启动之前,或者线程 //执行完成后,那么线程都处于结束状态,那么isAlive方法返回false,如果线程启动 //后并且在线程的执行过程中线程的isAlive都是处于可运行状态,方法返回true } |
线程让步(Yield方法)
package com.ibm.control; public class YieldDemo extendsThread { //Yield方法属于线程让步,我们在创建二个或者多个线程的时候,如果没有给线程 //设置优先级或者设置其他的线程状态,那么创建线程访问CPU的机率是对等的。 //表示是停止当前正在执行的线程去执行其他的线程 publicvoid run(){ for(inti=0;i<20;i++){ yield(); //线程让步 System.out.println(this.getName()+"i="+i); } } } | package com.ibm.control; public class YieldDemo02 extends Thread{ publicvoidrun(){ for(int i=0;i<20;i++){ System.out.println(this.getName()+"i="+i); } } } | package com.ibm.control; public class YieldTest { public static void main(String[] args) { YieldDemo yd1=new YieldDemo(); //给线程赋名称 yd1.setName("线程一"); yd1.start(); YieldDemo02 yd2=new YieldDemo02(); yd2.setName("线程四"); yd2.start(); } //如果在一个线程上加入了yield方法表示的是此线程将某些访问cpu的权限给其他线程先执行 //自己访问线程的几率相比其他没有让步的线程就小了,如果CPU是多核的,那么此线程操作就不好控制 } |
|
创建一个以时间命名的文件存放在指定的目录中
package com.ibm.control; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; public class SleepDemo02 extends Thread{ public void run(){ for (int i = 0; i < 10; i++) { try { sleep(5*1000); show(); } catch (InterruptedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } } public voidshow() throws IOException{ //创建一个以时间命名的文件存放在制定的目录中 File file = new File("d:\\aa"); Date date =new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh-mm-ss-SSS"); String datename = sdf.format(date); String filename = datename+".js"; //拼接生成文件的完整路径 String str = file.getPath(); String name = str+"\\"+filename; File newFile = new File(name); if(!newFile.exists()){ newFile.createNewFile(); } } public static void main(String[] args)throws IOException { new SleepDemo02().start(); } } |
线程优先级
最小值,最大值
package com.ibm.control; public class PriorityDemo01 extends Thread{ publicvoid run(){ //输出线程的优先级的值 System.out.println("PriorityDemo01线程:"+this.getPriority()); //输出线程的最大值 System.out.println("线程优先级最大值:"+Thread.MAX_PRIORITY); System.out.println("线程优先级最小值:"+Thread.MIN_PRIORITY); } public static void main(String[] args) { System.out.println("main线程:"+Thread.currentThread().getPriority()); //启动PriorityDemo01线程 new PriorityDemo01().start(); } } |
两个线程设置优先级作比较
package com.ibm.control; public class PriorityDemo02 extends Thread { public void run() { for (int i = 0; i < 500; i++) { System.out.println(this.getName() +" " + i); } } } |
package com.ibm.control; public class PriorityDemo03 extends Thread { public void run() { for (int i = 0; i < 500; i++) { System.out.println(this.getName() +" " + i); } } }
package com.ibm.control; public class PriorityTest { public static void main(String[] args) { PriorityDemo03 pd3=new PriorityDemo03(); PriorityDemo02 pd2=new PriorityDemo02(); pd3.setName("线程2"); pd2.setName("线程1"); //将线程pd3的优先级设置为最小,那么它访问cpu的优先的机率也变小了 pd3.setPriority(1); //将pd2的优先级设置为最大,那么它访问cpu的优先级机率就变大了 pd2.setPriority(10); //优先级大的线程比优先级小的线程优先访问cpu的机率大 pd3.start(); pd2.start(); } } | |
wait()、 notify()、notifyAll()
wait() 使线程处于等待状态,那么此线程需要线程监视器来唤醒等待的线程接着执行
notify() 唤醒单个的线程使线程从等待状态转变成运行状态
notifyAll() 唤醒全部处于等待的线程
package com.ibm.wait; public class WaitDemo extends Thread{ // wait notify和notifyAll方法不是出自于Thread而是出自于Object类 //而Thread继承Object类就将对应的3个方法继承过去了 //wait() 使线程处于等待状态,那么此线程需要线程监视器来唤醒等待的线程接着执行 // notify()唤醒单个的线程使线程从等待状态转变成运行状态 // notifyAll()唤醒全部处于等待的线程 //有一个银行账户一个人甲对此账户进行存800块钱, //那么另外一个人就立马将这个账户中的钱取出来,始终保持账户中没有钱 //一个人甲存完钱之后,那么唤醒乙线程去取钱,乙取完钱之后,乙线程处于等待状态,在乙线程中 //唤醒甲线程取钱,最终甲乙线程只能有一个运行一个就是等待需要创建二个线程类和一个账户类, //还一个测试类 WaitDemo public static void main(String[] args) { //创建账号 Account a=new Account("中国银行",0); //创建存钱的线程类 QuitDemo qd=new QuitDemo(a, 200); qd.start(); SaveDemo ad=new SaveDemo(a, 200); ad.start(); } } | package com.ibm.wait; public class Account { //账号 //金额 //标记 boolean 用来标示账户中有没有钱 private String accountname; private double accountmoney = 0; boolean flag =false;// 就是用来标明存钱与取钱 false表明取钱 true表明存钱 public Account(String accountname,double accountmoney) { this.accountname = accountname; this.accountmoney = accountmoney; } //加上取到余额的方法 public double getAccountmoney() { returnaccountmoney; } //定义二个方法一个方法存钱,一个方法取钱 //存钱 public synchronized void insertMoney(double money) { if (flag ==true) { try { //存钱的线程处于等待状态 wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { //存钱 accountmoney += money; //存完钱之后是不是要账户的状态改为true System.out.println(Thread.currentThread().getName() +"存的钱数:" + money); flag =true; //唤醒处于等待状态的取钱线程 notifyAll(); } } //取钱 public synchronized void selectMoney(double money) { if (!flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else { //取钱 accountmoney =accountmoney - money; System.out .println(Thread.currentThread().getName() +"取钱为" + money); System.out.println("----余额为:---" + accountmoney); //将账户状态改为false flag =false; //唤醒存钱的线程往账户中存钱 notifyAll(); } } } | package com.ibm.wait; public class SaveDemo extends Thread { Account a; //定义一个你要存的钱数 double money; publicSaveDemo(Account a,doublemoney){ this.a=a; this.money=money; } publicvoidrun(){ for(int i=0;i<20;i++){ a.insertMoney(money); } } } | package com.ibm.wait; public class QuitDemo extends Thread { Account a; //账户 doublemoney;//取钱数 publicQuitDemo(Account a,double money){ this.a=a; this.money=money; } publicvoidrun(){ //取钱 for(int i=0;i<20;i++){ a.selectMoney(money); } } } |
|
关键字synchronized :
来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任一时刻只能由一个线程访问。
第一种SYN使用方法 | package com.ibm.sync; public class Test { // synchronized关键字可以使线程在同一时刻只能有一个线程访问某一个方法或者对象 //用synchronizd的对象或者方法就类似于给方法在运行之前加了锁,只能有一个线程访问 //访问此方法当线程访问此方法结束时,那么此方法由被锁状态到解锁状态,那么其他的线程 //在锁被释放之后,才能够操作此方法 //在现实生活中一个银行账户A,在A中存放了1200块钱,那么我们在同一时刻分别对A账户用 //存折(线程一)和卡(线程二)取1000钱,那么启动线程的时候,调用取钱的方法, //那么线程执行后,取出来的钱是2000 //一个人甲取钱,将用户名和密码正确输入,并且通过,表明甲在取钱之前的验证已经通过 //此时甲暂时离开,然后乙来了,也输入用户名和密码通过,甲乙两个人操作的账户是同一个账户 //乙取1000,甲回来了,甲也取1000,甲乙两个人一共取了2000,但是账户中只有1200, //在现实生活中这种情况是不可能发生的,那么我们就应该对发生的这种情况进行控制,控制在调用 //取钱方法时,只能有一个线程调用,如果调用的这个线程没有结束,那么其他的线程都不能够 //访问这个方法. public static void main(String[] args) { //创建账户 CwAcount ca = new CwAcount("mm", 1200); SbbDemo sd = new SbbDemo(ca, 1000); sd.setName("mm存折"); sd.start(); SbbDemo sd2 = new SbbDemo(ca, 1000); sd2.setName("mm卡"); sd2.start(); //对方法进行枷锁与对对象进行加锁实质上就是控制在同一时刻只能有一个线程 //访问方法或者对象,如果线程没有执行完,则其他线程处于等待状态,等待当前线程 //执行完之后在执行 } } | package com.ibm.sync; public class CwAcount { private Stringacountno; private double acountmoney; public CwAcount(String acountno,double acountmoney){ this.acountno=acountno; this.acountmoney=acountmoney; } //提供一个取钱的方法 //money表示你需要取的钱数 publicsynchronized void getMoney(double money)throws InterruptedException{ //取钱之前需要对账户的信息进行验证 if(acountmoney>=money){ Thread.sleep(1000); acountmoney=acountmoney-money; System.out.println(Thread.currentThread().getName()+"取钱成功,账户余额为:"+acountmoney); }else{ System.out.println("不好意思多赚点钱了再来!"); } } } | package com.ibm.sync; public class SbbDemo extends Thread{ CwAcount ca; double money; public SbbDemo(CwAcount ca ,double money){ this.ca=ca ; this.money= money; } public void run(){ //执行取钱操作 try { ca.getMoney(money); } catch (InterruptedException e) { e.printStackTrace(); } } } |
|
第二种SYN使用方法 | package com.ibm.sync; public class Test { // synchronized关键字可以使线程在同一时刻只能有一个线程访问某一个方法或者对象 //用synchronizd的对象或者方法就类似于给方法在运行之前加了锁,只能有一个线程访问 //访问此方法当线程访问此方法结束时,那么此方法由被锁状态到解锁状态,那么其他的线程 //在锁被释放之后,才能够操作此方法 //在现实生活中一个银行账户A,在A中存放了1200块钱,那么我们在同一时刻分别对A账户用 //存折(线程一)和卡(线程二)取1000钱,那么启动线程的时候,调用取钱的方法, //那么线程执行后,取出来的钱是2000 //一个人甲取钱,将用户名和密码正确输入,并且通过,表明甲在取钱之前的验证已经通过 //此时甲暂时离开,然后乙来了,也输入用户名和密码通过,甲乙两个人操作的账户是同一个账户 //乙取1000,甲回来了,甲也取1000,甲乙两个人一共取了2000,但是账户中只有1200, //在现实生活中这种情况是不可能发生的,那么我们就应该对发生的这种情况进行控制,控制在调用 //取钱方法时,只能有一个线程调用,如果调用的这个线程没有结束,那么其他的线程都不能够 //访问这个方法. public static void main(String[] args) { //创建账户 CwAcount ca = new CwAcount("mm", 1200); WmDemo wm = new WmDemo(ca, 1000); wm.setName("网名存折"); wm.start(); WmDemo wm1 = new WmDemo(ca, 1000); wm1.setName("网名卡"); wm1.start(); } } | package com.ibm.sync; public class CwAcount { private Stringacountno; private double acountmoney; public CwAcount(String acountno,double acountmoney){ this.acountno=acountno; this.acountmoney=acountmoney; } //提供一个取钱的方法 //money表示你需要取的钱数 public void getMoney(double money)throws InterruptedException{ //取钱之前需要对账户的信息进行验证 if(acountmoney>=money){ Thread.sleep(1000); acountmoney=acountmoney-money; System.out.println(Thread.currentThread().getName()+"取钱成功,账户余额为:"+acountmoney); }else{ System.out.println("不好意思多赚点钱了再来!"); } } } | package com.ibm.sync; public class WmDemoextends Thread{ CwAcount ca; double money; public WmDemo(CwAcount ca ,double money){ this.ca = ca ; this.money = money; } public void run(){ //对这个对象进行枷锁,表明只能有一个线程来访问这个对象 //当当前线程对这个对象操作结束后,那么其他线程才能够操作这个对象 synchronized (ca) { try { ca.getMoney(money); } catch (InterruptedException e) { e.printStackTrace(); } } } } |
|
多线程编程知识概括
使用synchronized关键字修饰一个对象时,表明此对象在任意时刻只能有一个线程来操作此对象,在操作对象时,其他线程处于等待状态,只有等待操作的线程执行完毕之后,那么其他的线程才能够操作此对象。
释放对象锁的条件:
² 当线程执行到synchronized()块结束时,释放对象锁。
² 当在synchronized()块中遇到break,return或抛出exception,则自动释放对象锁。
² 当一个线程调用wait()方法时,它放弃拥有的对象锁并进入等待队列(按照优先级来说,等待线程高于给对象加锁)
死锁:
就是两个线程之间相互等待,都等待对象释放资源供自己使用,了但是在同一时刻,两个线程都不释放资源,那么两个线程就一直处于等待状态,那么线程的死锁就出现了。线程的死锁是可以采取操作避免出现的。
1.Wait(), notify()notifyall()都是出自于Object类
2.调用wait()方法使线程处于等待状态,要想改变线程的等待状态,可以调用线程类的中断方法或者是调用object()类的notify()或者notifyAll方法
3.notify是用来唤醒单个线程的等待状态 notifyAll是用来唤醒多个线程等待状态
同步方法:指的是线程中调用方法时同步,具体的指明多个线程同时调用一个相同的方法,一般情况下,这样会造成同时执行操作会降低处理效率。
避免无谓的同步方法
因为同步会降低程序的执行效率,所以应该避免无谓的同步
通过所谓的Fine-Grained锁的机制,可以避免这种情况
多线程编程一般规则:
如果两个或两个以上的线程都修改一个对象,那么把执行修改的方法定义为被同步的,如果对象更新影响到只读方法,那么只读方法也要定义成同步的。
不要滥用同步。如果在一个对象内的不同的方法访问的不是同一个数据,就不要将方法设置为synchronized的。
如果一个线程必须等待一个对象状态发生变化,那么他应该在对象内部等待,而不是在外部。他可以通过调用一个被同步的方法,并让这个方法调用wait()。
每当一个方法返回某个对象的锁时,它应当调用notifyAll()来让等待队列中的其他线程有机会执行。
记住wait()和notify()/notifyAll()是Object类方法,而不是Thread类的方法。仔细查看每次调用wait()方法(等待方法),都有相应的notify()/notifyAll()方法(唤醒方法),且它们均作用于同一个对象。
多线程编程一般规则(con.)
a)针对wait()、notify()/notifyAll()使用旋锁(spin lock);
b)优先使用notifyAll()而不是notify();
c)按照固定的顺序获得多个对象锁,以避免死锁;避免同时对对象进行加锁
d)不要对上锁的对象改变它的引用;
e)不要滥用同步机制,避免无谓的同步控制。